Add charging moves and tag system

This commit is contained in:
Flashfyre 2023-04-13 12:16:36 -04:00
parent e3f7603f22
commit 791bf3cc49
14 changed files with 12907 additions and 6397 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"id": 2004, "id": 2006,
"graphic": "PRAS- Status", "graphic": "PRAS- Status",
"frames": [ "frames": [
[ [

View File

@ -1,5 +1,5 @@
{ {
"id": 2006, "id": 2005,
"graphic": "PRAS- Status", "graphic": "PRAS- Status",
"frames": [ "frames": [
[ [

View File

@ -1,5 +1,5 @@
{ {
"id": 2005, "id": 2003,
"graphic": "PRAS- Status", "graphic": "PRAS- Status",
"frames": [ "frames": [
[ [

View File

@ -1,5 +1,5 @@
{ {
"id": 2002, "id": 2001,
"graphic": "PRAS- Status", "graphic": "PRAS- Status",
"frames": [ "frames": [
[ [

View File

@ -1,5 +1,5 @@
{ {
"id": 2001, "id": 2004,
"graphic": "PRAS- Status", "graphic": "PRAS- Status",
"frames": [ "frames": [
[ [

View File

@ -1,5 +1,5 @@
{ {
"id": 2003, "id": 2002,
"graphic": "PRAS- Poison", "graphic": "PRAS- Poison",
"frames": [ "frames": [
[ [

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,9 @@
//import { battleAnimRawData } from "./battle-anim-raw-data"; //import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "./battle-scene"; import BattleScene from "./battle-scene";
import { Moves } from "./move"; import { ChargeAttr, Moves, allMoves } from "./move";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./pokemon"; import Pokemon, { EnemyPokemon, PlayerPokemon } from "./pokemon";
import * as Utils from "./utils"; import * as Utils from "./utils";
//import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget { export enum AnimFrameTarget {
USER, USER,
@ -337,7 +338,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
} }
export const moveAnims = new Map<Moves, Anim | [Anim, Anim]>(); export const moveAnims = new Map<Moves, Anim | [Anim, Anim]>();
export const chargeAnims = new Map<ChargeAnim, Anim>(); export const chargeAnims = new Map<ChargeAnim, Anim | [Anim, Anim]>();
export const commonAnims = new Map<CommonAnim, Anim>(); export const commonAnims = new Map<CommonAnim, Anim>();
export function initCommonAnims(): Promise<void> { export function initCommonAnims(): Promise<void> {
@ -378,6 +379,39 @@ export function initMoveAnim(move: Moves): Promise<void> {
populateMoveAnim(move, ba[1]); populateMoveAnim(move, ba[1]);
} else } else
populateMoveAnim(move, ba); populateMoveAnim(move, ba);
const chargeAttr = allMoves[move - 1].getAttrs(ChargeAttr) as ChargeAttr[];
if (chargeAttr.length)
initMoveChargeAnim(chargeAttr[0].chargeAnim).then(() => resolve());
else
resolve();
});
}
});
}
export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise<void> {
return new Promise(resolve => {
if (chargeAnims.has(chargeAnim)) {
if (chargeAnims.get(chargeAnim) !== null)
resolve();
else {
let loadedCheckTimer = setInterval(() => {
if (chargeAnims.get(chargeAnim) !== null) {
clearInterval(loadedCheckTimer);
resolve();
}
}, 50);
}
} else {
chargeAnims.set(chargeAnim, null);
fetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, '-')}.json`)
.then(response => response.json())
.then(ca => {
if (Array.isArray(ca)) {
populateMoveChargeAnim(chargeAnim, ca[0]);
populateMoveChargeAnim(chargeAnim, ca[1]);
} else
populateMoveChargeAnim(chargeAnim, ca);
resolve(); resolve();
}); });
} }
@ -393,6 +427,15 @@ function populateMoveAnim(move: Moves, animSource: any) {
moveAnims.set(move, [ moveAnims.get(move) as Anim, moveAnim ]); moveAnims.set(move, [ moveAnims.get(move) as Anim, moveAnim ]);
} }
function populateMoveChargeAnim(chargeAnim: ChargeAnim, animSource: any) {
const moveChargeAnim = new Anim(animSource);
if (chargeAnims.get(chargeAnim) === null) {
chargeAnims.set(chargeAnim, moveChargeAnim);
return;
}
chargeAnims.set(chargeAnim, [ chargeAnims.get(chargeAnim) as Anim, moveChargeAnim ]);
}
export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> { export function loadCommonAnimAssets(scene: BattleScene, startLoad?: boolean): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
loadAnimAssets(scene, Array.from(commonAnims.values()), startLoad).then(() => resolve()); loadAnimAssets(scene, Array.from(commonAnims.values()), startLoad).then(() => resolve());
@ -404,9 +447,16 @@ export function loadMoveAnimAssets(scene: BattleScene, moveIds: Moves[], startLo
const moveAnimations = moveIds.map(m => { const moveAnimations = moveIds.map(m => {
const anims = moveAnims.get(m); const anims = moveAnims.get(m);
if (anims instanceof Anim) if (anims instanceof Anim)
return anims as Anim; return anims;
return anims[0] as Anim; return anims[0];
}); });
for (let moveId of moveIds) {
const chargeAttr = allMoves[moveId - 1].getAttrs(ChargeAttr) as ChargeAttr[];
if (chargeAttr.length) {
const moveChargeAnims = chargeAnims.get(chargeAttr[0].chargeAnim);
moveAnimations.push(moveChargeAnims instanceof Anim ? moveChargeAnims : moveChargeAnims[0]);
}
}
loadAnimAssets(scene, moveAnimations, startLoad).then(() => resolve()); loadAnimAssets(scene, moveAnimations, startLoad).then(() => resolve());
}); });
} }
@ -457,14 +507,18 @@ export abstract class BattleAnim {
abstract isReverseCoords(): boolean; abstract isReverseCoords(): boolean;
play(scene: BattleScene, callback?: Function) { play(scene: BattleScene, callback?: Function) {
const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user : this.target;
const target = !isOppAnim ? this.target : this.user;
const anim = this.getAnim(); const anim = this.getAnim();
const userInitialX = this.user.x; const userInitialX = user.x;
const userInitialY = this.user.y; const userInitialY = user.y;
const userHalfHeight = this.user.getSprite().displayHeight / 2; const userHalfHeight = user.getSprite().displayHeight / 2;
const targetInitialX = this.target.x; const targetInitialX = target.x;
const targetInitialY = this.target.y; const targetInitialY = target.y;
const targetHalfHeight = this.target.getSprite().displayHeight / 2; const targetHalfHeight = target.getSprite().displayHeight / 2;
const coordMultiplier = this.isReverseCoords() ? -1 : 1; const coordMultiplier = this.isReverseCoords() ? -1 : 1;
@ -483,10 +537,10 @@ export abstract class BattleAnim {
for (let frame of spriteFrames) { for (let frame of spriteFrames) {
switch (frame.target) { switch (frame.target) {
case AnimFrameTarget.USER: case AnimFrameTarget.USER:
this.user.setPosition(userInitialX + frame.x * coordMultiplier, userInitialY + frame.y * coordMultiplier); user.setPosition(userInitialX + frame.x * coordMultiplier, userInitialY + frame.y * coordMultiplier);
break; break;
case AnimFrameTarget.TARGET: case AnimFrameTarget.TARGET:
this.target.setPosition(targetInitialX + frame.x * coordMultiplier, targetInitialY + frame.y * coordMultiplier); target.setPosition(targetInitialX + frame.x * coordMultiplier, targetInitialY + frame.y * coordMultiplier);
break; break;
case AnimFrameTarget.GRAPHIC: case AnimFrameTarget.GRAPHIC:
if (g === sprites.length) { if (g === sprites.length) {
@ -498,16 +552,13 @@ export abstract class BattleAnim {
moveSprite.setFrame(frame.graphicFrame); moveSprite.setFrame(frame.graphicFrame);
const xProgress = Math.min(Math.max(frame.x, 0) / 128, 1); const xProgress = Math.min(Math.max(frame.x, 0) / 128, 1);
const yOffset = ((userHalfHeight * (1 - xProgress)) + (targetHalfHeight * xProgress)) * -1; const yOffset = ((userHalfHeight * (1 - xProgress)) + (targetHalfHeight * xProgress)) * -1;
const isOppAnim = this.isOppAnim();
moveSprite.setPosition((!isOppAnim ? userInitialX : targetInitialX) + frame.x * coordMultiplier, (!isOppAnim ? userInitialY : targetInitialY) + yOffset + frame.y * coordMultiplier); moveSprite.setPosition((!isOppAnim ? userInitialX : targetInitialX) + frame.x * coordMultiplier, (!isOppAnim ? userInitialY : targetInitialY) + yOffset + frame.y * coordMultiplier);
moveSprite.setAlpha(frame.opacity); moveSprite.setAlpha(frame.opacity);
moveSprite.setAngle(-frame.angle * coordMultiplier); moveSprite.setAngle(-frame.angle * coordMultiplier);
break; break;
} }
if (frame.target !== AnimFrameTarget.GRAPHIC) { if (frame.target !== AnimFrameTarget.GRAPHIC) {
const pokemon = frame.target === AnimFrameTarget.USER const pokemon = frame.target === AnimFrameTarget.USER ? user : target;
? this.user
: this.target;
pokemon.setAlpha(frame.opacity); pokemon.setAlpha(frame.opacity);
pokemon.setAngle(-frame.angle * coordMultiplier); pokemon.setAngle(-frame.angle * coordMultiplier);
const zoomScaleX = frame.zoomX / 100; const zoomScaleX = frame.zoomX / 100;
@ -530,22 +581,26 @@ export abstract class BattleAnim {
r--; r--;
}, },
onComplete: () => { onComplete: () => {
this.user.setPosition(userInitialX, userInitialY); const cleanUpAndComplete = () => {
this.user.setAlpha(1); user.setPosition(userInitialX, userInitialY);
this.user.setAngle(0); user.setAlpha(1);
this.target.setPosition(targetInitialX, targetInitialY); user.setAngle(0);
this.target.setAlpha(1); target.setPosition(targetInitialX, targetInitialY);
this.target.setAngle(0); target.setAlpha(1);
target.setAngle(0);
if (callback)
callback();
};
for (let ms of sprites) for (let ms of sprites)
ms.destroy(); ms.destroy();
if (r && callback) { if (r) {
scene.tweens.addCounter({ scene.tweens.addCounter({
duration: r, duration: r,
useFrames: true, useFrames: true,
onComplete: () => callback() onComplete: () => cleanUpAndComplete()
}); });
} else if (callback) } else
callback(); cleanUpAndComplete();
} }
}); });
} }
@ -597,6 +652,22 @@ export class MoveAnim extends BattleAnim {
} }
} }
export class MoveChargeAnim extends MoveAnim {
private chargeAnim: ChargeAnim;
constructor(chargeAnim: ChargeAnim, move: Moves, user: Pokemon, target: Pokemon) {
super(move, user, target);
this.chargeAnim = chargeAnim;
}
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;
}
}
export function populateAnims() { export function populateAnims() {
return; return;
const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase()); const commonAnimNames = Utils.getEnumKeys(CommonAnim).map(k => k.toLowerCase());
@ -605,7 +676,7 @@ export function populateAnims() {
const chargeAnimNames = Utils.getEnumKeys(ChargeAnim).map(k => k.toLowerCase()); const chargeAnimNames = Utils.getEnumKeys(ChargeAnim).map(k => k.toLowerCase());
const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/\_/g, ' ')); const chargeAnimMatchNames = chargeAnimNames.map(k => k.replace(/\_/g, ' '));
const chargeAnimIds = Utils.getEnumValues(ChargeAnim) as ChargeAnim[]; const chargeAnimIds = Utils.getEnumValues(ChargeAnim) as ChargeAnim[];
const commonNamePattern = /name: (?:Common:)?(.*)/; const commonNamePattern = /name: (?:Common:)?(Opp )?(.*)/;
const moveNameToId = {}; const moveNameToId = {};
for (let move of Utils.getEnumValues(Moves)) { for (let move of Utils.getEnumValues(Moves)) {
const moveName = Moves[move].toUpperCase().replace(/\_/g, ''); const moveName = Moves[move].toUpperCase().replace(/\_/g, '');
@ -620,7 +691,7 @@ export function populateAnims() {
let chargeAnimId: ChargeAnim; let chargeAnimId: ChargeAnim;
if (!fields[1].startsWith('name: Move:') && !(isOppMove = fields[1].startsWith('name: OppMove:'))) { if (!fields[1].startsWith('name: Move:') && !(isOppMove = fields[1].startsWith('name: OppMove:'))) {
const nameMatch = commonNamePattern.exec(fields[1]); const nameMatch = commonNamePattern.exec(fields[1]);
const name = nameMatch[1].toLowerCase(); const name = nameMatch[2].toLowerCase();
if (commonAnimMatchNames.indexOf(name) > -1) if (commonAnimMatchNames.indexOf(name) > -1)
commonAnimId = commonAnimIds[commonAnimMatchNames.indexOf(name)]; commonAnimId = commonAnimIds[commonAnimMatchNames.indexOf(name)];
else if (chargeAnimMatchNames.indexOf(name) > -1) else if (chargeAnimMatchNames.indexOf(name) > -1)
@ -635,7 +706,7 @@ export function populateAnims() {
if (commonAnimId) if (commonAnimId)
commonAnims.set(commonAnimId, anim); commonAnims.set(commonAnimId, anim);
else if (chargeAnimId) else if (chargeAnimId)
chargeAnims.set(chargeAnimId, anim); chargeAnims.set(chargeAnimId, !isOppMove ? anim : [ chargeAnims.get(chargeAnimId) as Anim, anim ]);
else else
moveAnims.set(moveNameToId[animName], !isOppMove ? anim : [ moveAnims.get(moveNameToId[animName]) as Anim, anim ]); moveAnims.set(moveNameToId[animName], !isOppMove ? anim : [ moveAnims.get(moveNameToId[animName]) as Anim, anim ]);
for (let f = 0; f < fields.length; f++) { for (let f = 0; f < fields.length; f++) {

View File

@ -1,14 +1,14 @@
import BattleScene from "./battle-scene"; import BattleScene from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove } from "./pokemon"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult } from "./pokemon";
import * as Utils from './utils'; import * as Utils from './utils';
import { allMoves, applyMoveAttrs, MissEffectAttr, MoveCategory, MoveHitEffectAttr, Moves, MultiHitAttr } from "./move"; import { allMoves, applyMoveAttrs, ChargeAttr, HitsTagAttr, MissEffectAttr, MoveCategory, MoveEffectAttr, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr } from "./move";
import { Mode } from './ui/ui'; import { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./pokemon-stat"; import { Stat } from "./pokemon-stat";
import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier } from "./modifier"; import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier } from "./modifier";
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./pokeball"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./pokeball";
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveAnim, chargeAnims, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { StatusEffect, getStatusEffectActivationText, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./status-effect"; import { StatusEffect, getStatusEffectActivationText, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./status-effect";
import { SummaryUiMode } from "./ui/summary-ui-handler"; import { SummaryUiMode } from "./ui/summary-ui-handler";
import EvolutionSceneHandler from "./ui/evolution-scene-handler"; import EvolutionSceneHandler from "./ui/evolution-scene-handler";
@ -19,6 +19,7 @@ import { Biome, biomeLinks } from "./biome";
import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier-type"; import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier-type";
import PokemonSpecies from "./pokemon-species"; import PokemonSpecies from "./pokemon-species";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { BattleTagLapseType } from "./battle-tag";
export class SelectStarterPhase extends BattlePhase { export class SelectStarterPhase extends BattlePhase {
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
@ -395,12 +396,17 @@ export class CommandPhase extends BattlePhase {
start() { start() {
super.start(); super.start();
this.scene.ui.setMode(Mode.COMMAND).then(() => { const playerPokemon = this.scene.getPlayerPokemon();
this.scene.currentBattle.addParticipant(this.scene.getPlayerPokemon());
this.scene.getPlayerPokemon().resetTurnData(); this.scene.currentBattle.addParticipant(playerPokemon);
this.scene.getEnemyPokemon().resetTurnData();
}); playerPokemon.resetTurnData();
this.scene.getEnemyPokemon().resetTurnData();
if (playerPokemon.summonData.moveQueue.length)
this.handleCommand(Command.FIGHT, playerPokemon.moveset.findIndex(m => m.moveId === playerPokemon.summonData.moveQueue[0].move));
else
this.scene.ui.setMode(Mode.COMMAND);
} }
handleCommand(command: Command, cursor: integer): boolean { handleCommand(command: Command, cursor: integer): boolean {
@ -454,6 +460,8 @@ export class CommandPhase extends BattlePhase {
for (let sef of statusEffectPhases) for (let sef of statusEffectPhases)
this.scene.pushPhase(sef); this.scene.pushPhase(sef);
this.scene.pushPhase(new TurnEndPhase(this.scene));
this.end(); this.end();
} }
@ -465,6 +473,19 @@ export class CommandPhase extends BattlePhase {
} }
} }
export class TurnEndPhase extends BattlePhase {
constructor(scene: BattleScene) {
super(scene);
}
start() {
this.scene.getPlayerPokemon().lapseTags(BattleTagLapseType.TURN_END);
this.scene.getEnemyPokemon().lapseTags(BattleTagLapseType.TURN_END);
this.end();
}
}
export abstract class PokemonPhase extends BattlePhase { export abstract class PokemonPhase extends BattlePhase {
protected player: boolean; protected player: boolean;
@ -522,7 +543,8 @@ abstract class MovePhase extends BattlePhase {
} }
if (!this.move) if (!this.move)
console.log(this.pokemon.moveset); console.log(this.pokemon.moveset);
this.move.ppUsed++; if (this.pokemon.summonData.moveQueue.length && !this.pokemon.summonData.moveQueue.shift().ignorePP)
this.move.ppUsed++;
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.pokemon.name} used\n${this.move.getName()}!`, 500)); this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.pokemon.name} used\n${this.move.getName()}!`, 500));
this.scene.unshiftPhase(this.getEffectPhase()); this.scene.unshiftPhase(this.getEffectPhase());
this.end(); this.end();
@ -608,35 +630,53 @@ abstract class MoveEffectPhase extends PokemonPhase {
const user = this.getUserPokemon(); const user = this.getUserPokemon();
const target = this.getTargetPokemon(); const target = this.getTargetPokemon();
if (user.turnData.hitsLeft === undefined) { const overridden = new Utils.BooleanHolder(false);
const hitCount = new Utils.IntegerHolder(1);
applyMoveAttrs(MultiHitAttr, this.scene, user, target, this.move.getMove(), hitCount);
user.turnData.hitCount = 0;
user.turnData.hitsLeft = user.turnData.hitsTotal = hitCount.value;
}
if (!this.hitCheck()) { applyMoveAttrs(OverrideMoveEffectAttr, this.scene, user, target, this.move.getMove(), overridden).then(() => {
this.scene.unshiftPhase(new MessagePhase(this.scene, `${!this.player ? 'Foe ' : ''}${user.name}'s\nattack missed!`));
applyMoveAttrs(MissEffectAttr, this.scene, user, target, this.move.getMove());
this.end();
return;
}
new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () => { if (overridden.value) {
this.getTargetPokemon().apply(this.getUserPokemon(), this.move, () => {
++user.turnData.hitCount;
if (this.getTargetPokemon().hp)
applyMoveAttrs(MoveHitEffectAttr, this.scene, user, target, this.move.getMove());
this.end(); this.end();
return;
}
user.lapseTags(BattleTagLapseType.MOVE);
if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1);
applyMoveAttrs(MultiHitAttr, this.scene, user, target, this.move.getMove(), hitCount);
user.turnData.hitCount = 0;
user.turnData.hitsLeft = user.turnData.hitsTotal = hitCount.value;
}
if (!this.hitCheck()) {
this.scene.unshiftPhase(new MessagePhase(this.scene, `${!this.player ? 'Foe ' : ''}${user.name}'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();
return;
}
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();
});
}); });
if (this.getUserPokemon().hp <= 0) {
this.scene.pushPhase(new FaintPhase(this.scene, this.player));
this.getTargetPokemon().resetBattleSummonData();
}
if (this.getTargetPokemon().hp <= 0) {
this.scene.pushPhase(new FaintPhase(this.scene, !this.player));
this.getUserPokemon().resetBattleSummonData();
}
}); });
} }
@ -644,13 +684,23 @@ abstract class MoveEffectPhase extends PokemonPhase {
const user = this.getUserPokemon(); const user = this.getUserPokemon();
if (--user.turnData.hitsLeft && this.getTargetPokemon().hp) if (--user.turnData.hitsLeft && this.getTargetPokemon().hp)
this.scene.unshiftPhase(this.getNewHitPhase()); this.scene.unshiftPhase(this.getNewHitPhase());
else if (user.turnData.hitsTotal > 1) else {
this.scene.unshiftPhase(new MessagePhase(this.scene, `Hit ${user.turnData.hitCount} time(s)!`)); if (user.turnData.hitsTotal > 1)
this.scene.unshiftPhase(new MessagePhase(this.scene, `Hit ${user.turnData.hitCount} time(s)!`));
}
super.end(); super.end();
} }
hitCheck(): boolean { hitCheck(): boolean {
// Check if not self targeting for this
const hiddenTag = this.getTargetPokemon().getTag(t => t.isHidden());
if (hiddenTag) {
if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length)
return false;
}
if (this.move.getMove().category !== MoveCategory.STATUS) { if (this.move.getMove().category !== MoveCategory.STATUS) {
const userAccuracyLevel = this.getUserPokemon().summonData.battleStats[BattleStat.ACC]; const userAccuracyLevel = this.getUserPokemon().summonData.battleStats[BattleStat.ACC];
const targetEvasionLevel = this.getTargetPokemon().summonData.battleStats[BattleStat.EVA]; const targetEvasionLevel = this.getTargetPokemon().summonData.battleStats[BattleStat.EVA];
@ -890,6 +940,9 @@ export class FaintPhase extends PokemonPhase {
} }
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
pokemon.lapseTags(BattleTagLapseType.FAINT);
pokemon.faintCry(() => { pokemon.faintCry(() => {
pokemon.hideInfo(); pokemon.hideInfo();
this.scene.sound.play('faint'); this.scene.sound.play('faint');

33
src/battle-tag.ts Normal file
View File

@ -0,0 +1,33 @@
export enum BattleTagType {
NONE,
FLYING,
UNDERGROUND
}
export enum BattleTagLapseType {
FAINT,
MOVE,
TURN_END
}
export class BattleTag {
public tagType: BattleTagType;
public lapseType: BattleTagLapseType;
public turnCount: integer;
constructor(tagType: BattleTagType, lapseType: BattleTagLapseType, turnCount: integer) {
this.tagType = tagType;
this.lapseType = lapseType;
this.turnCount = turnCount;
}
isHidden() {
switch (this.tagType) {
case BattleTagType.FLYING:
case BattleTagType.UNDERGROUND:
return true;
}
return false;
}
}

View File

@ -1,8 +1,10 @@
import { ChargeAnim, MoveChargeAnim } from "./battle-anims";
import { MessagePhase, ObtainStatusEffectPhase, StatChangePhase } from "./battle-phases"; import { MessagePhase, ObtainStatusEffectPhase, StatChangePhase } from "./battle-phases";
import BattleScene from "./battle-scene"; import BattleScene from "./battle-scene";
import { BattleStat } from "./battle-stat"; import { BattleStat } from "./battle-stat";
import Pokemon, { PlayerPokemon } from "./pokemon"; import Pokemon, { EnemyPokemon, MoveResult, PlayerPokemon, TurnMove } from "./pokemon";
import { StatusEffect, getStatusEffectOverlapText } from "./status-effect"; import { BattleTagLapseType, BattleTagType } from "./battle-tag";
import { StatusEffect } from "./status-effect";
import { Type } from "./type"; import { Type } from "./type";
import * as Utils from "./utils"; import * as Utils from "./utils";
@ -615,11 +617,14 @@ const enum MoveEffectText {
type MoveAttrFunc = (scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) => void; type MoveAttrFunc = (scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) => void;
export abstract class MoveAttr { export abstract class MoveAttr {
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
return true; return true;
} }
} }
export class MoveEffectAttr extends MoveAttr {
}
export class MoveHitEffectAttr extends MoveAttr { export class MoveHitEffectAttr extends MoveAttr {
} }
@ -700,21 +705,45 @@ class OneHitKOAttr extends MoveHitEffectAttr {
} }
} }
class ChargeAttr extends MoveAttr { export class OverrideMoveEffectAttr extends MoveAttr { }
private chargeEffect: boolean;
constructor(chargeEffect?: boolean) { export class ChargeAttr extends OverrideMoveEffectAttr {
public chargeAnim: ChargeAnim;
private chargeText: string;
private tagType: BattleTagType;
public chargeEffect: boolean;
constructor(chargeAnim: ChargeAnim, chargeText: string, tagType?: BattleTagType, chargeEffect?: boolean) {
super(); super();
this.chargeAnim = chargeAnim;
this.chargeText = chargeText;
this.tagType = tagType;
this.chargeEffect = !!chargeEffect; this.chargeEffect = !!chargeEffect;
} }
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return true; return new Promise(resolve => {
const lastMove = user.getLastXMoves(1) as TurnMove[];
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}`));
if (this.tagType)
user.addTag(this.tagType, BattleTagLapseType.MOVE);
if (this.chargeEffect)
applyMoveAttrs(MoveEffectAttr, scene, user, target, move);
user.summonData.moveHistory.push({ move: move.id, result: MoveResult.OTHER });
user.summonData.moveQueue.push({ move: move.id, ignorePP: true });
resolve(true);
});
} else
resolve(true);
});
} }
} }
export class StatChangeAttr extends MoveHitEffectAttr { export class StatChangeAttr extends MoveEffectAttr {
public stats: BattleStat[]; public stats: BattleStat[];
public levels: integer; public levels: integer;
public selfTarget: boolean; public selfTarget: boolean;
@ -758,13 +787,31 @@ export class MissEffectAttr extends MoveAttr {
} }
} }
export function applyMoveAttrs(attrType: { new(...args: any[]): MoveAttr }, scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): void { export class HitsTagAttr extends MoveAttr {
const moveAttrs = move.attrs.filter(a => a instanceof attrType); public tagType: BattleTagType;
for (let attr of moveAttrs) { public doubleDamage: boolean;
attr.apply(scene, user, target, move, args);
constructor(tagType: BattleTagType, doubleDamage?: boolean) {
super();
this.tagType = tagType;
this.doubleDamage = !!doubleDamage;
} }
} }
export function applyMoveAttrs(attrType: { new(...args: any[]): MoveAttr }, scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> {
return new Promise(resolve => {
const attrPromises: Promise<boolean>[] = [];
const moveAttrs = move.attrs.filter(a => a instanceof attrType);
for (let attr of moveAttrs) {
const result = attr.apply(scene, user, target, move, args);
if (result instanceof Promise<boolean>)
attrPromises.push(result);
}
Promise.allSettled(attrPromises).then(() => resolve());
});
}
export const allMoves = [ export const allMoves = [
new Move(Moves.POUND, "Pound", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, "", -1, 1), new Move(Moves.POUND, "Pound", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, "", -1, 1),
new Move(Moves.KARATE_CHOP, "Karate Chop", Type.FIGHTING, MoveCategory.PHYSICAL, 50, 100, 25, -1, "High critical hit ratio.", -1, 1, new HighCritAttr()), new Move(Moves.KARATE_CHOP, "Karate Chop", Type.FIGHTING, MoveCategory.PHYSICAL, 50, 100, 25, -1, "High critical hit ratio.", -1, 1, new HighCritAttr()),
@ -778,13 +825,16 @@ export const allMoves = [
new Move(Moves.SCRATCH, "Scratch", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, "", -1, 1), new Move(Moves.SCRATCH, "Scratch", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, "", -1, 1),
new Move(Moves.VISE_GRIP, "Vise Grip", Type.NORMAL, MoveCategory.PHYSICAL, 55, 100, 30, -1, "", -1, 1), new Move(Moves.VISE_GRIP, "Vise Grip", Type.NORMAL, MoveCategory.PHYSICAL, 55, 100, 30, -1, "", -1, 1),
new Move(Moves.GUILLOTINE, "Guillotine", Type.NORMAL, MoveCategory.PHYSICAL, -1, 30, 5, -1, "One-Hit-KO, if it hits.", -1, 1, new OneHitKOAttr()), new Move(Moves.GUILLOTINE, "Guillotine", Type.NORMAL, MoveCategory.PHYSICAL, -1, 30, 5, -1, "One-Hit-KO, if it hits.", -1, 1, new OneHitKOAttr()),
new Move(Moves.RAZOR_WIND, "Razor Wind", Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, "Charges on first turn, attacks on second. High critical hit ratio.", -1, 1, new ChargeAttr(), new HighCritAttr()), new Move(Moves.RAZOR_WIND, "Razor Wind", Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, "Charges on first turn, attacks on second. High critical hit ratio.", -1, 1,
new ChargeAttr(ChargeAnim.RAZOR_WIND_CHARGING, 'whipped\nup a whirlwind!'), new HighCritAttr()),
new Move(Moves.SWORDS_DANCE, "Swords Dance", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, 88, "Sharply raises user's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, 2, true)), new Move(Moves.SWORDS_DANCE, "Swords Dance", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, 88, "Sharply raises user's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, 2, true)),
new Move(Moves.CUT, "Cut", Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, "", -1, 1), new Move(Moves.CUT, "Cut", Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, "", -1, 1),
new Move(Moves.GUST, "Gust", Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, "Hits Pokémon using Fly/Bounce/Sky Drop with double power.", -1, 1), // TODO new Move(Moves.GUST, "Gust", Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, "Hits Pokémon using Fly/Bounce/Sky Drop with double power.", -1, 1,
new HitsTagAttr(BattleTagType.FLYING, true)),
new Move(Moves.WING_ATTACK, "Wing Attack", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, "", -1, 1), new Move(Moves.WING_ATTACK, "Wing Attack", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, "", -1, 1),
new Move(Moves.WHIRLWIND, "Whirlwind", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 1), // TODO new Move(Moves.WHIRLWIND, "Whirlwind", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 1), // TODO
new Move(Moves.FLY, "Fly", Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, 97, "Flies up on first turn, attacks on second turn.", -1, 1, new ChargeAttr()), new Move(Moves.FLY, "Fly", Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, 97, "Flies up on first turn, attacks on second turn.", -1, 1,
new ChargeAttr(ChargeAnim.FLY_CHARGING, 'flew\nup high!', BattleTagType.FLYING)),
new Move(Moves.BIND, "Bind", Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1), // TODO new Move(Moves.BIND, "Bind", Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1), // TODO
new Move(Moves.SLAM, "Slam", Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, "", -1, 1), new Move(Moves.SLAM, "Slam", Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, "", -1, 1),
new Move(Moves.VINE_WHIP, "Vine Whip", Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, "", -1, 1), new Move(Moves.VINE_WHIP, "Vine Whip", Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, "", -1, 1),
@ -842,7 +892,8 @@ export const allMoves = [
new Move(Moves.GROWTH, "Growth", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, -1, "Raises user's Attack and Special Attack.", -1, 1, new Move(Moves.GROWTH, "Growth", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, -1, "Raises user's Attack and Special Attack.", -1, 1,
new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], 1, true)), new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], 1, true)),
new Move(Moves.RAZOR_LEAF, "Razor Leaf", Type.GRASS, MoveCategory.PHYSICAL, 55, 95, 25, -1, "High critical hit ratio.", -1, 1), new Move(Moves.RAZOR_LEAF, "Razor Leaf", Type.GRASS, MoveCategory.PHYSICAL, 55, 95, 25, -1, "High critical hit ratio.", -1, 1),
new Move(Moves.SOLAR_BEAM, "Solar Beam", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, 168, "Charges on first turn, attacks on second.", -1, 1), new Move(Moves.SOLAR_BEAM, "Solar Beam", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, 168, "Charges on first turn, attacks on second.", -1, 1,
new ChargeAttr(ChargeAnim.SOLAR_BEAM_CHARGING, 'took\nin sunlight!')),
new Move(Moves.POISON_POWDER, "Poison Powder", Type.POISON, MoveCategory.STATUS, -1, 75, 35, -1, "Poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.POISON)), new Move(Moves.POISON_POWDER, "Poison Powder", Type.POISON, MoveCategory.STATUS, -1, 75, 35, -1, "Poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.POISON)),
new Move(Moves.STUN_SPORE, "Stun Spore", Type.GRASS, MoveCategory.STATUS, -1, 75, 30, -1, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), new Move(Moves.STUN_SPORE, "Stun Spore", Type.GRASS, MoveCategory.STATUS, -1, 75, 30, -1, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.SLEEP_POWDER, "Sleep Powder", Type.GRASS, MoveCategory.STATUS, -1, 75, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new Move(Moves.SLEEP_POWDER, "Sleep Powder", Type.GRASS, MoveCategory.STATUS, -1, 75, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
@ -855,9 +906,11 @@ export const allMoves = [
new Move(Moves.THUNDER_WAVE, "Thunder Wave", Type.ELECTRIC, MoveCategory.STATUS, -1, 90, 20, 82, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), new Move(Moves.THUNDER_WAVE, "Thunder Wave", Type.ELECTRIC, MoveCategory.STATUS, -1, 90, 20, 82, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.THUNDER, "Thunder", Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 166, "May paralyze opponent.", 30, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), new Move(Moves.THUNDER, "Thunder", Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 166, "May paralyze opponent.", 30, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.ROCK_THROW, "Rock Throw", Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, "", -1, 1), new Move(Moves.ROCK_THROW, "Rock Throw", Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, "", -1, 1),
new Move(Moves.EARTHQUAKE, "Earthquake", Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, 149, "Power is doubled if opponent is underground from using Dig.", -1, 1), new Move(Moves.EARTHQUAKE, "Earthquake", Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, 149, "Power is doubled if opponent is underground from using Dig.", -1, 1,
new HitsTagAttr(BattleTagType.UNDERGROUND, true)),
new Move(Moves.FISSURE, "Fissure", Type.GROUND, MoveCategory.PHYSICAL, -1, 30, 5, -1, "One-Hit-KO, if it hits.", -1, 1, new OneHitKOAttr()), new Move(Moves.FISSURE, "Fissure", Type.GROUND, MoveCategory.PHYSICAL, -1, 30, 5, -1, "One-Hit-KO, if it hits.", -1, 1, new OneHitKOAttr()),
new Move(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 Move(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 Move(Moves.TOXIC, "Toxic", Type.POISON, MoveCategory.STATUS, -1, 90, 10, -1, "Badly poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.TOXIC)), new Move(Moves.TOXIC, "Toxic", Type.POISON, MoveCategory.STATUS, -1, 90, 10, -1, "Badly poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.TOXIC)),
new Move(Moves.CONFUSION, "Confusion", Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, -1, "May confuse opponent.", 10, 1), // TODO new Move(Moves.CONFUSION, "Confusion", Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, -1, "May confuse opponent.", 10, 1), // TODO
new Move(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 Move(Moves.PSYCHIC, "Psychic", Type.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 120, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)),
@ -896,7 +949,8 @@ export const allMoves = [
new Move(Moves.WATERFALL, "Waterfall", Type.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 77, "May cause flinching.", 20, 1, new FlinchAttr()), new Move(Moves.WATERFALL, "Waterfall", Type.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 77, "May cause flinching.", 20, 1, new FlinchAttr()),
new Move(Moves.CLAMP, "Clamp", Type.WATER, MoveCategory.PHYSICAL, 35, 85, 15, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1), new Move(Moves.CLAMP, "Clamp", Type.WATER, MoveCategory.PHYSICAL, 35, 85, 15, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1),
new Move(Moves.SWIFT, "Swift", Type.NORMAL, MoveCategory.SPECIAL, 60, 999, 20, 32, "Ignores Accuracy and Evasiveness.", -1, 1), new Move(Moves.SWIFT, "Swift", Type.NORMAL, MoveCategory.SPECIAL, 60, 999, 20, 32, "Ignores Accuracy and Evasiveness.", -1, 1),
new Move(Moves.SKULL_BASH, "Skull Bash", Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, "Raises Defense on first turn, attacks on second.", 100, 1, new ChargeAttr(true), new StatChangeAttr(BattleStat.DEF, 1, true)), new Move(Moves.SKULL_BASH, "Skull Bash", Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, "Raises Defense on first turn, attacks on second.", 100, 1,
new ChargeAttr(ChargeAnim.SKULL_BASH_CHARGING, 'lowered\nits head!', null, true), new StatChangeAttr(BattleStat.DEF, 1, true)),
new Move(Moves.SPIKE_CANNON, "Spike Cannon", Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, "Hits 2-5 times in one turn.", -1, 1, new MultiHitAttr()), new Move(Moves.SPIKE_CANNON, "Spike Cannon", Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, "Hits 2-5 times in one turn.", -1, 1, new MultiHitAttr()),
new Move(Moves.CONSTRICT, "Constrict", Type.NORMAL, MoveCategory.PHYSICAL, 10, 100, 35, -1, "May lower opponent's Speed by one stage.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)), new Move(Moves.CONSTRICT, "Constrict", Type.NORMAL, MoveCategory.PHYSICAL, 10, 100, 35, -1, "May lower opponent's Speed by one stage.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)),
new Move(Moves.AMNESIA, "Amnesia", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 20, 128, "Sharply raises user's Special Defense.", -1, 1, new StatChangeAttr(BattleStat.SPDEF, 2, true)), new Move(Moves.AMNESIA, "Amnesia", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 20, 128, "Sharply raises user's Special Defense.", -1, 1, new StatChangeAttr(BattleStat.SPDEF, 2, true)),
@ -910,7 +964,8 @@ export const allMoves = [
new Move(Moves.BARRAGE, "Barrage", Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, "Hits 2-5 times in one turn.", -1, 1, new MultiHitAttr()), new Move(Moves.BARRAGE, "Barrage", Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, "Hits 2-5 times in one turn.", -1, 1, new MultiHitAttr()),
new Move(Moves.LEECH_LIFE, "Leech Life", Type.BUG, MoveCategory.PHYSICAL, 80, 100, 10, 95, "User recovers half the HP inflicted on opponent.", -1, 1), new Move(Moves.LEECH_LIFE, "Leech Life", Type.BUG, MoveCategory.PHYSICAL, 80, 100, 10, 95, "User recovers half the HP inflicted on opponent.", -1, 1),
new Move(Moves.LOVELY_KISS, "Lovely Kiss", Type.NORMAL, MoveCategory.STATUS, -1, 75, 10, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new Move(Moves.LOVELY_KISS, "Lovely Kiss", Type.NORMAL, MoveCategory.STATUS, -1, 75, 10, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
new Move(Moves.SKY_ATTACK, "Sky Attack", Type.FLYING, MoveCategory.PHYSICAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May cause flinching. High critical hit ratio.", 30, 1, new ChargeAttr(), new HighCritAttr(), new FlinchAttr()), new Move(Moves.SKY_ATTACK, "Sky Attack", Type.FLYING, MoveCategory.PHYSICAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May cause flinching. High critical hit ratio.", 30, 1,
new ChargeAttr(ChargeAnim.SKY_ATTACK_CHARGING, 'is glowing!'), new HighCritAttr(), new FlinchAttr()),
new Move(Moves.TRANSFORM, "Transform", Type.NORMAL, MoveCategory.STATUS, -1, -1, 10, -1, "User takes on the form and attacks of the opponent.", -1, 1), new Move(Moves.TRANSFORM, "Transform", Type.NORMAL, MoveCategory.STATUS, -1, -1, 10, -1, "User takes on the form and attacks of the opponent.", -1, 1),
new Move(Moves.BUBBLE, "Bubble", Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)), new Move(Moves.BUBBLE, "Bubble", Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)),
new Move(Moves.DIZZY_PUNCH, "Dizzy Punch", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, "May confuse opponent.", 20, 1), // TODO new Move(Moves.DIZZY_PUNCH, "Dizzy Punch", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, "May confuse opponent.", 20, 1), // TODO
@ -1007,7 +1062,8 @@ export const allMoves = [
new Move(Moves.MOONLIGHT, "Moonlight", Type.FAIRY, MoveCategory.STATUS, -1, -1, 5, -1, "User recovers HP. Amount varies with the weather.", -1, 2), new Move(Moves.MOONLIGHT, "Moonlight", Type.FAIRY, MoveCategory.STATUS, -1, -1, 5, -1, "User recovers HP. Amount varies with the weather.", -1, 2),
new Move(Moves.HIDDEN_POWER, "Hidden Power", Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, "Type and power depends on user's IVs.", -1, 2), new Move(Moves.HIDDEN_POWER, "Hidden Power", Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, "Type and power depends on user's IVs.", -1, 2),
new Move(Moves.CROSS_CHOP, "Cross Chop", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, "High critical hit ratio.", -1, 2, new HighCritAttr()), new Move(Moves.CROSS_CHOP, "Cross Chop", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, "High critical hit ratio.", -1, 2, new HighCritAttr()),
new Move(Moves.TWISTER, "Twister", Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, -1, "May cause flinching. Hits Pokémon using Fly/Bounce with double power.", 20, 2, new FlinchAttr()), // TODO new Move(Moves.TWISTER, "Twister", Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, -1, "May cause flinching. Hits Pokémon using Fly/Bounce with double power.", 20, 2,
new HitsTagAttr(BattleTagType.FLYING, true), new FlinchAttr()), // TODO
new Move(Moves.RAIN_DANCE, "Rain Dance", Type.WATER, MoveCategory.STATUS, -1, -1, 5, 50, "Makes it rain for 5 turns.", -1, 2), new Move(Moves.RAIN_DANCE, "Rain Dance", Type.WATER, MoveCategory.STATUS, -1, -1, 5, 50, "Makes it rain for 5 turns.", -1, 2),
new Move(Moves.SUNNY_DAY, "Sunny Day", Type.FIRE, MoveCategory.STATUS, -1, -1, 5, 49, "Makes it sunny for 5 turns.", -1, 2), new Move(Moves.SUNNY_DAY, "Sunny Day", Type.FIRE, MoveCategory.STATUS, -1, -1, 5, 49, "Makes it sunny for 5 turns.", -1, 2),
new Move(Moves.CRUNCH, "Crunch", Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 108, "May lower opponent's Defense.", 20, 2, new StatChangeAttr(BattleStat.DEF, -1)), new Move(Moves.CRUNCH, "Crunch", Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 108, "May lower opponent's Defense.", 20, 2, new StatChangeAttr(BattleStat.DEF, -1)),
@ -1062,7 +1118,8 @@ export const allMoves = [
new Move(Moves.GRUDGE, "Grudge", Type.GHOST, MoveCategory.STATUS, -1, -1, 5, -1, "If the users faints after using this move, the PP for the opponent's last move is depleted.", -1, 3), new Move(Moves.GRUDGE, "Grudge", Type.GHOST, MoveCategory.STATUS, -1, -1, 5, -1, "If the users faints after using this move, the PP for the opponent's last move is depleted.", -1, 3),
new Move(Moves.SNATCH, "Snatch", Type.DARK, MoveCategory.STATUS, -1, -1, 10, -1, "Steals the effects of the opponent's next move.", -1, 3), new Move(Moves.SNATCH, "Snatch", Type.DARK, MoveCategory.STATUS, -1, -1, 10, -1, "Steals the effects of the opponent's next move.", -1, 3),
new Move(Moves.SECRET_POWER, "Secret Power", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, "Effects of the attack vary with the location.", 30, 3), new Move(Moves.SECRET_POWER, "Secret Power", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, "Effects of the attack vary with the location.", 30, 3),
new Move(Moves.DIVE, "Dive", Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, "Dives underwater on first turn, attacks on second turn.", -1, 3, new ChargeAttr()), new Move(Moves.DIVE, "Dive", Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, "Dives underwater on first turn, attacks on second turn.", -1, 3,
new ChargeAttr(ChargeAnim.DIVE_CHARGING, 'hid\nunderwater!')),
new Move(Moves.ARM_THRUST, "Arm Thrust", Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, "Hits 2-5 times in one turn.", -1, 3, new MultiHitAttr()), new Move(Moves.ARM_THRUST, "Arm Thrust", Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, "Hits 2-5 times in one turn.", -1, 3, new MultiHitAttr()),
new Move(Moves.CAMOUFLAGE, "Camouflage", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, -1, "Changes user's type according to the location.", -1, 3), new Move(Moves.CAMOUFLAGE, "Camouflage", Type.NORMAL, MoveCategory.STATUS, -1, -1, 20, -1, "Changes user's type according to the location.", -1, 3),
new Move(Moves.TAIL_GLOW, "Tail Glow", Type.BUG, MoveCategory.STATUS, -1, -1, 20, -1, "Drastically raises user's Special Attack.", -1, 3, new StatChangeAttr(BattleStat.SPATK, 3, true)), new Move(Moves.TAIL_GLOW, "Tail Glow", Type.BUG, MoveCategory.STATUS, -1, -1, 20, -1, "Drastically raises user's Special Attack.", -1, 3, new StatChangeAttr(BattleStat.SPATK, 3, true)),
@ -1101,7 +1158,7 @@ export const allMoves = [
new Move(Moves.SIGNAL_BEAM, "Signal Beam", Type.BUG, MoveCategory.SPECIAL, 75, 100, 15, -1, "May confuse opponent.", 10, 3), // TODO new Move(Moves.SIGNAL_BEAM, "Signal Beam", Type.BUG, MoveCategory.SPECIAL, 75, 100, 15, -1, "May confuse opponent.", 10, 3), // TODO
new Move(Moves.SHADOW_PUNCH, "Shadow Punch", Type.GHOST, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3), new Move(Moves.SHADOW_PUNCH, "Shadow Punch", Type.GHOST, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3),
new Move(Moves.EXTRASENSORY, "Extrasensory", Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, -1, "May cause flinching.", 10, 3, new FlinchAttr()), new Move(Moves.EXTRASENSORY, "Extrasensory", Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, -1, "May cause flinching.", 10, 3, new FlinchAttr()),
new Move(Moves.SKY_UPPERCUT, "Sky Uppercut", Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, "Hits the opponent, even during Fly.", -1, 3), new Move(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)),
new Move(Moves.SAND_TOMB, "Sand Tomb", Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 3), new Move(Moves.SAND_TOMB, "Sand Tomb", Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 3),
new Move(Moves.SHEER_COLD, "Sheer Cold", Type.ICE, MoveCategory.SPECIAL, -1, 30, 5, -1, "One-Hit-KO, if it hits.", -1, 3), new Move(Moves.SHEER_COLD, "Sheer Cold", Type.ICE, MoveCategory.SPECIAL, -1, 30, 5, -1, "One-Hit-KO, if it hits.", -1, 3),
new Move(Moves.MUDDY_WATER, "Muddy Water", Type.WATER, MoveCategory.SPECIAL, 90, 85, 10, -1, "May lower opponent's Accuracy.", 30, 3, new StatChangeAttr(BattleStat.ACC, -1)), new Move(Moves.MUDDY_WATER, "Muddy Water", Type.WATER, MoveCategory.SPECIAL, 90, 85, 10, -1, "May lower opponent's Accuracy.", 30, 3, new StatChangeAttr(BattleStat.ACC, -1)),
@ -1115,7 +1172,8 @@ export const allMoves = [
new Move(Moves.FRENZY_PLANT, "Frenzy Plant", Type.GRASS, MoveCategory.SPECIAL, 150, 90, 5, 155, "User must recharge next turn.", -1, 3), new Move(Moves.FRENZY_PLANT, "Frenzy Plant", Type.GRASS, MoveCategory.SPECIAL, 150, 90, 5, 155, "User must recharge next turn.", -1, 3),
new Move(Moves.BULK_UP, "Bulk Up", Type.FIGHTING, MoveCategory.STATUS, -1, -1, 20, 64, "Raises user's Attack and Defense.", -1, 3, new Move(Moves.BULK_UP, "Bulk Up", Type.FIGHTING, MoveCategory.STATUS, -1, -1, 20, 64, "Raises user's Attack and Defense.", -1, 3,
new StatChangeAttr([ BattleStat.ATK, BattleStat.DEF ], 1, true)), new StatChangeAttr([ BattleStat.ATK, BattleStat.DEF ], 1, true)),
new Move(Moves.BOUNCE, "Bounce", Type.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, -1, "Springs up on first turn, attacks on second. May paralyze opponent.", 30, 3, new ChargeAttr(), new StatusEffectAttr(StatusEffect.PARALYSIS)), new Move(Moves.BOUNCE, "Bounce", Type.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, -1, "Springs up on first turn, attacks on second. May paralyze opponent.", 30, 3,
new ChargeAttr(ChargeAnim.BOUNCE_CHARGING, 'sprang up!', BattleTagType.FLYING), new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.MUD_SHOT, "Mud Shot", Type.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 35, "Lowers opponent's Speed.", 100, 3, new StatChangeAttr(BattleStat.SPD, -1)), new Move(Moves.MUD_SHOT, "Mud Shot", Type.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 35, "Lowers opponent's Speed.", 100, 3, new StatChangeAttr(BattleStat.SPD, -1)),
new Move(Moves.POISON_TAIL, "Poison Tail", Type.POISON, MoveCategory.PHYSICAL, 50, 100, 25, 26, "High critical hit ratio. May poison opponent.", 10, 3, new HighCritAttr(), new StatusEffectAttr(StatusEffect.POISON)), new Move(Moves.POISON_TAIL, "Poison Tail", Type.POISON, MoveCategory.PHYSICAL, 50, 100, 25, 26, "High critical hit ratio. May poison opponent.", 10, 3, new HighCritAttr(), new StatusEffectAttr(StatusEffect.POISON)),
new Move(Moves.COVET, "Covet", Type.NORMAL, MoveCategory.PHYSICAL, 60, 100, 25, -1, "Opponent's item is stolen by the user.", -1, 3), new Move(Moves.COVET, "Covet", Type.NORMAL, MoveCategory.PHYSICAL, 60, 100, 25, -1, "Opponent's item is stolen by the user.", -1, 3),
@ -1131,7 +1189,8 @@ export const allMoves = [
new Move(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 Move(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 Move(Moves.SHOCK_WAVE, "Shock Wave", Type.ELECTRIC, MoveCategory.SPECIAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3), new Move(Moves.SHOCK_WAVE, "Shock Wave", Type.ELECTRIC, MoveCategory.SPECIAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3),
new Move(Moves.WATER_PULSE, "Water Pulse", Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 11, "May confuse opponent.", 20, 3), new Move(Moves.WATER_PULSE, "Water Pulse", Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 11, "May confuse opponent.", 20, 3),
new Move(Moves.DOOM_DESIRE, "Doom Desire", Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, "Damage occurs 2 turns later.", -1, 3), new Move(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 Move(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)), new Move(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)),
new Move(Moves.ROOST, "Roost", Type.FLYING, MoveCategory.STATUS, -1, -1, 5, -1, "User recovers half of its max HP and loses the Flying type temporarily.", -1, 4), new Move(Moves.ROOST, "Roost", Type.FLYING, MoveCategory.STATUS, -1, -1, 5, -1, "User recovers half of its max HP and loses the Flying type temporarily.", -1, 4),
new Move(Moves.GRAVITY, "Gravity", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 5, -1, "Prevents moves like Fly and Bounce and the Ability Levitate for 5 turns.", -1, 4), new Move(Moves.GRAVITY, "Gravity", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 5, -1, "Prevents moves like Fly and Bounce and the Ability Levitate for 5 turns.", -1, 4),
@ -1249,7 +1308,8 @@ export const allMoves = [
new Move(Moves.SEED_FLARE, "Seed Flare", Type.GRASS, MoveCategory.SPECIAL, 120, 85, 5, -1, "May lower opponent's Special Defense.", 40, 4, new StatChangeAttr(BattleStat.SPDEF, -1)), new Move(Moves.SEED_FLARE, "Seed Flare", Type.GRASS, MoveCategory.SPECIAL, 120, 85, 5, -1, "May lower opponent's Special Defense.", 40, 4, new StatChangeAttr(BattleStat.SPDEF, -1)),
new Move(Moves.OMINOUS_WIND, "Ominous Wind", Type.GHOST, MoveCategory.SPECIAL, 60, 100, 5, -1, "May raise all user's stats at once.", 10, 4, new Move(Moves.OMINOUS_WIND, "Ominous Wind", Type.GHOST, MoveCategory.SPECIAL, 60, 100, 5, -1, "May raise all user's stats at once.", 10, 4,
new StatChangeAttr([ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)), new StatChangeAttr([ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)),
new Move(Moves.SHADOW_FORCE, "Shadow Force", Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, "Disappears on first turn, attacks on second. Can strike through Protect/Detect.", -1, 4), new Move(Moves.SHADOW_FORCE, "Shadow Force", Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, "Disappears on first turn, attacks on second. Can strike through Protect/Detect.", -1, 4,
new ChargeAttr(ChargeAnim.SHADOW_FORCE_CHARGING, 'vanished\ninstantly!')),
new Move(Moves.HONE_CLAWS, "Hone Claws", Type.DARK, MoveCategory.STATUS, -1, -1, 15, -1, "Raises user's Attack and Accuracy.", -1, 5, new Move(Moves.HONE_CLAWS, "Hone Claws", Type.DARK, MoveCategory.STATUS, -1, -1, 15, -1, "Raises user's Attack and Accuracy.", -1, 5,
new StatChangeAttr([ BattleStat.ATK, BattleStat.ACC ], 1, true)), new StatChangeAttr([ BattleStat.ATK, BattleStat.ACC ], 1, true)),
new Move(Moves.WIDE_GUARD, "Wide Guard", Type.ROCK, MoveCategory.STATUS, -1, -1, 10, -1, "Protects the user's team from multi-target attacks.", -1, 5), new Move(Moves.WIDE_GUARD, "Wide Guard", Type.ROCK, MoveCategory.STATUS, -1, -1, 10, -1, "Protects the user's team from multi-target attacks.", -1, 5),
@ -1262,7 +1322,7 @@ export const allMoves = [
new Move(Moves.RAGE_POWDER, "Rage Powder", Type.BUG, MoveCategory.STATUS, -1, -1, 20, -1, "Forces attacks to hit user, not team-mates.", -1, 5), new Move(Moves.RAGE_POWDER, "Rage Powder", Type.BUG, MoveCategory.STATUS, -1, -1, 20, -1, "Forces attacks to hit user, not team-mates.", -1, 5),
new Move(Moves.TELEKINESIS, "Telekinesis", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 15, -1, "Ignores opponent's Evasiveness for three turns, add Ground immunity.", -1, 5), new Move(Moves.TELEKINESIS, "Telekinesis", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 15, -1, "Ignores opponent's Evasiveness for three turns, add Ground immunity.", -1, 5),
new Move(Moves.MAGIC_ROOM, "Magic Room", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 10, -1, "Suppresses the effects of held items for five turns.", -1, 5), new Move(Moves.MAGIC_ROOM, "Magic Room", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 10, -1, "Suppresses the effects of held items for five turns.", -1, 5),
new Move(Moves.SMACK_DOWN, "Smack Down", Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, -1, "Makes Flying-type Pokémon vulnerable to Ground moves.", 100, 5), new Move(Moves.SMACK_DOWN, "Smack Down", Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, -1, "Makes Flying-type Pokémon vulnerable to Ground moves.", 100, 5), // TODO, logic with fly
new Move(Moves.STORM_THROW, "Storm Throw", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "Always results in a critical hit.", 100, 5), // TODO new Move(Moves.STORM_THROW, "Storm Throw", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "Always results in a critical hit.", 100, 5), // TODO
new Move(Moves.FLAME_BURST, "Flame Burst", Type.FIRE, MoveCategory.SPECIAL, 70, 100, 15, -1, "May also injure nearby Pokémon.", -1, 5), new Move(Moves.FLAME_BURST, "Flame Burst", Type.FIRE, MoveCategory.SPECIAL, 70, 100, 15, -1, "May also injure nearby Pokémon.", -1, 5),
new Move(Moves.SLUDGE_WAVE, "Sludge Wave", Type.POISON, MoveCategory.SPECIAL, 95, 100, 10, -1, "May poison opponent.", 10, 5, new StatusEffectAttr(StatusEffect.POISON)), new Move(Moves.SLUDGE_WAVE, "Sludge Wave", Type.POISON, MoveCategory.SPECIAL, 95, 100, 10, -1, "May poison opponent.", 10, 5, new StatusEffectAttr(StatusEffect.POISON)),
@ -1293,7 +1353,8 @@ export const allMoves = [
new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], 2, true), new StatChangeAttr([ BattleStat.DEF, BattleStat.SPDEF ], -1, true)), new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], 2, true), new StatChangeAttr([ BattleStat.DEF, BattleStat.SPDEF ], -1, true)),
new Move(Moves.HEAL_PULSE, "Heal Pulse", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 10, -1, "Restores half the target's max HP.", -1, 5), new Move(Moves.HEAL_PULSE, "Heal Pulse", Type.PSYCHIC, MoveCategory.STATUS, -1, -1, 10, -1, "Restores half the target's max HP.", -1, 5),
new Move(Moves.HEX, "Hex", Type.GHOST, MoveCategory.SPECIAL, 65, 100, 10, 29, "Inflicts more damage if the target has a status condition.", -1, 5), new Move(Moves.HEX, "Hex", Type.GHOST, MoveCategory.SPECIAL, 65, 100, 10, 29, "Inflicts more damage if the target has a status condition.", -1, 5),
new Move(Moves.SKY_DROP, "Sky Drop", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "Takes opponent into the air on first turn, drops them on second turn.", -1, 5), new Move(Moves.SKY_DROP, "Sky Drop", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 10, -1, "Takes opponent into the air on first turn, drops them on second turn.", -1, 5,
new ChargeAttr(ChargeAnim.SKY_DROP_CHARGING, 'took {TARGET}\ninto the sky!', BattleTagType.FLYING)), // TODO: Add 2nd turn message
new Move(Moves.SHIFT_GEAR, "Shift Gear", Type.STEEL, MoveCategory.STATUS, -1, -1, 10, -1, "Raises user's Attack and sharply raises Speed.", -1, 5, new Move(Moves.SHIFT_GEAR, "Shift Gear", Type.STEEL, MoveCategory.STATUS, -1, -1, 10, -1, "Raises user's Attack and sharply raises Speed.", -1, 5,
new StatChangeAttr(BattleStat.ATK, 1, true), new StatChangeAttr(BattleStat.SPD, 2, true)), new StatChangeAttr(BattleStat.ATK, 1, true), new StatChangeAttr(BattleStat.SPD, 2, true)),
new Move(Moves.CIRCLE_THROW, "Circle Throw", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 5), new Move(Moves.CIRCLE_THROW, "Circle Throw", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 5),
@ -1342,7 +1403,8 @@ export const allMoves = [
new Move(Moves.BLUE_FLARE, "Blue Flare", Type.FIRE, MoveCategory.SPECIAL, 130, 85, 5, -1, "May burn opponent.", 20, 5, new StatusEffectAttr(StatusEffect.BURN)), new Move(Moves.BLUE_FLARE, "Blue Flare", Type.FIRE, MoveCategory.SPECIAL, 130, 85, 5, -1, "May burn opponent.", 20, 5, new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.FIERY_DANCE, "Fiery Dance", Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, -1, "May raise user's Special Attack.", 50, 5, new StatChangeAttr(BattleStat.SPATK, 1, true)), new Move(Moves.FIERY_DANCE, "Fiery Dance", Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, -1, "May raise user's Special Attack.", 50, 5, new StatChangeAttr(BattleStat.SPATK, 1, true)),
new Move(Moves.FREEZE_SHOCK, "Freeze Shock", Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May paralyze opponent.", 30, 5, new StatusEffectAttr(StatusEffect.PARALYSIS)), new Move(Moves.FREEZE_SHOCK, "Freeze Shock", Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May paralyze opponent.", 30, 5, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new Move(Moves.ICE_BURN, "Ice Burn", Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May burn opponent.", 30, 5, new StatusEffectAttr(StatusEffect.BURN)), new Move(Moves.ICE_BURN, "Ice Burn", Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, -1, "Charges on first turn, attacks on second. May burn opponent.", 30, 5,
new ChargeAttr(ChargeAnim.ICE_BURN_CHARGING, 'became cloaked\nin freezing air!'), new StatusEffectAttr(StatusEffect.BURN)),
new Move(Moves.SNARL, "Snarl", Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 30, "Lowers opponent's Special Attack.", 100, 5, new StatChangeAttr(BattleStat.SPATK, -1)), new Move(Moves.SNARL, "Snarl", Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 30, "Lowers opponent's Special Attack.", 100, 5, new StatChangeAttr(BattleStat.SPATK, -1)),
new Move(Moves.ICICLE_CRASH, "Icicle Crash", Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, -1, "May cause flinching.", 30, 5, new FlinchAttr()), new Move(Moves.ICICLE_CRASH, "Icicle Crash", Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, -1, "May cause flinching.", 30, 5, new FlinchAttr()),
new Move(Moves.V_CREATE, "V-create", Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, "Lowers user's Defense, Special Defense and Speed.", 100, 5, new Move(Moves.V_CREATE, "V-create", Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, "Lowers user's Defense, Special Defense and Speed.", 100, 5,

View File

@ -1,7 +1,7 @@
import Phaser from 'phaser'; import Phaser from 'phaser';
import BattleScene from './battle-scene'; import BattleScene from './battle-scene';
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './battle-info'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './battle-info';
import { default as Move, allMoves, MoveCategory, Moves, StatChangeAttr, applyMoveAttrs, HighCritAttr } from './move'; import { default as Move, allMoves, MoveCategory, Moves, StatChangeAttr, applyMoveAttrs, HighCritAttr, HitsTagAttr } from './move';
import { pokemonLevelMoves } from './pokemon-level-moves'; import { pokemonLevelMoves } from './pokemon-level-moves';
import { default as PokemonSpecies, getPokemonSpecies } from './pokemon-species'; import { default as PokemonSpecies, getPokemonSpecies } from './pokemon-species';
import * as Utils from './utils'; import * as Utils from './utils';
@ -17,6 +17,7 @@ import { tmSpecies } from './tms';
import { pokemonEvolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './pokemon-evolutions'; import { pokemonEvolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './pokemon-evolutions';
import { MessagePhase, StatChangePhase } from './battle-phases'; import { MessagePhase, StatChangePhase } from './battle-phases';
import { BattleStat } from './battle-stat'; import { BattleStat } from './battle-stat';
import { BattleTag, BattleTagLapseType, BattleTagType } from './battle-tag';
export default abstract class Pokemon extends Phaser.GameObjects.Container { export default abstract class Pokemon extends Phaser.GameObjects.Container {
public id: integer; public id: integer;
@ -409,93 +410,138 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
} }
apply(source: Pokemon, battlerMove: PokemonMove, callback?: Function) { apply(source: Pokemon, battlerMove: PokemonMove): Promise<MoveResult> {
const battleScene = this.scene as BattleScene; return new Promise(resolve => {
let result: integer; const battleScene = this.scene as BattleScene;
let success = false; let result: MoveResult = MoveResult.STATUS;
const move = battlerMove.getMove(); let success = false;
const moveCategory = move.category; const move = battlerMove.getMove();
let damage = 0; const moveCategory = move.category;
switch (moveCategory) { let damage = 0;
case MoveCategory.PHYSICAL: switch (moveCategory) {
case MoveCategory.SPECIAL: case MoveCategory.PHYSICAL:
const isPhysical = moveCategory === MoveCategory.PHYSICAL; case MoveCategory.SPECIAL:
const critChance = new Utils.IntegerHolder(16); const isPhysical = moveCategory === MoveCategory.PHYSICAL;
applyMoveAttrs(HighCritAttr, this.scene as BattleScene, source, this, move, critChance); const critChance = new Utils.IntegerHolder(16);
const isCritical = Utils.randInt(critChance.value) === 0; applyMoveAttrs(HighCritAttr, this.scene as BattleScene, source, this, move, critChance);
const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK); const isCritical = Utils.randInt(critChance.value) === 0;
const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF); const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK);
const stabMultiplier = source.species.type1 === move.type || (source.species.type2 > -1 && source.species.type2 === move.type) ? 1.5 : 1; const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF);
const typeMultiplier = getTypeDamageMultiplier(move.type, this.species.type1) * (this.species.type2 > -1 ? getTypeDamageMultiplier(move.type, this.species.type2) : 1); const stabMultiplier = source.species.type1 === move.type || (source.species.type2 > -1 && source.species.type2 === move.type) ? 1.5 : 1;
const criticalMultiplier = isCritical ? 2 : 1; const typeMultiplier = getTypeDamageMultiplier(move.type, this.species.type1) * (this.species.type2 > -1 ? getTypeDamageMultiplier(move.type, this.species.type2) : 1);
damage = Math.ceil(((((2 * source.level / 5 + 2) * move.power * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier; const criticalMultiplier = isCritical ? 2 : 1;
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) damage = Math.ceil(((((2 * source.level / 5 + 2) * move.power * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier;
damage = Math.floor(damage / 2); if (isPhysical && source.status && source.status.effect === StatusEffect.BURN)
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef); damage = Math.floor(damage / 2);
if (damage) { move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
this.hp = Math.max(this.hp - damage, 0); if (this.getTag(hta.tagType)) {
if (isCritical) console.log('ye');
battleScene.unshiftPhase(new MessagePhase(battleScene, 'A critical hit!')); damage *= 2;
}
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');
battleScene.unshiftPhase(new MessagePhase(battleScene, 'It\'s super effective!'));
success = true;
break;
case MoveResult.NOT_VERY_EFFECTIVE:
this.scene.sound.play('hit_weak');
battleScene.unshiftPhase(new MessagePhase(battleScene, 'It\'s not very effective!'))
success = true;
break;
case MoveResult.NO_EFFECT:
battleScene.unshiftPhase(new MessagePhase(battleScene, `It doesn\'t affect ${this.name}!`))
success = true;
break;
case MoveCategory.STATUS:
result = MoveResult.OTHER;
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(() => {
if (callback)
callback();
});
} }
});
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
if (damage) {
this.hp = Math.max(this.hp - damage, 0);
if (isCritical)
battleScene.unshiftPhase(new MessagePhase(battleScene, 'A critical hit!'));
} }
}); if (typeMultiplier >= 2)
} else { result = MoveResult.SUPER_EFFECTIVE;
this.battleInfo.updateInfo(this).then(() => { else if (typeMultiplier >= 1)
if (callback) result = MoveResult.EFFECTIVE;
callback(); 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');
battleScene.unshiftPhase(new MessagePhase(battleScene, 'It\'s super effective!'));
success = true;
break;
case MoveResult.NOT_VERY_EFFECTIVE:
this.scene.sound.play('hit_weak');
battleScene.unshiftPhase(new MessagePhase(battleScene, 'It\'s not very effective!'))
success = true;
break;
case MoveResult.NO_EFFECT:
battleScene.unshiftPhase(new MessagePhase(battleScene, `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));
}
} else
resolve(result);
});
}
addTag(tagType: BattleTagType, lapseType: BattleTagLapseType, turnCount?: integer): boolean {
if (this.getTag(tagType))
return false;
const newTag = new BattleTag(tagType, lapseType || BattleTagLapseType.FAINT, turnCount || 1);
this.summonData.tags.push(newTag);
if (newTag.isHidden())
this.setVisible(false);
}
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));
}
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));
}
lapseTags(lapseType: BattleTagLapseType) {
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 } else
callback(); this.setVisible(visible);
}
getLastXMoves(turnCount?: integer): TurnMove[] {
const moveHistory = this.summonData.moveHistory;
return moveHistory.slice(Math.max(moveHistory.length - (turnCount || 1), 0), moveHistory.length).reverse();
} }
cry(soundConfig?: Phaser.Types.Sound.SoundConfig): integer { cry(soundConfig?: Phaser.Types.Sound.SoundConfig): integer {
@ -731,6 +777,12 @@ export class EnemyPokemon extends Pokemon {
} }
getNextMove(): PokemonMove { getNextMove(): PokemonMove {
const queuedMove = this.summonData.moveQueue.length
? this.moveset.find(m => m.moveId === this.summonData.moveQueue[0].move)
: null;
if (queuedMove && (this.summonData.moveQueue[0].ignorePP || queuedMove.isUsable()))
return queuedMove;
const movePool = this.moveset.filter(m => m.isUsable()); const movePool = this.moveset.filter(m => m.isUsable());
if (movePool.length) { if (movePool.length) {
if (movePool.length === 1) if (movePool.length === 1)
@ -823,8 +875,22 @@ export class EnemyPokemon extends Pokemon {
} }
} }
export interface TurnMove {
move: Moves;
result: MoveResult;
}
export interface QueuedMove {
move: Moves;
ignorePP?: boolean;
}
export class PokemonSummonData { export class PokemonSummonData {
public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ]; public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ];
public moveHistory: TurnMove[] = [];
public moveQueue: QueuedMove[] = [];
public tags: BattleTag[] = [];
public charging: boolean;
public confusionTurns: integer; public confusionTurns: integer;
} }
@ -850,16 +916,19 @@ export enum MoveResult {
SUPER_EFFECTIVE, SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE, NOT_VERY_EFFECTIVE,
NO_EFFECT, NO_EFFECT,
STATUS,
FAILED,
MISSED,
OTHER OTHER
}; };
export class PokemonMove { export class PokemonMove {
public moveId: integer; public moveId: Moves;
public ppUsed: integer; public ppUsed: integer;
public ppUp: integer; public ppUp: integer;
public disableTurns: integer; public disableTurns: integer;
constructor(moveId: integer, ppUsed: integer, ppUp: integer) { constructor(moveId: Moves, ppUsed: integer, ppUp: integer) {
this.moveId = moveId; this.moveId = moveId;
this.ppUsed = ppUsed; this.ppUsed = ppUsed;
this.ppUp = ppUp; this.ppUp = ppUp;