Add sleep moves

pull/1/head
Flashfyre 2023-04-16 18:40:32 -04:00
parent d030327336
commit a66d2a8d17
6 changed files with 190 additions and 59 deletions

View File

@ -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();

View File

@ -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:

View File

@ -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());
}

View File

@ -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<boolean> {
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),

View File

@ -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 {

View File

@ -7,7 +7,8 @@ export enum StatusEffect {
PARALYSIS,
SLEEP,
FREEZE,
BURN
BURN,
FAINT
}
export class Status {