diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 88e558dea..08c809467 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -1,7 +1,7 @@ import BattleScene from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult } from "./pokemon"; import * as Utils from './utils'; -import { allMoves, applyMoveAttrs, ChargeAttr, ConditionalFailMoveAttr, HitsTagAttr, MissEffectAttr, MoveCategory, MoveEffectAttr, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr } from "./move"; +import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, ConditionalMoveAttr, HitsTagAttr, MissEffectAttr, MoveCategory, MoveEffectAttr, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr } from "./move"; import { Mode } from './ui/ui'; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./pokemon-stat"; @@ -9,7 +9,7 @@ import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, HitHealMod import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./pokeball"; import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; -import { StatusEffect, getStatusEffectActivationText, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./status-effect"; +import { Status, StatusEffect, getStatusEffectActivationText, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./status-effect"; import { SummaryUiMode } from "./ui/summary-ui-handler"; import EvolutionSceneHandler from "./ui/evolution-scene-handler"; import { EvolutionPhase } from "./evolution-phase"; @@ -585,13 +585,16 @@ export class CommonAnimPhase extends PokemonPhase { export abstract class MovePhase extends BattlePhase { protected pokemon: Pokemon; protected move: PokemonMove; + protected followUp: boolean; + protected hasFollowUp: boolean; protected cancelled: boolean; - constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove) { + constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove, followUp?: boolean) { super(scene); this.pokemon = pokemon; this.move = move; + this.followUp = !!followUp; this.cancelled = false; } @@ -608,19 +611,23 @@ export abstract class MovePhase extends BattlePhase { start() { super.start(); - this.pokemon.lapseTags(BattleTagLapseType.MOVE); + const target = this.pokemon.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon(); + + if (!this.followUp) + this.pokemon.lapseTags(BattleTagLapseType.MOVE); const doMove = () => { if (this.cancelled) { this.end(); return; } + this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500)); - if (this.pokemon.summonData.moveQueue.length && !this.pokemon.summonData.moveQueue.shift().ignorePP) + if (!this.pokemon.summonData.moveQueue.length || !this.pokemon.summonData.moveQueue.shift().ignorePP) this.move.ppUsed++; - + const failed = new Utils.BooleanHolder(false); - applyMoveAttrs(ConditionalFailMoveAttr, this.pokemon, this.pokemon.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon(), this.move.getMove(), failed); + applyMoveAttrs(ConditionalMoveAttr, this.pokemon, target, this.move.getMove(), failed); if (failed.value) this.scene.unshiftPhase(new MessagePhase(this.scene, 'But it failed!')); else @@ -634,10 +641,11 @@ export abstract class MovePhase extends BattlePhase { return; } - if (this.pokemon.status && !this.pokemon.status.isPostTurn()) { + if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { this.pokemon.status.incrementTurn(); let activated = false; let healed = false; + switch (this.pokemon.status.effect) { case StatusEffect.PARALYSIS: if (Utils.randInt(4) === 0) { @@ -646,8 +654,9 @@ export abstract class MovePhase extends BattlePhase { } break; case StatusEffect.SLEEP: + applyMoveAttrs(BypassSleepAttr, this.pokemon, target, this.move.getMove()); healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; - activated = !healed; + activated = !healed && !this.pokemon.getTag(BattleTagType.BYPASS_SLEEP); this.cancelled = activated; break; case StatusEffect.FREEZE: @@ -673,11 +682,18 @@ export abstract class MovePhase extends BattlePhase { } else doMove(); } + + end() { + if (!this.followUp) + this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.isPlayer())); + + super.end(); + } } export class PlayerMovePhase extends MovePhase { - constructor(scene: BattleScene, pokemon: PlayerPokemon, move: PokemonMove) { - super(scene, pokemon, move); + constructor(scene: BattleScene, pokemon: PlayerPokemon, move: PokemonMove, followUp?: boolean) { + super(scene, pokemon, move, followUp); } getEffectPhase(): MoveEffectPhase { @@ -686,8 +702,8 @@ export class PlayerMovePhase extends MovePhase { } export class EnemyMovePhase extends MovePhase { - constructor(scene: BattleScene, pokemon: EnemyPokemon, move: PokemonMove) { - super(scene, pokemon, move); + constructor(scene: BattleScene, pokemon: EnemyPokemon, move: PokemonMove, followUp?: boolean) { + super(scene, pokemon, move, followUp); } getEffectPhase(): MoveEffectPhase { @@ -742,14 +758,6 @@ abstract class MoveEffectPhase extends PokemonPhase { 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, 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) @@ -844,6 +852,20 @@ export class EnemyMoveEffectPhase extends MoveEffectPhase { } } +export class MoveEndPhase extends PokemonPhase { + constructor(scene: BattleScene, player: boolean) { + super(scene, player); + } + + start() { + super.start(); + + this.getPokemon().lapseTags(BattleTagLapseType.AFTER_MOVE); + + this.end(); + } +} + export class MoveAnimTestPhase extends BattlePhase { private moveQueue: Moves[]; @@ -963,17 +985,21 @@ export class StatChangePhase extends PokemonPhase { export class ObtainStatusEffectPhase extends PokemonPhase { private statusEffect: StatusEffect; + private cureTurn: integer; - constructor(scene: BattleScene, player: boolean, statusEffect: StatusEffect) { + constructor(scene: BattleScene, player: boolean, statusEffect: StatusEffect, cureTurn?: integer) { super(scene, player); this.statusEffect = statusEffect; + this.cureTurn = cureTurn; } start() { const pokemon = this.getPokemon(); if (!pokemon.status) { if (pokemon.trySetStatus(this.statusEffect)) { + if (this.cureTurn) + pokemon.status.cureTurn = this.cureTurn; pokemon.updateInfo(true); new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => { this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect)))); @@ -1004,16 +1030,12 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { switch (pokemon.status.effect) { case StatusEffect.POISON: case StatusEffect.BURN: - pokemon.hp = Math.max(pokemon.hp - Math.max(pokemon.getMaxHp() >> 3, 1), 0); + pokemon.damage(Math.max(pokemon.getMaxHp() >> 3, 1)); break; case StatusEffect.TOXIC: - pokemon.hp = Math.max(pokemon.hp - Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1), 0); + pokemon.damage(Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1)); break; } - if (pokemon.hp <= 0) { - this.scene.pushPhase(new FaintPhase(this.scene, this.player)); - (this.player ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).resetBattleSummonData(); - } pokemon.updateInfo().then(() => this.end()); }); } else @@ -1109,6 +1131,7 @@ export class FaintPhase extends PokemonPhase { onComplete: () => { pokemon.setVisible(false); pokemon.y -= 150; + pokemon.trySetStatus(StatusEffect.FAINT); if (pokemon.isPlayer()) this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); this.scene.field.remove(pokemon); @@ -1347,7 +1370,7 @@ export class PokemonHealPhase extends CommonAnimPhase { } start() { - if (!this.skipAnim) + if (!this.skipAnim && this.getPokemon().getHpRatio() < 1) super.start(); else this.end(); diff --git a/src/battle-tag.ts b/src/battle-tag.ts index 79a8108cd..f4212b044 100644 --- a/src/battle-tag.ts +++ b/src/battle-tag.ts @@ -9,15 +9,18 @@ export enum BattleTagType { NONE, FLINCHED, CONFUSED, + NIGHTMARE, FRENZY, FLYING, UNDERGROUND, + BYPASS_SLEEP, IGNORE_FLYING } export enum BattleTagLapseType { FAINT, MOVE, + AFTER_MOVE, MOVE_EFFECT, TURN_END, CUSTOM @@ -40,7 +43,7 @@ export class BattleTag { onOverlap(pokemon: Pokemon): void { } - lapse(pokemon: Pokemon): boolean { + lapse(pokemon: Pokemon, lapseType: BattleTagLapseType): boolean { return --this.turnCount > 0; } } @@ -50,8 +53,8 @@ export class FlinchedTag extends BattleTag { super(BattleTagType.FLINCHED, BattleTagLapseType.MOVE, 1); } - lapse(pokemon: Pokemon): boolean { - super.lapse(pokemon); + lapse(pokemon: Pokemon, lapseType: BattleTagLapseType): boolean { + super.lapse(pokemon, lapseType); (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' flinched!'))); @@ -61,14 +64,14 @@ export class FlinchedTag extends BattleTag { } export class PseudoStatusTag extends BattleTag { - constructor(tagType: BattleTagType, turnCount: integer) { - super(tagType, BattleTagLapseType.MOVE, turnCount); + constructor(tagType: BattleTagType, lapseType: BattleTagLapseType, turnCount: integer) { + super(tagType, lapseType, turnCount); } } export class ConfusedTag extends PseudoStatusTag { - constructor(tagType: BattleTagType, turnCount: integer) { - super(tagType, turnCount); + constructor(turnCount: integer) { + super(BattleTagType.CONFUSED, BattleTagLapseType.MOVE, turnCount); } onAdd(pokemon: Pokemon): void { @@ -90,8 +93,8 @@ export class ConfusedTag extends PseudoStatusTag { pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is\nalready confused!'))); } - lapse(pokemon: Pokemon): boolean { - const ret = super.lapse(pokemon); + lapse(pokemon: Pokemon, lapseType: BattleTagLapseType): boolean { + const ret = super.lapse(pokemon, lapseType); if (ret) { pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is\nconfused!'))); @@ -103,7 +106,7 @@ export class ConfusedTag extends PseudoStatusTag { 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.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer())); (pokemon.scene.getCurrentPhase() as MovePhase).cancel(); } } @@ -112,6 +115,40 @@ export class ConfusedTag extends PseudoStatusTag { } } +export class NightmareTag extends PseudoStatusTag { + constructor() { + super(BattleTagType.NIGHTMARE, BattleTagLapseType.AFTER_MOVE, 1); + } + + onAdd(pokemon: Pokemon): void { + super.onAdd(pokemon); + + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' began\nhaving a nightmare!'))); + } + + onOverlap(pokemon: Pokemon): void { + super.onOverlap(pokemon); + + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is\nalready locked in a nightmare!'))); + } + + lapse(pokemon: Pokemon, lapseType: BattleTagLapseType): boolean { + console.trace(lapseType); + const ret = lapseType !== BattleTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); + + if (ret) { + pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is locked\nin a nightmare!'))); + pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CURSE)); // TODO: Update animation type + + const damage = Math.ceil(pokemon.getMaxHp() / 4); + pokemon.hp = Math.max(pokemon.hp - damage, 0); + pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer())); + } + + return ret; + } +} + export class HideSpriteTag extends BattleTag { constructor(tagType: BattleTagType, turnCount: integer) { super(tagType, BattleTagLapseType.MOVE_EFFECT, turnCount); @@ -139,10 +176,14 @@ export function getBattleTag(tagType: BattleTagType, turnCount: integer): Battle return new FlinchedTag(); break; case BattleTagType.CONFUSED: - return new ConfusedTag(tagType, turnCount); + return new ConfusedTag(turnCount); + case BattleTagType.NIGHTMARE: + return new NightmareTag(); case BattleTagType.FLYING: case BattleTagType.UNDERGROUND: return new HideSpriteTag(tagType, turnCount); + case BattleTagType.BYPASS_SLEEP: + return new BattleTag(BattleTagType.BYPASS_SLEEP, BattleTagLapseType.TURN_END, turnCount); case BattleTagType.IGNORE_FLYING: return new BattleTag(tagType, BattleTagLapseType.TURN_END, turnCount); default: diff --git a/src/modifier.ts b/src/modifier.ts index 609221167..e7b536925 100644 --- a/src/modifier.ts +++ b/src/modifier.ts @@ -356,6 +356,8 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { let restorePoints = this.restorePoints; if (!this.fainted) restorePoints = Math.floor(restorePoints * (args[1] as number)); + else + pokemon.resetStatus(); pokemon.hp = Math.min(pokemon.hp + (this.percent ? (restorePoints * 0.01) * pokemon.getMaxHp() : restorePoints), pokemon.getMaxHp()); } diff --git a/src/move.ts b/src/move.ts index fff09eb57..37b942baa 100644 --- a/src/move.ts +++ b/src/move.ts @@ -1,6 +1,5 @@ import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; -import { EnemyMovePhase, MessagePhase, ObtainStatusEffectPhase, PlayerMovePhase, PokemonHealPhase, StatChangePhase } from "./battle-phases"; -import BattleScene from "./battle-scene"; +import { EnemyMovePhase, MessagePhase, MovePhase, ObtainStatusEffectPhase, PlayerMovePhase, PokemonHealPhase, StatChangePhase } from "./battle-phases"; import { BattleStat } from "./battle-stat"; import Pokemon, { EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "./pokemon"; import { BattleTagType } from "./battle-tag"; @@ -686,8 +685,18 @@ enum MultiHitType { } class HealAttr extends MoveEffectAttr { + private fullHeal: boolean; + private showAnim: boolean; + + constructor(fullHeal?: boolean, showAnim?: boolean) { + super(true); + + this.fullHeal = !!fullHeal; + this.showAnim = !!showAnim; + } + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.getMaxHp() / 2), 1), getPokemonMessage(user, ' regained\nhealth!'), true, true)); + user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.getMaxHp() / (this.fullHeal ? 1 : 2)), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim)); return true; } } @@ -737,17 +746,23 @@ export class MultiHitAttr extends MoveAttr { class StatusEffectAttr extends MoveHitEffectAttr { public effect: StatusEffect; + public selfTarget: boolean; + public cureTurn: integer; - constructor(effect: StatusEffect) { + constructor(effect: StatusEffect, selfTarget?: boolean, cureTurn?: integer) { super(); + this.effect = effect; + this.selfTarget = !!selfTarget; + this.cureTurn = cureTurn; } apply(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)) { - user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, target.isPlayer(), this.effect)); + const pokemon = this.selfTarget ? user : target; + if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) { + user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.isPlayer(), this.effect, this.cureTurn)); return true; } } @@ -755,6 +770,19 @@ class StatusEffectAttr extends MoveHitEffectAttr { } } +export class BypassSleepAttr extends MoveAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + if (user.status?.effect === StatusEffect.SLEEP) { + console.log('add bypass sleep'); + user.addTag(BattleTagType.BYPASS_SLEEP, 1); + console.log(user.getTag(BattleTagType.BYPASS_SLEEP)); + return true; + } + + return false; + } +} + class OneHitKOAttr extends MoveHitEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { target.hp = 0; @@ -826,7 +854,7 @@ export class StatChangeAttr extends MoveEffectAttr { } } -export class ConditionalFailMoveAttr extends MoveAttr { +export class ConditionalMoveAttr extends MoveAttr { private successFunc: MoveAttrFunc; constructor(successFunc: MoveAttrFunc) { @@ -956,13 +984,28 @@ export function applyMoveAttrs(attrType: { new(...args: any[]): MoveAttr }, user }); } +class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const moves = user.moveset.filter(m => m.moveId !== move.id); + if (moves.length) { + const move = moves[Utils.randInt(moves.length)]; + const moveIndex = user.moveset.findIndex(m => m.moveId === move.moveId); + user.summonData.moveQueue.push({ move: move.moveId }); + user.scene.unshiftPhase(user.isPlayer() ? new PlayerMovePhase(user.scene, user as PlayerPokemon, user.moveset[moveIndex], true) : new EnemyMovePhase(user.scene, user as EnemyPokemon, user.moveset[moveIndex], true)); + return true; + } + + return false; + } +} + class RandomMoveAttr extends OverrideMoveEffectAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { return new Promise(resolve => { 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 }); - user.scene.unshiftPhase(user.isPlayer() ? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId)) : new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId))); + user.scene.unshiftPhase(user.isPlayer() ? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId), true) : new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId), true)); initMoveAnim(moveId).then(() => { loadMoveAnimAssets(user.scene, [ moveId ], true) .then(() => resolve(true)); @@ -1123,7 +1166,8 @@ export const allMoves = [ return true; })), new StatusMove(Moves.GLARE, "Glare", Type.NORMAL, 100, 30, -1, "Paralyzes opponent.", -1, 0, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), - new AttackMove(Moves.DREAM_EATER, "Dream Eater", Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 15, -1, "User recovers half the HP inflicted on a sleeping opponent.", -1, 0, 1, new HitHealAttr()), + new AttackMove(Moves.DREAM_EATER, "Dream Eater", Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 15, -1, "User recovers half the HP inflicted on a sleeping opponent.", -1, 0, 1, + new ConditionalMoveAttr((user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP), new HitHealAttr()), new StatusMove(Moves.POISON_GAS, "Poison Gas", Type.POISON, 90, 40, -1, "Poisons opponent.", -1, 0, 1, new StatusEffectAttr(StatusEffect.POISON)), new AttackMove(Moves.BARRAGE, "Barrage", Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, "Hits 2-5 times in one turn.", -1, 0, 1, new MultiHitAttr()), new AttackMove(Moves.LEECH_LIFE, "Leech Life", Type.BUG, MoveCategory.PHYSICAL, 80, 100, 10, 95, "User recovers half the HP inflicted on opponent.", -1, 0, 1, new HitHealAttr()), @@ -1142,7 +1186,8 @@ export const allMoves = [ new AttackMove(Moves.EXPLOSION, "Explosion", Type.NORMAL, MoveCategory.PHYSICAL, 250, 100, 5, -1, "User faints.", -1, 0, 1), new AttackMove(Moves.FURY_SWIPES, "Fury Swipes", Type.NORMAL, MoveCategory.PHYSICAL, 18, 80, 15, -1, "Hits 2-5 times in one turn.", -1, 0, 1, new MultiHitAttr()), new AttackMove(Moves.BONEMERANG, "Bonemerang", Type.GROUND, MoveCategory.PHYSICAL, 50, 90, 10, -1, "Hits twice in one turn.", -1, 0, 1, new MultiHitAttr(MultiHitType._2)), - new StatusMove(Moves.REST, "Rest", Type.PSYCHIC, -1, 5, 85, "User sleeps for 2 turns, but user is fully healed.", -1, 0, 1), + new StatusMove(Moves.REST, "Rest", Type.PSYCHIC, -1, 5, 85, "User sleeps for 2 turns, but user is fully healed.", -1, 0, 1, + new ConditionalMoveAttr((user: Pokemon, target: Pokemon, move: Move) => user.status?.effect !== StatusEffect.SLEEP), new StatusEffectAttr(StatusEffect.SLEEP, true, 3), new HealAttr(true, true)), new AttackMove(Moves.ROCK_SLIDE, "Rock Slide", Type.ROCK, MoveCategory.PHYSICAL, 75, 90, 10, 86, "May cause flinching.", 30, 0, 1, new FlinchAttr()), new AttackMove(Moves.HYPER_FANG, "Hyper Fang", Type.NORMAL, MoveCategory.PHYSICAL, 80, 90, 15, -1, "May cause flinching.", 10, 0, 1, new FlinchAttr()), new StatusMove(Moves.SHARPEN, "Sharpen", Type.NORMAL, -1, 30, -1, "Raises user's Attack.", -1, 0, 1, new StatChangeAttr(BattleStat.ATK, 1, true)), @@ -1161,9 +1206,11 @@ export const allMoves = [ new AttackMove(Moves.THIEF, "Thief", Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 18, "Also steals opponent's held item.", -1, 0, 2), new StatusMove(Moves.SPIDER_WEB, "Spider Web", Type.BUG, -1, 10, -1, "Opponent cannot escape/switch.", -1, 0, 2), new StatusMove(Moves.MIND_READER, "Mind Reader", Type.NORMAL, -1, 5, -1, "User's next attack is guaranteed to hit.", -1, 0, 2), - new StatusMove(Moves.NIGHTMARE, "Nightmare", Type.GHOST, 100, 15, -1, "The sleeping opponent loses 25% of its max HP each turn.", -1, 0, 2), + new StatusMove(Moves.NIGHTMARE, "Nightmare", Type.GHOST, 100, 15, -1, "The sleeping opponent loses 25% of its max HP each turn.", -1, 0, 2, + new ConditionalMoveAttr((user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP), new AddTagAttr(BattleTagType.NIGHTMARE, 1)), new AttackMove(Moves.FLAME_WHEEL, "Flame Wheel", Type.FIRE, MoveCategory.PHYSICAL, 60, 100, 25, -1, "May burn opponent.", 10, 0, 2, new StatusEffectAttr(StatusEffect.BURN)), - new AttackMove(Moves.SNORE, "Snore", Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 15, -1, "Can only be used if asleep. May cause flinching.", 30, 0, 2, new FlinchAttr()), // TODO + new AttackMove(Moves.SNORE, "Snore", Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 15, -1, "Can only be used if asleep. May cause flinching.", 30, 0, 2, + new BypassSleepAttr(), new ConditionalMoveAttr((user: Pokemon, target: Pokemon, move: Move) => user.status?.effect === StatusEffect.SLEEP), new FlinchAttr()), new StatusMove(Moves.CURSE, "Curse", Type.GHOST, -1, 10, -1, "Ghosts lose 50% of max HP and curse the opponent; Non-Ghosts raise Attack, Defense and lower Speed.", -1, 0, 2), new AttackMove(Moves.FLAIL, "Flail", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, "The lower the user's HP, the higher the power.", -1, 0, 2), new StatusMove(Moves.CONVERSION_2, "Conversion 2", Type.NORMAL, -1, 30, -1, "User changes type to become resistant to opponent's last move.", -1, 0, 2), @@ -1205,7 +1252,8 @@ export const allMoves = [ new AttackMove(Moves.STEEL_WING, "Steel Wing", Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, -1, "May raise user's Defense.", 10, 0, 2, new StatChangeAttr(BattleStat.DEF, 1, true)), new StatusMove(Moves.MEAN_LOOK, "Mean Look", Type.NORMAL, -1, 5, -1, "Opponent cannot flee or switch.", -1, 0, 2), new StatusMove(Moves.ATTRACT, "Attract", Type.NORMAL, 100, 15, -1, "If opponent is the opposite gender, it's less likely to attack.", -1, 0, 2), - new StatusMove(Moves.SLEEP_TALK, "Sleep Talk", Type.NORMAL, -1, 10, 70, "User performs one of its own moves while sleeping.", -1, 0, 2), + new StatusMove(Moves.SLEEP_TALK, "Sleep Talk", Type.NORMAL, -1, 10, 70, "User performs one of its own moves while sleeping.", -1, 0, 2, + new BypassSleepAttr(), new ConditionalMoveAttr((user: Pokemon, target: Pokemon, move: Move) => user.status?.effect === StatusEffect.SLEEP), new RandomMovesetMoveAttr()), new StatusMove(Moves.HEAL_BELL, "Heal Bell", Type.NORMAL, -1, 5, -1, "Heals the user's party's status conditions.", -1, 0, 2), new AttackMove(Moves.RETURN, "Return", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, "Power increases with higher Friendship.", -1, 0, 2), new AttackMove(Moves.PRESENT, "Present", Type.NORMAL, MoveCategory.PHYSICAL, -1, 90, 15, -1, "Either deals damage or heals.", -1, 0, 2), @@ -1246,7 +1294,7 @@ 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, 0, 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, 0, 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, 3, - new ConditionalFailMoveAttr((user: Pokemon, target: Pokemon, move: Move) => user.battleSummonData.turnCount === 1), new FlinchAttr()), + new ConditionalMoveAttr((user: Pokemon, target: Pokemon, move: Move) => user.battleSummonData.turnCount === 1), new FlinchAttr()), new AttackMove(Moves.UPROAR, "Uproar", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, "User attacks for 3 turns and prevents sleep.", -1, 0, 3), // TODO new StatusMove(Moves.STOCKPILE, "Stockpile", Type.NORMAL, -1, 20, -1, "Stores energy for use with Spit Up and Swallow.", -1, 0, 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, 0, 3), diff --git a/src/pokemon.ts b/src/pokemon.ts index f97f06135..1da72da9e 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -15,7 +15,7 @@ import { initMoveAnim, loadMoveAnimAssets } from './battle-anims'; import { Status, StatusEffect } from './status-effect'; import { tmSpecies } from './tms'; import { pokemonEvolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './pokemon-evolutions'; -import { DamagePhase, MessagePhase } from './battle-phases'; +import { DamagePhase, FaintPhase, MessagePhase } from './battle-phases'; import { BattleStat } from './battle-stat'; import { BattleTag, BattleTagLapseType, BattleTagType, getBattleTag } from './battle-tag'; import { Species } from './species'; @@ -476,7 +476,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (damage) { - this.hp = Math.max(this.hp - damage, 0); + this.damage(damage); source.turnData.damageDealt += damage; this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), result as DamageResult)) if (isCritical) @@ -510,6 +510,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return result; } + damage(damage: integer): void { + if (!this.hp) + return; + + this.hp = Math.max(this.hp - damage, 0); + if (!this.hp) { + this.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer())); + (this.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).resetBattleSummonData(); + } + } + addTag(tagType: BattleTagType, turnCount?: integer): boolean { const existingTag = this.getTag(tagType); if (existingTag) { @@ -545,7 +556,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { lapseTag(tagType: BattleTagType): void { const tags = this.summonData.tags; const tag = tags.find(t => t.tagType === tagType); - if (tag && !(tag.lapse(this))) { + if (tag && !(tag.lapse(this, BattleTagLapseType.CUSTOM))) { tag.onRemove(this); tags.splice(tags.indexOf(tag), 1); } @@ -553,7 +564,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { lapseTags(lapseType: BattleTagLapseType): void { const tags = this.summonData.tags; - tags.filter(t => lapseType === BattleTagLapseType.FAINT || ((t.lapseType === lapseType) && !(t.lapse(this))) || (lapseType === BattleTagLapseType.TURN_END && t.turnCount < 1)).forEach(t => { + tags.filter(t => lapseType === BattleTagLapseType.FAINT || ((t.lapseType === lapseType) && !(t.lapse(this, lapseType))) || (lapseType === BattleTagLapseType.TURN_END && t.turnCount < 1)).forEach(t => { t.onRemove(this); tags.splice(tags.indexOf(t), 1); }); @@ -651,7 +662,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } resetStatus(): void { + const lastStatus = this.status.effect; this.status = undefined; + if (lastStatus === StatusEffect.SLEEP) { + if (this.getTag(BattleTagType.NIGHTMARE)) + this.lapseTag(BattleTagType.NIGHTMARE); + } } resetSummonData(): void { diff --git a/src/status-effect.ts b/src/status-effect.ts index a1426050a..a829b9f7a 100644 --- a/src/status-effect.ts +++ b/src/status-effect.ts @@ -7,7 +7,8 @@ export enum StatusEffect { PARALYSIS, SLEEP, FREEZE, - BURN + BURN, + FAINT } export class Status {