From 656b6951b6302b171cce983cfc4e6915baedef66 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Sat, 15 Apr 2023 01:32:16 -0400 Subject: [PATCH] Add confusion and frenzy move effects --- src/battle-anims.ts | 10 +- src/battle-phases.ts | 134 ++++++++++++++++--------- src/battle-tag.ts | 112 +++++++++++++++++++-- src/messages.ts | 5 + src/modifier-type.ts | 2 +- src/move.ts | 173 ++++++++++++++++++++++++-------- src/pokemon.ts | 231 +++++++++++++++++++++---------------------- 7 files changed, 450 insertions(+), 217 deletions(-) create mode 100644 src/messages.ts diff --git a/src/battle-anims.ts b/src/battle-anims.ts index 9142b9e48..f7591d995 100644 --- a/src/battle-anims.ts +++ b/src/battle-anims.ts @@ -332,7 +332,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent { moveAnim.bgSprite.setScale(1.25); moveAnim.bgSprite.setAlpha(0); scene.field.add(moveAnim.bgSprite); - scene.field.moveAbove(moveAnim.bgSprite, scene.arenaEnemy); + scene.field.moveBelow(moveAnim.bgSprite, scene.getEnemyPokemon()); scene.tweens.add({ targets: moveAnim.bgSprite, @@ -766,15 +766,15 @@ export class MoveAnim extends BattleAnim { getAnim(): Anim { return moveAnims.get(this.move) instanceof Anim ? moveAnims.get(this.move) as Anim - : moveAnims.get(this.move)[this.user instanceof PlayerPokemon ? 0 : 1] as Anim; + : moveAnims.get(this.move)[this.user.isPlayer() ? 0 : 1] as Anim; } isOppAnim(): boolean { - return this.user instanceof EnemyPokemon && Array.isArray(moveAnims.get(this.move)); + return !this.user.isPlayer() && Array.isArray(moveAnims.get(this.move)); } isReverseCoords(): boolean { - return this.user instanceof EnemyPokemon && !this.isOppAnim(); + return !this.user.isPlayer() && !this.isOppAnim(); } getGraphicScale(): number { @@ -799,7 +799,7 @@ export class MoveChargeAnim extends MoveAnim { getAnim(): Anim { return chargeAnims.get(this.chargeAnim) instanceof Anim ? chargeAnims.get(this.chargeAnim) as Anim - : chargeAnims.get(this.chargeAnim)[this.user instanceof PlayerPokemon ? 0 : 1] as Anim; + : chargeAnims.get(this.chargeAnim)[this.user.isPlayer() ? 0 : 1] as Anim; } } diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 46e5ad712..5cf4d3bcd 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -1,5 +1,5 @@ import BattleScene from "./battle-scene"; -import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult } from "./pokemon"; +import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult } from "./pokemon"; import * as Utils from './utils'; import { allMoves, applyMoveAttrs, ChargeAttr, HitsTagAttr, MissEffectAttr, MoveCategory, MoveEffectAttr, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr } from "./move"; import { Mode } from './ui/ui'; @@ -8,7 +8,7 @@ import { Stat } from "./pokemon-stat"; import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, HitHealModifier } from "./modifier"; import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./pokeball"; -import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveAnim, chargeAnims, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; +import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { StatusEffect, getStatusEffectActivationText, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./status-effect"; import { SummaryUiMode } from "./ui/summary-ui-handler"; import EvolutionSceneHandler from "./ui/evolution-scene-handler"; @@ -19,7 +19,8 @@ import { Biome, biomeLinks } from "./biome"; import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier-type"; import PokemonSpecies from "./pokemon-species"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { BattleTagLapseType } from "./battle-tag"; +import { BattleTagLapseType, BattleTagType, HideSpriteTag as HiddenTag } from "./battle-tag"; +import { getPokemonMessage } from "./messages"; export class SelectStarterPhase extends BattlePhase { constructor(scene: BattleScene) { @@ -385,6 +386,11 @@ export class CheckSwitchPhase extends BattlePhase { return; } + if (this.scene.getPlayerPokemon().getTag(BattleTagType.FRENZY)) { + super.end(); + return; + } + this.scene.ui.showText('Will you switch\nPOKéMON?', null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.unshiftPhase(new SwitchPhase(this.scene, false, true)); @@ -551,7 +557,7 @@ export class CommonAnimPhase extends PokemonPhase { } } -abstract class MovePhase extends BattlePhase { +export abstract class MovePhase extends BattlePhase { protected pokemon: Pokemon; protected move: PokemonMove; protected cancelled: boolean; @@ -570,20 +576,24 @@ abstract class MovePhase extends BattlePhase { return !!this.pokemon.hp; } + cancel(): void { + this.cancelled = true; + } + start() { super.start(); + this.pokemon.lapseTags(BattleTagLapseType.MOVE); + const doMove = () => { if (this.cancelled) { this.end(); return; } - if (!this.move) - console.log(this.pokemon.moveset); + this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500)); + this.scene.unshiftPhase(this.getEffectPhase()); if (this.pokemon.summonData.moveQueue.length && !this.pokemon.summonData.moveQueue.shift().ignorePP) this.move.ppUsed++; - this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.pokemon instanceof EnemyPokemon ? 'Foe ' : ''}${this.pokemon.name} used\n${this.move.getName()}!`, 500)); - this.scene.unshiftPhase(this.getEffectPhase()); this.end(); }; @@ -616,12 +626,13 @@ abstract class MovePhase extends BattlePhase { } if (activated) { this.scene.unshiftPhase(new MessagePhase(this.scene, - `${this.pokemon instanceof PlayerPokemon ? '' : 'Foe '}${this.pokemon.name}${getStatusEffectActivationText(this.pokemon.status.effect)}`)); - new CommonBattleAnim(CommonAnim.POISON + (this.pokemon.status.effect - 1), this.pokemon).play(this.scene, () => doMove()); + getPokemonMessage(this.pokemon, getStatusEffectActivationText(this.pokemon.status.effect)))); + this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.isPlayer(), CommonAnim.POISON + (this.pokemon.status.effect - 1))); + doMove(); } else { if (healed) { this.scene.unshiftPhase(new MessagePhase(this.scene, - `${this.pokemon instanceof PlayerPokemon ? '' : 'Foe '}${this.pokemon.name}${getStatusEffectHealText(this.pokemon.status.effect)}`)); + getPokemonMessage(this.pokemon, getStatusEffectHealText(this.pokemon.status.effect)))); this.pokemon.resetStatus(); this.pokemon.updateInfo(true); } @@ -678,7 +689,7 @@ abstract class MoveEffectPhase extends PokemonPhase { return; } - user.lapseTags(BattleTagLapseType.MOVE); + user.lapseTags(BattleTagLapseType.MOVE_EFFECT); if (user.turnData.hitsLeft === undefined) { const hitCount = new Utils.IntegerHolder(1); @@ -688,7 +699,7 @@ abstract class MoveEffectPhase extends PokemonPhase { } if (!this.hitCheck()) { - this.scene.unshiftPhase(new MessagePhase(this.scene, `${!this.player ? 'Foe ' : ''}${user.name}'s\nattack missed!`)); + this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(user, '\'s\nattack missed!'))); user.summonData.moveHistory.push({ move: this.move.moveId, result: MoveResult.MISSED }); applyMoveAttrs(MissEffectAttr, this.scene, user, target, this.move.getMove()); this.end(); @@ -696,25 +707,22 @@ abstract class MoveEffectPhase extends PokemonPhase { } new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () => { - target.apply(user, this.move).then(result => { - ++user.turnData.hitCount; - user.summonData.moveHistory.push({ move: this.move.moveId, result: result }); - if (user.hp <= 0) { - this.scene.pushPhase(new FaintPhase(this.scene, this.player)); - target.resetBattleSummonData(); - } - if (target.hp <= 0) { - this.scene.pushPhase(new FaintPhase(this.scene, !this.player)); - this.getUserPokemon().resetBattleSummonData(); - } - if (target.hp) { - applyMoveAttrs(MoveEffectAttr, this.scene, user, target, this.move.getMove()); - // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present - if (!this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) - applyMoveAttrs(MoveHitEffectAttr, this.scene, user, target, this.move.getMove()); - } - this.end(); - }); + const result = target.apply(user, this.move); + ++user.turnData.hitCount; + user.summonData.moveHistory.push({ move: this.move.moveId, result: result }); + if (user.hp <= 0) { + this.scene.pushPhase(new FaintPhase(this.scene, this.player)); + target.resetBattleSummonData(); + } + if (target.hp <= 0) { + this.scene.pushPhase(new FaintPhase(this.scene, !this.player)); + this.getUserPokemon().resetBattleSummonData(); + } + applyMoveAttrs(MoveEffectAttr, this.scene, user, target, this.move.getMove()); + // Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present + if (target.hp && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) + applyMoveAttrs(MoveHitEffectAttr, this.scene, user, target, this.move.getMove()); + this.end(); }); }); } @@ -736,7 +744,7 @@ abstract class MoveEffectPhase extends PokemonPhase { hitCheck(): boolean { // Check if not self targeting for this - const hiddenTag = this.getTargetPokemon().getTag(t => t.isHidden()); + const hiddenTag = this.getTargetPokemon().getTag(HiddenTag); if (hiddenTag) { if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) return false; @@ -847,7 +855,8 @@ export class StatChangePhase extends PokemonPhase { constructor(scene: BattleScene, player: boolean, stats: BattleStat[], levels: integer) { super(scene, player); - this.stats = stats; + const allStats = Utils.getEnumValues(BattleStat); + this.stats = stats.map(s => s !== BattleStat.RAND ? s : allStats[Utils.randInt(BattleStat.SPD + 1)]); this.levels = levels; } @@ -915,7 +924,7 @@ export class StatChangePhase extends PokemonPhase { const messages: string[] = []; for (let s = 0; s < this.stats.length; s++) - messages.push(`${this.player ? '' : 'Foe '}${this.getPokemon().name}'s ${getBattleStatName(this.stats[s])} ${getBattleStatLevelChangeDescription(Math.abs(relLevels[s]), this.levels >= 1)}!`); + messages.push(getPokemonMessage(this.getPokemon(), `'s ${getBattleStatName(this.stats[s])} ${getBattleStatLevelChangeDescription(Math.abs(relLevels[s]), this.levels >= 1)}!`)); return messages; } } @@ -935,7 +944,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase { if (pokemon.trySetStatus(this.statusEffect)) { pokemon.updateInfo(true); new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => { - this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.player ? '' : 'Foe '}${pokemon.name}${getStatusEffectObtainText(this.statusEffect)}`)); + this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect)))); if (pokemon.status.isPostTurn()) this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.player)); this.end(); @@ -943,7 +952,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase { return; } } else if (pokemon.status.effect === this.statusEffect) - this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.player ? '' : 'Foe '}${pokemon.name}${getStatusEffectOverlapText(this.statusEffect)}`)); + this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(pokemon, getStatusEffectOverlapText(this.statusEffect)))); this.end(); } } @@ -959,7 +968,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { pokemon.status.incrementTurn(); new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => { this.scene.unshiftPhase(new MessagePhase(this.scene, - `${pokemon instanceof PlayerPokemon ? '' : 'Foe '}${pokemon.name}${getStatusEffectActivationText(pokemon.status.effect)}`)); + getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect)))); switch (pokemon.status.effect) { case StatusEffect.POISON: case StatusEffect.BURN: @@ -1000,6 +1009,44 @@ export class MessagePhase extends BattlePhase { } } +export class DamagePhase extends PokemonPhase { + private damage: integer; + private damageResult: DamageResult; + + constructor(scene: BattleScene, player: boolean, damageResult?: DamageResult) { + super(scene, player); + + this.damageResult = damageResult || MoveResult.EFFECTIVE; + } + + start() { + super.start(); + + switch (this.damageResult) { + case MoveResult.EFFECTIVE: + this.scene.sound.play('hit'); + break; + case MoveResult.SUPER_EFFECTIVE: + this.scene.sound.play('hit_strong'); + break; + case MoveResult.NOT_VERY_EFFECTIVE: + this.scene.sound.play('hit_weak'); + break; + } + + const flashTimer = this.scene.time.addEvent({ + delay: 100, + repeat: 5, + startAt: 200, + callback: () => { + this.getPokemon().getSprite().setVisible(flashTimer.repeatCount % 2 === 0); + if (!flashTimer.repeatCount) + this.getPokemon().updateInfo().then(() => this.end()); + } + }); + } +} + export class FaintPhase extends PokemonPhase { constructor(scene: BattleScene, player: boolean) { super(scene, player); @@ -1008,13 +1055,12 @@ export class FaintPhase extends PokemonPhase { start() { super.start(); - if (this.player) { - this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.getPokemon().name} fainted!`, null, true)); + this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(this.getPokemon(), ' fainted!'), null, true)); + + if (this.player) this.scene.unshiftPhase(new SwitchPhase(this.scene, true, false)); - } else { - this.scene.unshiftPhase(new MessagePhase(this.scene, `Foe ${this.getPokemon().name} fainted!`, null, true)); + else this.scene.unshiftPhase(new VictoryPhase(this.scene)); - } const pokemon = this.getPokemon(); @@ -1031,7 +1077,7 @@ export class FaintPhase extends PokemonPhase { onComplete: () => { pokemon.setVisible(false); pokemon.y -= 150; - if (pokemon instanceof PlayerPokemon) + if (pokemon.isPlayer()) this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); this.scene.field.remove(pokemon); this.end(); diff --git a/src/battle-tag.ts b/src/battle-tag.ts index 3b270fc25..c68238b81 100644 --- a/src/battle-tag.ts +++ b/src/battle-tag.ts @@ -1,5 +1,14 @@ +import { CommonAnim } from "./battle-anims"; +import { CommonAnimPhase, DamagePhase, MessagePhase, MovePhase } from "./battle-phases"; +import { getPokemonMessage } from "./messages"; +import Pokemon from "./pokemon"; +import { Stat } from "./pokemon-stat"; +import * as Utils from "./utils"; + export enum BattleTagType { NONE, + CONFUSED, + FRENZY, FLYING, UNDERGROUND } @@ -7,7 +16,9 @@ export enum BattleTagType { export enum BattleTagLapseType { FAINT, MOVE, - TURN_END + MOVE_EFFECT, + TURN_END, + CUSTOM } export class BattleTag { @@ -21,13 +32,98 @@ export class BattleTag { this.turnCount = turnCount; } - isHidden() { - switch (this.tagType) { - case BattleTagType.FLYING: - case BattleTagType.UNDERGROUND: - return true; - } + onAdd(pokemon: Pokemon): void { } - return false; + onRemove(pokemon: Pokemon): void { } + + onOverlap(pokemon: Pokemon): void { } + + lapse(pokemon: Pokemon): boolean { + return !!--this.turnCount; + } +} + +export class PseudoStatusTag extends BattleTag { + constructor(tagType: BattleTagType, turnCount: integer) { + super(tagType, BattleTagLapseType.MOVE, turnCount); + } +} + +export class ConfusedTag extends PseudoStatusTag { + constructor(tagType: BattleTagType, turnCount: integer) { + super(tagType, turnCount); + } + + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION)); + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' became\nconfused!'))); + } + + onRemove(pokemon: Pokemon): void { + super.onRemove(pokemon); + + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' snapped\nout of confusion!'))); + } + + onOverlap(pokemon: Pokemon): void { + super.onOverlap(pokemon); + + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is\nalready confused!'))); + } + + lapse(pokemon: Pokemon): boolean { + const ret = super.lapse(pokemon); + + if (ret) { + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is\nconfused!'))); + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION)); + + 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)); + pokemon.hp = Math.max(pokemon.hp - damage, 0); + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, 'It hurt itself in its\nconfusion!')); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), damage)); + (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); + } + } + + return ret; + } +} + +export class HideSpriteTag extends BattleTag { + constructor(tagType: BattleTagType, turnCount: integer) { + super(tagType, BattleTagLapseType.MOVE_EFFECT, turnCount); + } + + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + pokemon.setVisible(false); + } + + onRemove(pokemon: Pokemon): void { + // Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible + pokemon.scene.tweens.addCounter({ + duration: 2, + useFrames: true, + onComplete: () => pokemon.setVisible(true) + }); + } +} + +export function getBattleTag(tagType: BattleTagType, turnCount: integer): BattleTag { + switch (tagType) { + case BattleTagType.CONFUSED: + return new ConfusedTag(tagType, turnCount); + case BattleTagType.FLYING: + case BattleTagType.UNDERGROUND: + return new HideSpriteTag(tagType, turnCount); + default: + return new BattleTag(tagType, BattleTagLapseType.CUSTOM, turnCount); } } \ No newline at end of file diff --git a/src/messages.ts b/src/messages.ts new file mode 100644 index 000000000..495ff3ebd --- /dev/null +++ b/src/messages.ts @@ -0,0 +1,5 @@ +import Pokemon, { EnemyPokemon } from "./pokemon"; + +export function getPokemonMessage(pokemon: Pokemon, content: string): string { + return `${!pokemon.isPlayer() ? 'Wild ' : ''}${pokemon.name}${content}`; +} \ No newline at end of file diff --git a/src/modifier-type.ts b/src/modifier-type.ts index b47353fa5..f33d67607 100644 --- a/src/modifier-type.ts +++ b/src/modifier-type.ts @@ -498,8 +498,8 @@ export function getModifierTypeOptionsForWave(waveIndex: integer, count: integer function getNewModifierTypeOption(party: PlayerPokemon[], tier?: ModifierTier, upgrade?: boolean): ModifierTypeOption { const tierValue = Utils.randInt(256); if (tier === undefined) { - tier = (tierValue >= 52 ? ModifierTier.COMMON : tierValue >= 8 ? ModifierTier.GREAT : tierValue >= 1 ? ModifierTier.ULTRA : ModifierTier.MASTER) + (upgrade ? 1 : 0); upgrade = Utils.randInt(32) === 0; + tier = (tierValue >= 52 ? ModifierTier.COMMON : tierValue >= 8 ? ModifierTier.GREAT : tierValue >= 1 ? ModifierTier.ULTRA : ModifierTier.MASTER) + (upgrade ? 1 : 0); } const thresholds = Object.keys(modifierPoolThresholds[tier]); const totalWeight = parseInt(thresholds[thresholds.length - 1]); diff --git a/src/move.ts b/src/move.ts index bbd8eead3..cf3a40a7f 100644 --- a/src/move.ts +++ b/src/move.ts @@ -3,10 +3,11 @@ import { EnemyMovePhase, MessagePhase, ObtainStatusEffectPhase, PlayerMovePhase, import BattleScene from "./battle-scene"; import { BattleStat } from "./battle-stat"; import Pokemon, { EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "./pokemon"; -import { BattleTagLapseType, BattleTagType } from "./battle-tag"; +import { BattleTagType } from "./battle-tag"; import { StatusEffect } from "./status-effect"; import { Type } from "./type"; import * as Utils from "./utils"; +import { getPokemonMessage } from "./messages"; export enum MoveCategory { PHYSICAL, @@ -622,10 +623,6 @@ export enum Moves { FUSION_BOLT }; -const enum MoveEffectText { - BUT_IT_FAILED = 'But it failed!' -} - type MoveAttrFunc = (scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) => void; export abstract class MoveAttr { @@ -635,6 +632,21 @@ export abstract class MoveAttr { } export class MoveEffectAttr extends MoveAttr { + public selfTarget: boolean; + + constructor(selfTarget?: boolean) { + super(); + + this.selfTarget = !!selfTarget; + } + + canApply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]) { + return !!(this.selfTarget ? user.hp && !user.getTag(BattleTagType.FRENZY) : target.hp); + } + + apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]) { + return this.canApply(scene, user, target, move, args); + } } export class MoveHitEffectAttr extends MoveAttr { @@ -702,10 +714,12 @@ class StatusEffectAttr extends MoveHitEffectAttr { apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { const statusCheck = move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance; if (statusCheck) { - if (!target.status || (target.status.effect === this.effect && move.chance < 0)) - scene.unshiftPhase(new ObtainStatusEffectPhase(scene, target instanceof PlayerPokemon, this.effect)); + if (!target.status || (target.status.effect === this.effect && move.chance < 0)) { + scene.unshiftPhase(new ObtainStatusEffectPhase(scene, target.isPlayer(), this.effect)); + return true; + } } - return true; + return false; } } @@ -740,9 +754,9 @@ export class ChargeAttr extends OverrideMoveEffectAttr { if (!lastMove.length || lastMove[0].move !== move.id || lastMove[0].result !== MoveResult.OTHER) { (args[0] as Utils.BooleanHolder).value = true; new MoveChargeAnim(this.chargeAnim, move.id, user, target).play(scene, () => { - scene.unshiftPhase(new MessagePhase(scene, `${user instanceof EnemyPokemon ? 'Foe ' : ''}${user.name} ${this.chargeText}`)); + scene.unshiftPhase(new MessagePhase(scene, getPokemonMessage(user, ` ${this.chargeText}`))); if (this.tagType) - user.addTag(this.tagType, BattleTagLapseType.MOVE); + user.addTag(this.tagType); if (this.chargeEffect) applyMoveAttrs(MoveEffectAttr, scene, user, target, move); user.summonData.moveHistory.push({ move: move.id, result: MoveResult.OTHER }); @@ -750,7 +764,7 @@ export class ChargeAttr extends OverrideMoveEffectAttr { resolve(true); }); } else - resolve(true); + resolve(false); }); } } @@ -758,29 +772,36 @@ export class ChargeAttr extends OverrideMoveEffectAttr { export class StatChangeAttr extends MoveEffectAttr { public stats: BattleStat[]; public levels: integer; - public selfTarget: boolean; constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean) { - super(); + super(selfTarget); this.stats = typeof(stats) === 'number' ? [ stats as BattleStat ] : stats as BattleStat[]; this.levels = levels; - this.selfTarget = !!selfTarget; } apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) - scene.unshiftPhase(new StatChangePhase(scene, user instanceof PlayerPokemon === this.selfTarget, this.stats, this.levels)); - return true; + if (!super.apply(scene, user, target, move, args)) + return false; + + if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { + scene.unshiftPhase(new StatChangePhase(scene, user.isPlayer() === this.selfTarget, this.stats, this.levels)); + return true; + } + + return false; } } class FlinchAttr extends MoveHitEffectAttr { apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (Utils.randInt(100) < move.chance) + if (Utils.randInt(100) < move.chance) { target.turnData.flinched = true; - return true; + return true; + } + + return false; } } @@ -799,6 +820,73 @@ export class MissEffectAttr extends MoveAttr { } } +export class FrenzyAttr extends MoveEffectAttr { + constructor() { + super(true); + } + + canApply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]) { + return !!(this.selfTarget ? user.hp : target.hp); + } + + apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.apply(scene, user, target, move, args)) + return false; + + if (!user.summonData.moveQueue.length) { + if (!user.getTag(BattleTagType.FRENZY)) { + const turnCount = Utils.randInt(2) + 1; + new Array(turnCount).fill(null).map(() => user.summonData.moveQueue.push({ move: move.id, ignorePP: true })); + user.addTag(BattleTagType.FRENZY); + console.log('add frenzy'); + } else { + applyMoveAttrs(AddTagAttr, scene, user, target, move, args); + user.lapseTag(BattleTagType.FRENZY); + console.log('remove frenzy'); + } + return true; + } + + return false; + } +} + +const frenzyMissFunc = (scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) => { + while (user.summonData.moveQueue.length && user.summonData.moveQueue[0].move === move.id) + user.summonData.moveQueue.shift(); + user.lapseTag(BattleTagType.FRENZY) +}; + +export class AddTagAttr extends MoveEffectAttr { + public tagType: BattleTagType; + public turnCount: integer; + + constructor(tagType: BattleTagType, turnCount: integer, selfTarget?: boolean) { + super(selfTarget); + + this.tagType = tagType; + this.turnCount = turnCount; + } + + apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (!super.apply(scene, user, target, move, args)) + return false; + + if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { + (this.selfTarget ? user : target).addTag(this.tagType, this.turnCount); + return true; + } + + return false; + } +} + +export class ConfuseAttr extends AddTagAttr { + constructor(selfTarget?: boolean) { + super(BattleTagType.CONFUSED, Utils.randInt(4, 1), selfTarget); + } +} + export class HitsTagAttr extends MoveAttr { public tagType: BattleTagType; public doubleDamage: boolean; @@ -811,6 +899,8 @@ export class HitsTagAttr extends MoveAttr { } } +type AttrPredicate = (moveAttr: MoveAttr) => boolean; + export function applyMoveAttrs(attrType: { new(...args: any[]): MoveAttr }, scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise { return new Promise(resolve => { const attrPromises: Promise[] = []; @@ -830,7 +920,7 @@ class RandomMoveAttr extends OverrideMoveEffectAttr { const moveIds = Utils.getEnumValues(Moves).filter(m => m !== move.id); const moveId = moveIds[Utils.randInt(moveIds.length)]; user.summonData.moveQueue.push({ move: moveId, ignorePP: true }); - scene.unshiftPhase(user instanceof PlayerPokemon ? new PlayerMovePhase(scene, user, new PokemonMove(moveId)) : new EnemyMovePhase(scene, user as EnemyPokemon, new PokemonMove(moveId))); + scene.unshiftPhase(user.isPlayer() ? new PlayerMovePhase(scene, user as PlayerPokemon, new PokemonMove(moveId)) : new EnemyMovePhase(scene, user as EnemyPokemon, new PokemonMove(moveId))); initMoveAnim(moveId).then(() => { loadMoveAnimAssets(scene, [ moveId ], true) .then(() => resolve(true)); @@ -879,7 +969,8 @@ export const allMoves = [ new AttackMove(Moves.BODY_SLAM, "Body Slam", Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 66, "May paralyze opponent.", 30, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), new AttackMove(Moves.WRAP, "Wrap", Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1), new AttackMove(Moves.TAKE_DOWN, "Take Down", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 1, "User receives recoil damage.", -1, 1), - new AttackMove(Moves.THRASH, "Thrash", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1), + new AttackMove(Moves.THRASH, "Thrash", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1, + new FrenzyAttr(), frenzyMissFunc, new ConfuseAttr(true)), // TODO: Update to still confuse if last hit misses new AttackMove(Moves.DOUBLE_EDGE, "Double-Edge", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, "User receives recoil damage.", -1, 1), new StatusMove(Moves.TAIL_WHIP, "Tail Whip", Type.NORMAL, 100, 30, -1, "Lowers opponent's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, -1)), new AttackMove(Moves.POISON_STING, "Poison Sting", Type.POISON, MoveCategory.PHYSICAL, 15, 100, 35, -1, "May poison the opponent.", 30, 1, new StatusEffectAttr(StatusEffect.POISON)), @@ -890,7 +981,7 @@ export const allMoves = [ new StatusMove(Moves.GROWL, "Growl", Type.NORMAL, 100, 40, -1, "Lowers opponent's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, -1)), new StatusMove(Moves.ROAR, "Roar", Type.NORMAL, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 1), new StatusMove(Moves.SING, "Sing", Type.NORMAL, 55, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), - new StatusMove(Moves.SUPERSONIC, "Supersonic", Type.NORMAL, 55, 20, -1, "Confuses opponent.", -1, 1), + new StatusMove(Moves.SUPERSONIC, "Supersonic", Type.NORMAL, 55, 20, -1, "Confuses opponent.", -1, 1, new ConfuseAttr()), new AttackMove(Moves.SONIC_BOOM, "Sonic Boom", Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, "Always inflicts 20 HP.", -1, 1), new StatusMove(Moves.DISABLE, "Disable", Type.NORMAL, 100, 20, -1, "Opponent can't use its last attack for a few turns.", -1, 1), new AttackMove(Moves.ACID, "Acid", Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)), @@ -902,7 +993,7 @@ export const allMoves = [ new AttackMove(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 1), new AttackMove(Moves.ICE_BEAM, "Ice Beam", Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 135, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)), new AttackMove(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)), - new AttackMove(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 1), + new AttackMove(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 1, new ConfuseAttr()), new AttackMove(Moves.BUBBLE_BEAM, "Bubble Beam", Type.WATER, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)), new AttackMove(Moves.AURORA_BEAM, "Aurora Beam", Type.ICE, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Attack.", 10, 1, new StatChangeAttr(BattleStat.ATK, -1)), new AttackMove(Moves.HYPER_BEAM, "Hyper Beam", Type.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, 163, "User must recharge next turn.", -1, 1), @@ -924,7 +1015,8 @@ export const allMoves = [ new StatusMove(Moves.POISON_POWDER, "Poison Powder", Type.POISON, 75, 35, -1, "Poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.POISON)), new StatusMove(Moves.STUN_SPORE, "Stun Spore", Type.GRASS, 75, 30, -1, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), new StatusMove(Moves.SLEEP_POWDER, "Sleep Powder", Type.GRASS, 75, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), - new AttackMove(Moves.PETAL_DANCE, "Petal Dance", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1), + new AttackMove(Moves.PETAL_DANCE, "Petal Dance", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1, + new FrenzyAttr(), frenzyMissFunc, new ConfuseAttr(true)), // TODO: Update to still confuse if last hit misses new StatusMove(Moves.STRING_SHOT, "String Shot", Type.BUG, 95, 40, -1, "Sharply lowers opponent's Speed.", -1, 1, new StatChangeAttr(BattleStat.SPD, -2)), new AttackMove(Moves.DRAGON_RAGE, "Dragon Rage", Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, "Always inflicts 40 HP.", -1, 1), new AttackMove(Moves.FIRE_SPIN, "Fire Spin", Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, 24, "Traps opponent, damaging them for 4-5 turns.", 100, 1), @@ -939,7 +1031,7 @@ export const allMoves = [ new AttackMove(Moves.DIG, "Dig", Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, 55, "Digs underground on first turn, attacks on second. Can also escape from caves.", -1, 1, new ChargeAttr(ChargeAnim.DIG_CHARGING, 'dug a hole!', BattleTagType.UNDERGROUND)), new StatusMove(Moves.TOXIC, "Toxic", Type.POISON, 90, 10, -1, "Badly poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.TOXIC)), - new AttackMove(Moves.CONFUSION, "Confusion", Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, -1, "May confuse opponent.", 10, 1), // TODO + new AttackMove(Moves.CONFUSION, "Confusion", Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, -1, "May confuse opponent.", 10, 1, new ConfuseAttr()), new AttackMove(Moves.PSYCHIC, "Psychic", Type.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 120, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)), new StatusMove(Moves.HYPNOSIS, "Hypnosis", Type.PSYCHIC, 60, 20, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new StatusMove(Moves.MEDITATE, "Meditate", Type.PSYCHIC, -1, 40, -1, "Raises user's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, 1, true)), @@ -955,7 +1047,7 @@ export const allMoves = [ new StatusMove(Moves.HARDEN, "Harden", Type.NORMAL, -1, 30, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)), new StatusMove(Moves.MINIMIZE, "Minimize", Type.NORMAL, -1, 10, -1, "Sharply raises user's Evasiveness.", -1, 1, new StatChangeAttr(BattleStat.EVA, 1, true)), new StatusMove(Moves.SMOKESCREEN, "Smokescreen", Type.NORMAL, 100, 20, -1, "Lowers opponent's Accuracy.", -1, 1, new StatChangeAttr(BattleStat.ACC, -1)), - new StatusMove(Moves.CONFUSE_RAY, "Confuse Ray", Type.GHOST, 100, 10, 17, "Confuses opponent.", -1, 1), // TODO + new StatusMove(Moves.CONFUSE_RAY, "Confuse Ray", Type.GHOST, 100, 10, 17, "Confuses opponent.", -1, 1, new ConfuseAttr()), new StatusMove(Moves.WITHDRAW, "Withdraw", Type.WATER, -1, 40, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)), new StatusMove(Moves.DEFENSE_CURL, "Defense Curl", Type.NORMAL, -1, 40, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)), new StatusMove(Moves.BARRIER, "Barrier", Type.PSYCHIC, -1, 20, -1, "Sharply raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 2, true)), @@ -995,7 +1087,7 @@ export const allMoves = [ new ChargeAttr(ChargeAnim.SKY_ATTACK_CHARGING, 'is glowing!'), new HighCritAttr(), new FlinchAttr()), new StatusMove(Moves.TRANSFORM, "Transform", Type.NORMAL, -1, 10, -1, "User takes on the form and attacks of the opponent.", -1, 1), new AttackMove(Moves.BUBBLE, "Bubble", Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)), - new AttackMove(Moves.DIZZY_PUNCH, "Dizzy Punch", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, "May confuse opponent.", 20, 1), // TODO + new AttackMove(Moves.DIZZY_PUNCH, "Dizzy Punch", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, "May confuse opponent.", 20, 1, new ConfuseAttr()), new StatusMove(Moves.SPORE, "Spore", Type.GRASS, 100, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new StatusMove(Moves.FLASH, "Flash", Type.NORMAL, 100, 20, -1, "Lowers opponent's Accuracy.", -1, 1, new StatChangeAttr(BattleStat.ACC, -1)), new AttackMove(Moves.PSYWAVE, "Psywave", Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, "Inflicts damage 50-150% of user's level.", -1, 1), @@ -1036,7 +1128,7 @@ export const allMoves = [ new AttackMove(Moves.MACH_PUNCH, "Mach Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, "User attacks first.", -1, 2), new StatusMove(Moves.SCARY_FACE, "Scary Face", Type.NORMAL, 100, 10, 6, "Sharply lowers opponent's Speed.", -1, 2, new StatChangeAttr(BattleStat.SPD, -2)), new AttackMove(Moves.FEINT_ATTACK, "Feint Attack", Type.DARK, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 2), - new StatusMove(Moves.SWEET_KISS, "Sweet Kiss", Type.FAIRY, 75, 10, -1, "Confuses opponent.", -1, 2), // TODO + new StatusMove(Moves.SWEET_KISS, "Sweet Kiss", Type.FAIRY, 75, 10, -1, "Confuses opponent.", -1, 2, new ConfuseAttr()), new StatusMove(Moves.BELLY_DRUM, "Belly Drum", Type.NORMAL, -1, 10, -1, "User loses 50% of its max HP, but Attack raises to maximum.", -1, 2), new AttackMove(Moves.SLUDGE_BOMB, "Sludge Bomb", Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 148, "May poison opponent.", 30, 2, new StatusEffectAttr(StatusEffect.POISON)), new AttackMove(Moves.MUD_SLAP, "Mud-Slap", Type.GROUND, MoveCategory.SPECIAL, 20, 100, 10, 5, "Lowers opponent's Accuracy.", 100, 2, new StatChangeAttr(BattleStat.ACC, -1)), @@ -1050,14 +1142,15 @@ export const allMoves = [ new StatusMove(Moves.DETECT, "Detect", Type.FIGHTING, -1, 5, -1, "Protects the user, but may fail if used consecutively.", -1, 2), new AttackMove(Moves.BONE_RUSH, "Bone Rush", Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, "Hits 2-5 times in one turn.", -1, 2, new MultiHitAttr()), new StatusMove(Moves.LOCK_ON, "Lock-On", Type.NORMAL, -1, 5, -1, "User's next attack is guaranteed to hit.", -1, 2), - new AttackMove(Moves.OUTRAGE, "Outrage", Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, 156, "User attacks for 2-3 turns but then becomes confused.", -1, 2), + new AttackMove(Moves.OUTRAGE, "Outrage", Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, 156, "User attacks for 2-3 turns but then becomes confused.", -1, 2, + new FrenzyAttr(), frenzyMissFunc, new ConfuseAttr(true)), // TODO: Update to still confuse if last hit misses new StatusMove(Moves.SANDSTORM, "Sandstorm", Type.ROCK, -1, 10, 51, "Creates a sandstorm for 5 turns.", -1, 2), new AttackMove(Moves.GIGA_DRAIN, "Giga Drain", Type.GRASS, MoveCategory.SPECIAL, 75, 100, 10, 111, "User recovers half the HP inflicted on opponent.", -1, 2), new StatusMove(Moves.ENDURE, "Endure", Type.NORMAL, -1, 10, 47, "Always left with at least 1 HP, but may fail if used consecutively.", -1, 2), new StatusMove(Moves.CHARM, "Charm", Type.FAIRY, 100, 20, 2, "Sharply lowers opponent's Attack.", -1, 2, new StatChangeAttr(BattleStat.ATK, -2)), new AttackMove(Moves.ROLLOUT, "Rollout", Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, "Doubles in power each turn for 5 turns.", -1, 2), new AttackMove(Moves.FALSE_SWIPE, "False Swipe", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, 57, "Always leaves opponent with at least 1 HP.", -1, 2), - new StatusMove(Moves.SWAGGER, "Swagger", Type.NORMAL, 85, 15, -1, "Confuses opponent, but sharply raises its Attack.", -1, 2, new StatChangeAttr(BattleStat.ATK, 2)), // todo + new StatusMove(Moves.SWAGGER, "Swagger", Type.NORMAL, 85, 15, -1, "Confuses opponent, but sharply raises its Attack.", -1, 2, new StatChangeAttr(BattleStat.ATK, 2), new ConfuseAttr()), new StatusMove(Moves.MILK_DRINK, "Milk Drink", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 2), new AttackMove(Moves.SPARK, "Spark", Type.ELECTRIC, MoveCategory.PHYSICAL, 65, 100, 20, -1, "May paralyze opponent.", 30, 2, new StatusEffectAttr(StatusEffect.PARALYSIS)), new AttackMove(Moves.FURY_CUTTER, "Fury Cutter", Type.BUG, MoveCategory.PHYSICAL, 40, 95, 20, -1, "Power increases each turn.", -1, 2), @@ -1073,7 +1166,7 @@ export const allMoves = [ new StatusMove(Moves.PAIN_SPLIT, "Pain Split", Type.NORMAL, -1, 20, -1, "The user's and opponent's HP becomes the average of both.", -1, 2), new AttackMove(Moves.SACRED_FIRE, "Sacred Fire", Type.FIRE, MoveCategory.PHYSICAL, 100, 95, 5, -1, "May burn opponent.", 50, 2, new StatusEffectAttr(StatusEffect.BURN)), new AttackMove(Moves.MAGNITUDE, "Magnitude", Type.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, -1, "Hits with random power.", -1, 2), - new AttackMove(Moves.DYNAMIC_PUNCH, "Dynamic Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, -1, "Confuses opponent.", 100, 2), // TODO + new AttackMove(Moves.DYNAMIC_PUNCH, "Dynamic Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, -1, "Confuses opponent.", 100, 2, new ConfuseAttr()), new AttackMove(Moves.MEGAHORN, "Megahorn", Type.BUG, MoveCategory.PHYSICAL, 120, 85, 10, -1, "", -1, 2), new AttackMove(Moves.DRAGON_BREATH, "Dragon Breath", Type.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, -1, "May paralyze opponent.", 30, 2, new StatusEffectAttr(StatusEffect.PARALYSIS)), new StatusMove(Moves.BATON_PASS, "Baton Pass", Type.NORMAL, -1, 40, 132, "User switches out and gives stat changes to the incoming Pokémon.", -1, 2), @@ -1105,14 +1198,14 @@ export const allMoves = [ new AttackMove(Moves.WHIRLPOOL, "Whirlpool", Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 2), new AttackMove(Moves.BEAT_UP, "Beat Up", Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, "Each Pokémon in user's party attacks.", -1, 2), new AttackMove(Moves.FAKE_OUT, "Fake Out", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 10, -1, "User attacks first, foe flinches. Only usable on first turn.", 100, 3, new FlinchAttr()), // TODO - new AttackMove(Moves.UPROAR, "Uproar", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, "User attacks for 3 turns and prevents sleep.", -1, 3), + new AttackMove(Moves.UPROAR, "Uproar", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, "User attacks for 3 turns and prevents sleep.", -1, 3), // TODO new StatusMove(Moves.STOCKPILE, "Stockpile", Type.NORMAL, -1, 20, -1, "Stores energy for use with Spit Up and Swallow.", -1, 3), new AttackMove(Moves.SPIT_UP, "Spit Up", Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, -1, "Power depends on how many times the user performed Stockpile.", -1, 3), new StatusMove(Moves.SWALLOW, "Swallow", Type.NORMAL, -1, 10, -1, "The more times the user has performed Stockpile, the more HP is recovered.", -1, 3), new AttackMove(Moves.HEAT_WAVE, "Heat Wave", Type.FIRE, MoveCategory.SPECIAL, 95, 90, 10, 118, "May burn opponent.", 10, 3, new StatusEffectAttr(StatusEffect.BURN)), new StatusMove(Moves.HAIL, "Hail", Type.ICE, -1, 10, -1, "Non-Ice types are damaged for 5 turns.", -1, 3), new StatusMove(Moves.TORMENT, "Torment", Type.DARK, 100, 15, -1, "Opponent cannot use the same move in a row.", -1, 3), - new StatusMove(Moves.FLATTER, "Flatter", Type.DARK, 100, 15, -1, "Confuses opponent, but raises its Special Attack.", -1, 3, new StatChangeAttr(BattleStat.SPATK, 1)), // TODO + new StatusMove(Moves.FLATTER, "Flatter", Type.DARK, 100, 15, -1, "Confuses opponent, but raises its Special Attack.", -1, 3, new StatChangeAttr(BattleStat.SPATK, 1), new ConfuseAttr()), new StatusMove(Moves.WILL_O_WISP, "Will-O-Wisp", Type.FIRE, 85, 15, 107, "Burns opponent.", -1, 3, new StatusEffectAttr(StatusEffect.BURN)), new StatusMove(Moves.MEMENTO, "Memento", Type.DARK, 100, 10, -1, "User faints, sharply lowers opponent's Attack and Special Attack.", -1, 3, new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], -2)), // TODO @@ -1153,7 +1246,7 @@ export const allMoves = [ new AttackMove(Moves.LUSTER_PURGE, "Luster Purge", Type.PSYCHIC, MoveCategory.SPECIAL, 70, 100, 5, -1, "May lower opponent's Special Defense.", 50, 3, new StatChangeAttr(BattleStat.SPDEF, -1)), new AttackMove(Moves.MIST_BALL, "Mist Ball", Type.PSYCHIC, MoveCategory.SPECIAL, 70, 100, 5, -1, "May lower opponent's Special Attack.", 50, 3, new StatChangeAttr(BattleStat.SPATK, -1)), new StatusMove(Moves.FEATHER_DANCE, "Feather Dance", Type.FLYING, 100, 15, -1, "Sharply lowers opponent's Attack.", -1, 3, new StatChangeAttr(BattleStat.ATK, -2)), - new StatusMove(Moves.TEETER_DANCE, "Teeter Dance", Type.NORMAL, 100, 20, -1, "Confuses all Pokémon.", -1, 3), // TODO + new StatusMove(Moves.TEETER_DANCE, "Teeter Dance", Type.NORMAL, 100, 20, -1, "Confuses all Pokémon.", -1, 3, new ConfuseAttr(true), new ConfuseAttr()), new AttackMove(Moves.BLAZE_KICK, "Blaze Kick", Type.FIRE, MoveCategory.PHYSICAL, 85, 90, 10, -1, "High critical hit ratio. May burn opponent.", 10, 3, new HighCritAttr(), new StatusEffectAttr(StatusEffect.BURN)), new StatusMove(Moves.MUD_SPORT, "Mud Sport", Type.GROUND, -1, 15, -1, "Weakens the power of Electric-type moves.", -1, 3), new AttackMove(Moves.ICE_BALL, "Ice Ball", Type.ICE, MoveCategory.PHYSICAL, 30, 90, 20, -1, "Doubles in power each turn for 5 turns.", -1, 3), @@ -1182,7 +1275,7 @@ export const allMoves = [ new StatusMove(Moves.COSMIC_POWER, "Cosmic Power", Type.PSYCHIC, -1, 20, -1, "Raises user's Defense and Special Defense.", -1, 3, new StatChangeAttr([ BattleStat.DEF, BattleStat.SPDEF ], 1, true)), new AttackMove(Moves.WATER_SPOUT, "Water Spout", Type.WATER, MoveCategory.SPECIAL, 150, 100, 5, -1, "The higher the user's HP, the higher the damage caused.", -1, 3), - new AttackMove(Moves.SIGNAL_BEAM, "Signal Beam", Type.BUG, MoveCategory.SPECIAL, 75, 100, 15, -1, "May confuse opponent.", 10, 3), // TODO + new AttackMove(Moves.SIGNAL_BEAM, "Signal Beam", Type.BUG, MoveCategory.SPECIAL, 75, 100, 15, -1, "May confuse opponent.", 10, 3, new ConfuseAttr()), new AttackMove(Moves.SHADOW_PUNCH, "Shadow Punch", Type.GHOST, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3), new AttackMove(Moves.EXTRASENSORY, "Extrasensory", Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, -1, "May cause flinching.", 10, 3, new FlinchAttr()), new AttackMove(Moves.SKY_UPPERCUT, "Sky Uppercut", Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, "Hits the opponent, even during Fly.", -1, 3, new HitsTagAttr(BattleTagType.FLYING)), @@ -1215,7 +1308,7 @@ export const allMoves = [ new StatChangeAttr([ BattleStat.ATK, BattleStat.SPD ], 1, true)), new AttackMove(Moves.ROCK_BLAST, "Rock Blast", Type.ROCK, MoveCategory.PHYSICAL, 25, 90, 10, 76, "Hits 2-5 times in one turn.", -1, 3, new MultiHitAttr()), new AttackMove(Moves.SHOCK_WAVE, "Shock Wave", Type.ELECTRIC, MoveCategory.SPECIAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3), - new AttackMove(Moves.WATER_PULSE, "Water Pulse", Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 11, "May confuse opponent.", 20, 3), + new AttackMove(Moves.WATER_PULSE, "Water Pulse", Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 11, "May confuse opponent.", 20, 3, new ConfuseAttr()), new AttackMove(Moves.DOOM_DESIRE, "Doom Desire", Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, "Damage occurs 2 turns later.", -1, 3, new ChargeAttr(ChargeAnim.DOOM_DESIRE_CHARGING, 'chose\nDOOM DESIRE as its destiny!')), new AttackMove(Moves.PSYCHO_BOOST, "Psycho Boost", Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, "Sharply lowers user's Special Attack.", 100, 3, new StatChangeAttr(BattleStat.SPATK, -2, true)), @@ -1231,7 +1324,7 @@ export const allMoves = [ new AttackMove(Moves.FEINT, "Feint", Type.NORMAL, MoveCategory.PHYSICAL, 30, 100, 10, -1, "Only hits if opponent uses Protect or Detect in the same turn.", -1, 4), new AttackMove(Moves.PLUCK, "Pluck", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 20, -1, "If the opponent is holding a berry, its effect is stolen by user.", -1, 4), new StatusMove(Moves.TAILWIND, "Tailwind", Type.FLYING, -1, 15, 113, "Doubles Speed for 4 turns.", -1, 4), - new StatusMove(Moves.ACUPRESSURE, "Acupressure", Type.NORMAL, -1, 30, -1, "Sharply raises a random stat.", -1, 4, new StatChangeAttr(BattleStat.RAND, 2, true)), // TODO + new StatusMove(Moves.ACUPRESSURE, "Acupressure", Type.NORMAL, -1, 30, -1, "Sharply raises a random stat.", -1, 4, new StatChangeAttr(BattleStat.RAND, 2, true)), new AttackMove(Moves.METAL_BURST, "Metal Burst", Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, "Deals damage equal to 1.5x opponent's attack.", -1, 4), new AttackMove(Moves.U_TURN, "U-turn", Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, 60, "User switches out immediately after attacking.", -1, 4), new AttackMove(Moves.CLOSE_COMBAT, "Close Combat", Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, 167, "Lowers user's Defense and Special Defense.", 100, 4, @@ -1296,7 +1389,7 @@ export const allMoves = [ new AttackMove(Moves.ZEN_HEADBUTT, "Zen Headbutt", Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 90, 15, 59, "May cause flinching.", 20, 4, new FlinchAttr()), new AttackMove(Moves.MIRROR_SHOT, "Mirror Shot", Type.STEEL, MoveCategory.SPECIAL, 65, 85, 10, -1, "May lower opponent's Accuracy.", 30, 4, new StatChangeAttr(BattleStat.ACC, -1)), new AttackMove(Moves.FLASH_CANNON, "Flash Cannon", Type.STEEL, MoveCategory.SPECIAL, 80, 100, 10, 93, "May lower opponent's Special Defense.", 10, 4, new StatChangeAttr(BattleStat.SPDEF, -1)), - new AttackMove(Moves.ROCK_CLIMB, "Rock Climb", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, "May confuse opponent.", 20, 4), // TODO + new AttackMove(Moves.ROCK_CLIMB, "Rock Climb", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, "May confuse opponent.", 20, 4, new ConfuseAttr()), new StatusMove(Moves.DEFOG, "Defog", Type.FLYING, -1, 15, -1, "Lowers opponent's Evasiveness and clears fog.", -1, 4, new StatChangeAttr(BattleStat.EVA, -1)), // TODO new StatusMove(Moves.TRICK_ROOM, "Trick Room", Type.PSYCHIC, -1, 5, 161, "Slower Pokémon move first in the turn for 5 turns.", -1, 4), new AttackMove(Moves.DRACO_METEOR, "Draco Meteor", Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, 169, "Sharply lowers user's Special Attack.", 100, 4, new StatChangeAttr(BattleStat.SPATK, -2, true)), @@ -1314,7 +1407,7 @@ export const allMoves = [ new StatusMove(Moves.CAPTIVATE, "Captivate", Type.NORMAL, 100, 20, -1, "Sharply lowers opponent's Special Attack if opposite gender.", -1, 4), // TODO XX new StatusMove(Moves.STEALTH_ROCK, "Stealth Rock", Type.ROCK, -1, 20, 116, "Damages opponent switching into battle.", -1, 4), new AttackMove(Moves.GRASS_KNOT, "Grass Knot", Type.GRASS, MoveCategory.SPECIAL, -1, 100, 20, 81, "The heavier the opponent, the stronger the attack.", -1, 4), - new AttackMove(Moves.CHATTER, "Chatter", Type.FLYING, MoveCategory.SPECIAL, 65, 100, 20, -1, "Confuses opponent.", 100, 4), + new AttackMove(Moves.CHATTER, "Chatter", Type.FLYING, MoveCategory.SPECIAL, 65, 100, 20, -1, "Confuses opponent.", 100, 4, new ConfuseAttr()), new AttackMove(Moves.JUDGMENT, "Judgment", Type.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, "Type depends on the Arceus Plate being held.", -1, 4), new AttackMove(Moves.BUG_BITE, "Bug Bite", Type.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, "Receives the effect from the opponent's held berry.", -1, 4), new AttackMove(Moves.CHARGE_BEAM, "Charge Beam", Type.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 23, "May raise user's Special Attack.", 70, 4, new StatChangeAttr(BattleStat.SPATK, 1, true)), @@ -1418,7 +1511,7 @@ export const allMoves = [ new AttackMove(Moves.NIGHT_DAZE, "Night Daze", Type.DARK, MoveCategory.SPECIAL, 85, 95, 10, -1, "May lower opponent's Accuracy.", 40, 5, new StatChangeAttr(BattleStat.ACC, -1)), new AttackMove(Moves.PSYSTRIKE, "Psystrike", Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 10, -1, "Inflicts damage based on the target's Defense, not Special Defense.", -1, 5), new AttackMove(Moves.TAIL_SLAP, "Tail Slap", Type.NORMAL, MoveCategory.PHYSICAL, 25, 85, 10, -1, "Hits 2-5 times in one turn.", -1, 5, new MultiHitAttr()), - new AttackMove(Moves.HURRICANE, "Hurricane", Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 160, "May confuse opponent.", 30, 5), + new AttackMove(Moves.HURRICANE, "Hurricane", Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 160, "May confuse opponent.", 30, 5, new ConfuseAttr()), new AttackMove(Moves.HEAD_CHARGE, "Head Charge", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, "User receives recoil damage.", -1, 5), new AttackMove(Moves.GEAR_GRIND, "Gear Grind", Type.STEEL, MoveCategory.PHYSICAL, 50, 85, 15, -1, "Hits twice in one turn.", -1, 5, new MultiHitAttr(MultiHitType._2)), new AttackMove(Moves.SEARING_SHOT, "Searing Shot", Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, "May burn opponent.", 30, 5, new StatusEffectAttr(StatusEffect.BURN)), diff --git a/src/pokemon.ts b/src/pokemon.ts index 2cfe24aa6..b055eea12 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -1,7 +1,7 @@ import Phaser from 'phaser'; import BattleScene from './battle-scene'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './battle-info'; -import { default as Move, allMoves, MoveCategory, Moves, StatChangeAttr, applyMoveAttrs, HighCritAttr, HitsTagAttr } from './move'; +import { default as Move, allMoves, MoveCategory, Moves, StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs } from './move'; import { pokemonLevelMoves } from './pokemon-level-moves'; import { default as PokemonSpecies, getPokemonSpecies } from './pokemon-species'; import * as Utils from './utils'; @@ -15,9 +15,9 @@ import { initMoveAnim, loadMoveAnimAssets } from './battle-anims'; import { Status, StatusEffect } from './status-effect'; import { tmSpecies } from './tms'; import { pokemonEvolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './pokemon-evolutions'; -import { MessagePhase } from './battle-phases'; +import { DamagePhase, MessagePhase } from './battle-phases'; import { BattleStat } from './battle-stat'; -import { BattleTag, BattleTagLapseType, BattleTagType } from './battle-tag'; +import { BattleTag, BattleTagLapseType, BattleTagType, getBattleTag } from './battle-tag'; import { Species } from './species'; export default abstract class Pokemon extends Phaser.GameObjects.Container { @@ -411,135 +411,128 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); } - apply(source: Pokemon, battlerMove: PokemonMove): Promise { - return new Promise(resolve => { - let result: MoveResult = MoveResult.STATUS; - let success = false; - const move = battlerMove.getMove(); - const moveCategory = move.category; - let damage = 0; - switch (moveCategory) { - case MoveCategory.PHYSICAL: - case MoveCategory.SPECIAL: - const isPhysical = moveCategory === MoveCategory.PHYSICAL; - const power = new Utils.NumberHolder(move.power); - this.scene.applyModifiers(AttackTypeBoosterModifier, source, power); - const critChance = new Utils.IntegerHolder(16); - applyMoveAttrs(HighCritAttr, this.scene, source, this, move, critChance); - const isCritical = Utils.randInt(critChance.value) === 0; - const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK); - const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF); - const stabMultiplier = source.species.type1 === move.type || (source.species.type2 > -1 && source.species.type2 === move.type) ? 1.5 : 1; - const typeMultiplier = getTypeDamageMultiplier(move.type, this.species.type1) * (this.species.type2 > -1 ? getTypeDamageMultiplier(move.type, this.species.type2) : 1); - const criticalMultiplier = isCritical ? 2 : 1; - damage = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier; - if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) - damage = Math.floor(damage / 2); - move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { - if (this.getTag(hta.tagType)) { - console.log('ye'); - damage *= 2; - } - }); - console.log('damage', damage, move.name, move.power, sourceAtk, targetDef); - if (damage) { - this.hp = Math.max(this.hp - damage, 0); - source.turnData.damageDealt += damage; - if (isCritical) - this.scene.unshiftPhase(new MessagePhase(this.scene, 'A critical hit!')); - } - if (typeMultiplier >= 2) - result = MoveResult.SUPER_EFFECTIVE; - else if (typeMultiplier >= 1) - result = MoveResult.EFFECTIVE; - else if (typeMultiplier > 0) - result = MoveResult.NOT_VERY_EFFECTIVE; - else - result = MoveResult.NO_EFFECT; + apply(source: Pokemon, battlerMove: PokemonMove): MoveResult { + let result: MoveResult = MoveResult.STATUS; + let success = false; + const move = battlerMove.getMove(); + const moveCategory = move.category; + let damage = 0; + switch (moveCategory) { + case MoveCategory.PHYSICAL: + case MoveCategory.SPECIAL: + const isPhysical = moveCategory === MoveCategory.PHYSICAL; + const power = new Utils.NumberHolder(move.power); + this.scene.applyModifiers(AttackTypeBoosterModifier, source, power); + const critChance = new Utils.IntegerHolder(16); + applyMoveAttrs(HighCritAttr, this.scene, source, this, move, critChance); + const isCritical = Utils.randInt(critChance.value) === 0; + const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK); + const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF); + const stabMultiplier = source.species.type1 === move.type || (source.species.type2 > -1 && source.species.type2 === move.type) ? 1.5 : 1; + const typeMultiplier = getTypeDamageMultiplier(move.type, this.species.type1) * (this.species.type2 > -1 ? getTypeDamageMultiplier(move.type, this.species.type2) : 1); + const criticalMultiplier = isCritical ? 2 : 1; + damage = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier; + if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) + damage = Math.floor(damage / 2); + move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + if (this.getTag(hta.tagType)) + damage *= 2; + }); + console.log('damage', damage, move.name, move.power, sourceAtk, targetDef); + + if (typeMultiplier >= 2) + result = MoveResult.SUPER_EFFECTIVE; + else if (typeMultiplier >= 1) + result = MoveResult.EFFECTIVE; + else if (typeMultiplier > 0) + result = MoveResult.NOT_VERY_EFFECTIVE; + else + result = MoveResult.NO_EFFECT; - switch (result) { - case MoveResult.EFFECTIVE: - this.scene.sound.play('hit'); - success = true; - break; - case MoveResult.SUPER_EFFECTIVE: - this.scene.sound.play('hit_strong'); - this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s super effective!')); - success = true; - break; - case MoveResult.NOT_VERY_EFFECTIVE: - this.scene.sound.play('hit_weak'); - this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s not very effective!')) - success = true; - break; - case MoveResult.NO_EFFECT: - this.scene.unshiftPhase(new MessagePhase(this.scene, `It doesn\'t affect ${this.name}!`)) - success = true; - break; - } - break; - case MoveCategory.STATUS: - result = MoveResult.STATUS; - success = true; - break; - } + - if (success) { - if (result <= MoveResult.NOT_VERY_EFFECTIVE) { - const flashTimer = this.scene.time.addEvent({ - delay: 100, - repeat: 5, - startAt: 200, - callback: () => { - this.getSprite().setVisible(flashTimer.repeatCount % 2 === 0); - if (!flashTimer.repeatCount) { - this.battleInfo.updateInfo(this).then(() => resolve(result)); - } - } - }); - } else { - this.battleInfo.updateInfo(this).then(() => resolve(result)); + if (damage) { + this.hp = Math.max(this.hp - damage, 0); + source.turnData.damageDealt += damage; + this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), result as DamageResult)) + if (isCritical) + this.scene.unshiftPhase(new MessagePhase(this.scene, 'A critical hit!')); } - } else - resolve(result); - }); + + switch (result) { + case MoveResult.EFFECTIVE: + success = true; + break; + case MoveResult.SUPER_EFFECTIVE: + this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s super effective!')); + success = true; + break; + case MoveResult.NOT_VERY_EFFECTIVE: + this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s not very effective!')) + success = true; + break; + case MoveResult.NO_EFFECT: + this.scene.unshiftPhase(new MessagePhase(this.scene, `It doesn\'t affect ${this.name}!`)) + success = true; + break; + } + break; + case MoveCategory.STATUS: + result = MoveResult.STATUS; + success = true; + break; + } + + return result; } - addTag(tagType: BattleTagType, lapseType: BattleTagLapseType, turnCount?: integer): boolean { - if (this.getTag(tagType)) + addTag(tagType: BattleTagType, turnCount?: integer): boolean { + const existingTag = this.getTag(tagType); + if (existingTag) { + existingTag.onOverlap(this); return false; + } - const newTag = new BattleTag(tagType, lapseType || BattleTagLapseType.FAINT, turnCount || 1); + const newTag = getBattleTag(tagType, turnCount || 1); this.summonData.tags.push(newTag); - if (newTag.isHidden()) - this.setVisible(false); + newTag.onAdd(this); } - getTag(tagFilter: BattleTagType | ((tag: BattleTag) => boolean)): BattleTag { - return typeof(tagFilter) === 'number' - ? this.summonData.tags.find(t => t.tagType === tagFilter) - : this.summonData.tags.find(t => tagFilter(t)); + getTag(tagType: BattleTagType | { new(...args: any[]): BattleTag }): BattleTag { + return typeof(tagType) === 'number' + ? this.summonData.tags.find(t => t.tagType === tagType) + : this.summonData.tags.find(t => t instanceof tagType); } - getTags(tagFilter: BattleTagType | ((tag: BattleTag) => boolean)): BattleTag[] { - return typeof(tagFilter) === 'number' - ? this.summonData.tags.filter(t => t.tagType === tagFilter) - : this.summonData.tags.filter(t => tagFilter(t)); + findTag(tagFilter: ((tag: BattleTag) => boolean)) { + return this.summonData.tags.find(t => tagFilter(t)); } - lapseTags(lapseType: BattleTagLapseType) { + getTags(tagType: BattleTagType | { new(...args: any[]): BattleTag }): BattleTag[] { + return typeof(tagType) === 'number' + ? this.summonData.tags.filter(t => t.tagType === tagType) + : this.summonData.tags.filter(t => t instanceof tagType); + } + + findTags(tagFilter: ((tag: BattleTag) => boolean)) { + return this.summonData.tags.filter(t => tagFilter(t)); + } + + lapseTag(tagType: BattleTagType): void { const tags = this.summonData.tags; - tags.filter(t => lapseType === BattleTagLapseType.FAINT || ((t.lapseType === lapseType) && !(--t.turnCount))).forEach(t => tags.splice(tags.indexOf(t), 1)); - const visible = !this.getTag(t => t.isHidden()); - if (visible && !this.visible) { - // Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible - this.scene.tweens.addCounter({ - duration: 2, - useFrames: true, - onComplete: () => this.setVisible(true) - }); - } else - this.setVisible(visible); + const tag = tags.find(t => t.tagType === tagType); + if (tag && !(tag.lapse(this))) { + tag.onRemove(this); + tags.splice(tags.indexOf(tag), 1); + } + } + + lapseTags(lapseType: BattleTagLapseType): void { + const tags = this.summonData.tags; + tags.filter(t => lapseType === BattleTagLapseType.FAINT || ((t.lapseType === lapseType) && !(t.lapse(this)))).forEach(t => { + t.onRemove(this); + tags.splice(tags.indexOf(t), 1); + }); } getLastXMoves(turnCount?: integer): TurnMove[] { @@ -914,8 +907,6 @@ export class PokemonSummonData { public moveHistory: TurnMove[] = []; public moveQueue: QueuedMove[] = []; public tags: BattleTag[] = []; - public charging: boolean; - public confusionTurns: integer; } export class PokemonBattleSummonData { @@ -947,6 +938,8 @@ export enum MoveResult { OTHER }; +export type DamageResult = MoveResult.EFFECTIVE | MoveResult.SUPER_EFFECTIVE | MoveResult.NOT_VERY_EFFECTIVE; + export class PokemonMove { public moveId: Moves; public ppUsed: integer;