Add confusion and frenzy move effects

pull/1/head
Flashfyre 2023-04-15 01:32:16 -04:00
parent b11e391e7a
commit 656b6951b6
7 changed files with 450 additions and 217 deletions

View File

@ -332,7 +332,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
moveAnim.bgSprite.setScale(1.25); moveAnim.bgSprite.setScale(1.25);
moveAnim.bgSprite.setAlpha(0); moveAnim.bgSprite.setAlpha(0);
scene.field.add(moveAnim.bgSprite); scene.field.add(moveAnim.bgSprite);
scene.field.moveAbove(moveAnim.bgSprite, scene.arenaEnemy); scene.field.moveBelow(moveAnim.bgSprite, scene.getEnemyPokemon());
scene.tweens.add({ scene.tweens.add({
targets: moveAnim.bgSprite, targets: moveAnim.bgSprite,
@ -766,15 +766,15 @@ export class MoveAnim extends BattleAnim {
getAnim(): Anim { getAnim(): Anim {
return moveAnims.get(this.move) instanceof Anim return moveAnims.get(this.move) instanceof Anim
? moveAnims.get(this.move) as Anim ? moveAnims.get(this.move) as Anim
: moveAnims.get(this.move)[this.user instanceof PlayerPokemon ? 0 : 1] as Anim; : moveAnims.get(this.move)[this.user.isPlayer() ? 0 : 1] as Anim;
} }
isOppAnim(): boolean { isOppAnim(): boolean {
return this.user instanceof EnemyPokemon && Array.isArray(moveAnims.get(this.move)); return !this.user.isPlayer() && Array.isArray(moveAnims.get(this.move));
} }
isReverseCoords(): boolean { isReverseCoords(): boolean {
return this.user instanceof EnemyPokemon && !this.isOppAnim(); return !this.user.isPlayer() && !this.isOppAnim();
} }
getGraphicScale(): number { getGraphicScale(): number {
@ -799,7 +799,7 @@ export class MoveChargeAnim extends MoveAnim {
getAnim(): Anim { getAnim(): Anim {
return chargeAnims.get(this.chargeAnim) instanceof Anim return chargeAnims.get(this.chargeAnim) instanceof Anim
? chargeAnims.get(this.chargeAnim) as Anim ? chargeAnims.get(this.chargeAnim) as Anim
: chargeAnims.get(this.chargeAnim)[this.user instanceof PlayerPokemon ? 0 : 1] as Anim; : chargeAnims.get(this.chargeAnim)[this.user.isPlayer() ? 0 : 1] as Anim;
} }
} }

View File

@ -1,5 +1,5 @@
import BattleScene from "./battle-scene"; import BattleScene from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult } from "./pokemon"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult } from "./pokemon";
import * as Utils from './utils'; import * as Utils from './utils';
import { allMoves, applyMoveAttrs, ChargeAttr, HitsTagAttr, MissEffectAttr, MoveCategory, MoveEffectAttr, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr } 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';
@ -8,7 +8,7 @@ import { Stat } from "./pokemon-stat";
import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, HitHealModifier } from "./modifier"; import { ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, HitHealModifier } 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 { ChargeAnim, CommonAnim, CommonBattleAnim, MoveAnim, chargeAnims, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { StatusEffect, getStatusEffectActivationText, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./status-effect"; import { 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,7 +19,8 @@ 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"; import { BattleTagLapseType, BattleTagType, HideSpriteTag as HiddenTag } from "./battle-tag";
import { getPokemonMessage } from "./messages";
export class SelectStarterPhase extends BattlePhase { export class SelectStarterPhase extends BattlePhase {
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
@ -385,6 +386,11 @@ export class CheckSwitchPhase extends BattlePhase {
return; return;
} }
if (this.scene.getPlayerPokemon().getTag(BattleTagType.FRENZY)) {
super.end();
return;
}
this.scene.ui.showText('Will you switch\nPOKéMON?', null, () => { this.scene.ui.showText('Will you switch\nPOKéMON?', null, () => {
this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.CONFIRM, () => {
this.scene.unshiftPhase(new SwitchPhase(this.scene, false, true)); this.scene.unshiftPhase(new SwitchPhase(this.scene, false, true));
@ -551,7 +557,7 @@ export class CommonAnimPhase extends PokemonPhase {
} }
} }
abstract class MovePhase extends BattlePhase { export abstract class MovePhase extends BattlePhase {
protected pokemon: Pokemon; protected pokemon: Pokemon;
protected move: PokemonMove; protected move: PokemonMove;
protected cancelled: boolean; protected cancelled: boolean;
@ -570,20 +576,24 @@ abstract class MovePhase extends BattlePhase {
return !!this.pokemon.hp; return !!this.pokemon.hp;
} }
cancel(): void {
this.cancelled = true;
}
start() { start() {
super.start(); super.start();
this.pokemon.lapseTags(BattleTagLapseType.MOVE);
const doMove = () => { const doMove = () => {
if (this.cancelled) { if (this.cancelled) {
this.end(); this.end();
return; return;
} }
if (!this.move) this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500));
console.log(this.pokemon.moveset); this.scene.unshiftPhase(this.getEffectPhase());
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++; this.move.ppUsed++;
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.pokemon instanceof EnemyPokemon ? 'Foe ' : ''}${this.pokemon.name} used\n${this.move.getName()}!`, 500));
this.scene.unshiftPhase(this.getEffectPhase());
this.end(); this.end();
}; };
@ -616,12 +626,13 @@ abstract class MovePhase extends BattlePhase {
} }
if (activated) { if (activated) {
this.scene.unshiftPhase(new MessagePhase(this.scene, this.scene.unshiftPhase(new MessagePhase(this.scene,
`${this.pokemon instanceof PlayerPokemon ? '' : 'Foe '}${this.pokemon.name}${getStatusEffectActivationText(this.pokemon.status.effect)}`)); getPokemonMessage(this.pokemon, getStatusEffectActivationText(this.pokemon.status.effect))));
new CommonBattleAnim(CommonAnim.POISON + (this.pokemon.status.effect - 1), this.pokemon).play(this.scene, () => doMove()); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.isPlayer(), CommonAnim.POISON + (this.pokemon.status.effect - 1)));
doMove();
} else { } else {
if (healed) { if (healed) {
this.scene.unshiftPhase(new MessagePhase(this.scene, this.scene.unshiftPhase(new MessagePhase(this.scene,
`${this.pokemon instanceof PlayerPokemon ? '' : 'Foe '}${this.pokemon.name}${getStatusEffectHealText(this.pokemon.status.effect)}`)); getPokemonMessage(this.pokemon, getStatusEffectHealText(this.pokemon.status.effect))));
this.pokemon.resetStatus(); this.pokemon.resetStatus();
this.pokemon.updateInfo(true); this.pokemon.updateInfo(true);
} }
@ -678,7 +689,7 @@ abstract class MoveEffectPhase extends PokemonPhase {
return; return;
} }
user.lapseTags(BattleTagLapseType.MOVE); user.lapseTags(BattleTagLapseType.MOVE_EFFECT);
if (user.turnData.hitsLeft === undefined) { if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1); const hitCount = new Utils.IntegerHolder(1);
@ -688,7 +699,7 @@ abstract class MoveEffectPhase extends PokemonPhase {
} }
if (!this.hitCheck()) { if (!this.hitCheck()) {
this.scene.unshiftPhase(new MessagePhase(this.scene, `${!this.player ? 'Foe ' : ''}${user.name}'s\nattack missed!`)); this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(user, '\'s\nattack missed!')));
user.summonData.moveHistory.push({ move: this.move.moveId, result: MoveResult.MISSED }); user.summonData.moveHistory.push({ move: this.move.moveId, result: MoveResult.MISSED });
applyMoveAttrs(MissEffectAttr, this.scene, user, target, this.move.getMove()); applyMoveAttrs(MissEffectAttr, this.scene, user, target, this.move.getMove());
this.end(); this.end();
@ -696,25 +707,22 @@ abstract class MoveEffectPhase extends PokemonPhase {
} }
new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () => { new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () => {
target.apply(user, this.move).then(result => { const result = target.apply(user, this.move);
++user.turnData.hitCount; ++user.turnData.hitCount;
user.summonData.moveHistory.push({ move: this.move.moveId, result: result }); user.summonData.moveHistory.push({ move: this.move.moveId, result: result });
if (user.hp <= 0) { if (user.hp <= 0) {
this.scene.pushPhase(new FaintPhase(this.scene, this.player)); this.scene.pushPhase(new FaintPhase(this.scene, this.player));
target.resetBattleSummonData(); target.resetBattleSummonData();
} }
if (target.hp <= 0) { if (target.hp <= 0) {
this.scene.pushPhase(new FaintPhase(this.scene, !this.player)); this.scene.pushPhase(new FaintPhase(this.scene, !this.player));
this.getUserPokemon().resetBattleSummonData(); this.getUserPokemon().resetBattleSummonData();
} }
if (target.hp) { applyMoveAttrs(MoveEffectAttr, this.scene, user, target, this.move.getMove());
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
// 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)
if (!this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) applyMoveAttrs(MoveHitEffectAttr, this.scene, user, target, this.move.getMove());
applyMoveAttrs(MoveHitEffectAttr, this.scene, user, target, this.move.getMove()); this.end();
}
this.end();
});
}); });
}); });
} }
@ -736,7 +744,7 @@ abstract class MoveEffectPhase extends PokemonPhase {
hitCheck(): boolean { hitCheck(): boolean {
// Check if not self targeting for this // Check if not self targeting for this
const hiddenTag = this.getTargetPokemon().getTag(t => t.isHidden()); const hiddenTag = this.getTargetPokemon().getTag(HiddenTag);
if (hiddenTag) { if (hiddenTag) {
if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length)
return false; return false;
@ -847,7 +855,8 @@ export class StatChangePhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, stats: BattleStat[], levels: integer) { constructor(scene: BattleScene, player: boolean, stats: BattleStat[], levels: integer) {
super(scene, player); super(scene, player);
this.stats = stats; const allStats = Utils.getEnumValues(BattleStat);
this.stats = stats.map(s => s !== BattleStat.RAND ? s : allStats[Utils.randInt(BattleStat.SPD + 1)]);
this.levels = levels; this.levels = levels;
} }
@ -915,7 +924,7 @@ export class StatChangePhase extends PokemonPhase {
const messages: string[] = []; const messages: string[] = [];
for (let s = 0; s < this.stats.length; s++) for (let s = 0; s < this.stats.length; s++)
messages.push(`${this.player ? '' : 'Foe '}${this.getPokemon().name}'s ${getBattleStatName(this.stats[s])} ${getBattleStatLevelChangeDescription(Math.abs(relLevels[s]), this.levels >= 1)}!`); messages.push(getPokemonMessage(this.getPokemon(), `'s ${getBattleStatName(this.stats[s])} ${getBattleStatLevelChangeDescription(Math.abs(relLevels[s]), this.levels >= 1)}!`));
return messages; return messages;
} }
} }
@ -935,7 +944,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
if (pokemon.trySetStatus(this.statusEffect)) { if (pokemon.trySetStatus(this.statusEffect)) {
pokemon.updateInfo(true); pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => { new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => {
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.player ? '' : 'Foe '}${pokemon.name}${getStatusEffectObtainText(this.statusEffect)}`)); this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect))));
if (pokemon.status.isPostTurn()) if (pokemon.status.isPostTurn())
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.player)); this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.player));
this.end(); this.end();
@ -943,7 +952,7 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
return; return;
} }
} else if (pokemon.status.effect === this.statusEffect) } else if (pokemon.status.effect === this.statusEffect)
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.player ? '' : 'Foe '}${pokemon.name}${getStatusEffectOverlapText(this.statusEffect)}`)); this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(pokemon, getStatusEffectOverlapText(this.statusEffect))));
this.end(); this.end();
} }
} }
@ -959,7 +968,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
pokemon.status.incrementTurn(); pokemon.status.incrementTurn();
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => { new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => {
this.scene.unshiftPhase(new MessagePhase(this.scene, this.scene.unshiftPhase(new MessagePhase(this.scene,
`${pokemon instanceof PlayerPokemon ? '' : 'Foe '}${pokemon.name}${getStatusEffectActivationText(pokemon.status.effect)}`)); getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect))));
switch (pokemon.status.effect) { switch (pokemon.status.effect) {
case StatusEffect.POISON: case StatusEffect.POISON:
case StatusEffect.BURN: case StatusEffect.BURN:
@ -1000,6 +1009,44 @@ export class MessagePhase extends BattlePhase {
} }
} }
export class DamagePhase extends PokemonPhase {
private damage: integer;
private damageResult: DamageResult;
constructor(scene: BattleScene, player: boolean, damageResult?: DamageResult) {
super(scene, player);
this.damageResult = damageResult || MoveResult.EFFECTIVE;
}
start() {
super.start();
switch (this.damageResult) {
case MoveResult.EFFECTIVE:
this.scene.sound.play('hit');
break;
case MoveResult.SUPER_EFFECTIVE:
this.scene.sound.play('hit_strong');
break;
case MoveResult.NOT_VERY_EFFECTIVE:
this.scene.sound.play('hit_weak');
break;
}
const flashTimer = this.scene.time.addEvent({
delay: 100,
repeat: 5,
startAt: 200,
callback: () => {
this.getPokemon().getSprite().setVisible(flashTimer.repeatCount % 2 === 0);
if (!flashTimer.repeatCount)
this.getPokemon().updateInfo().then(() => this.end());
}
});
}
}
export class FaintPhase extends PokemonPhase { export class FaintPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean) { constructor(scene: BattleScene, player: boolean) {
super(scene, player); super(scene, player);
@ -1008,13 +1055,12 @@ export class FaintPhase extends PokemonPhase {
start() { start() {
super.start(); super.start();
if (this.player) { this.scene.unshiftPhase(new MessagePhase(this.scene, getPokemonMessage(this.getPokemon(), ' fainted!'), null, true));
this.scene.unshiftPhase(new MessagePhase(this.scene, `${this.getPokemon().name} fainted!`, null, true));
if (this.player)
this.scene.unshiftPhase(new SwitchPhase(this.scene, true, false)); this.scene.unshiftPhase(new SwitchPhase(this.scene, true, false));
} else { else
this.scene.unshiftPhase(new MessagePhase(this.scene, `Foe ${this.getPokemon().name} fainted!`, null, true));
this.scene.unshiftPhase(new VictoryPhase(this.scene)); this.scene.unshiftPhase(new VictoryPhase(this.scene));
}
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
@ -1031,7 +1077,7 @@ export class FaintPhase extends PokemonPhase {
onComplete: () => { onComplete: () => {
pokemon.setVisible(false); pokemon.setVisible(false);
pokemon.y -= 150; pokemon.y -= 150;
if (pokemon instanceof PlayerPokemon) if (pokemon.isPlayer())
this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon);
this.scene.field.remove(pokemon); this.scene.field.remove(pokemon);
this.end(); this.end();

View File

@ -1,5 +1,14 @@
import { CommonAnim } from "./battle-anims";
import { CommonAnimPhase, DamagePhase, MessagePhase, MovePhase } from "./battle-phases";
import { getPokemonMessage } from "./messages";
import Pokemon from "./pokemon";
import { Stat } from "./pokemon-stat";
import * as Utils from "./utils";
export enum BattleTagType { export enum BattleTagType {
NONE, NONE,
CONFUSED,
FRENZY,
FLYING, FLYING,
UNDERGROUND UNDERGROUND
} }
@ -7,7 +16,9 @@ export enum BattleTagType {
export enum BattleTagLapseType { export enum BattleTagLapseType {
FAINT, FAINT,
MOVE, MOVE,
TURN_END MOVE_EFFECT,
TURN_END,
CUSTOM
} }
export class BattleTag { export class BattleTag {
@ -21,13 +32,98 @@ export class BattleTag {
this.turnCount = turnCount; this.turnCount = turnCount;
} }
isHidden() { onAdd(pokemon: Pokemon): void { }
switch (this.tagType) {
case BattleTagType.FLYING:
case BattleTagType.UNDERGROUND:
return true;
}
return false; onRemove(pokemon: Pokemon): void { }
onOverlap(pokemon: Pokemon): void { }
lapse(pokemon: Pokemon): boolean {
return !!--this.turnCount;
}
}
export class PseudoStatusTag extends BattleTag {
constructor(tagType: BattleTagType, turnCount: integer) {
super(tagType, BattleTagLapseType.MOVE, turnCount);
}
}
export class ConfusedTag extends PseudoStatusTag {
constructor(tagType: BattleTagType, turnCount: integer) {
super(tagType, turnCount);
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION));
pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' became\nconfused!')));
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' snapped\nout of confusion!')));
}
onOverlap(pokemon: Pokemon): void {
super.onOverlap(pokemon);
pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is\nalready confused!')));
}
lapse(pokemon: Pokemon): boolean {
const ret = super.lapse(pokemon);
if (ret) {
pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, getPokemonMessage(pokemon, ' is\nconfused!')));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION));
if (Utils.randInt(2)) {
const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * ((Utils.randInt(15) + 85) / 100));
pokemon.hp = Math.max(pokemon.hp - damage, 0);
pokemon.scene.unshiftPhase(new MessagePhase(pokemon.scene, 'It hurt itself in its\nconfusion!'));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), damage));
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
}
}
return ret;
}
}
export class HideSpriteTag extends BattleTag {
constructor(tagType: BattleTagType, turnCount: integer) {
super(tagType, BattleTagLapseType.MOVE_EFFECT, turnCount);
}
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
pokemon.setVisible(false);
}
onRemove(pokemon: Pokemon): void {
// Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible
pokemon.scene.tweens.addCounter({
duration: 2,
useFrames: true,
onComplete: () => pokemon.setVisible(true)
});
}
}
export function getBattleTag(tagType: BattleTagType, turnCount: integer): BattleTag {
switch (tagType) {
case BattleTagType.CONFUSED:
return new ConfusedTag(tagType, turnCount);
case BattleTagType.FLYING:
case BattleTagType.UNDERGROUND:
return new HideSpriteTag(tagType, turnCount);
default:
return new BattleTag(tagType, BattleTagLapseType.CUSTOM, turnCount);
} }
} }

5
src/messages.ts Normal file
View File

@ -0,0 +1,5 @@
import Pokemon, { EnemyPokemon } from "./pokemon";
export function getPokemonMessage(pokemon: Pokemon, content: string): string {
return `${!pokemon.isPlayer() ? 'Wild ' : ''}${pokemon.name}${content}`;
}

View File

@ -498,8 +498,8 @@ export function getModifierTypeOptionsForWave(waveIndex: integer, count: integer
function getNewModifierTypeOption(party: PlayerPokemon[], tier?: ModifierTier, upgrade?: boolean): ModifierTypeOption { function getNewModifierTypeOption(party: PlayerPokemon[], tier?: ModifierTier, upgrade?: boolean): ModifierTypeOption {
const tierValue = Utils.randInt(256); const tierValue = Utils.randInt(256);
if (tier === undefined) { if (tier === undefined) {
tier = (tierValue >= 52 ? ModifierTier.COMMON : tierValue >= 8 ? ModifierTier.GREAT : tierValue >= 1 ? ModifierTier.ULTRA : ModifierTier.MASTER) + (upgrade ? 1 : 0);
upgrade = Utils.randInt(32) === 0; upgrade = Utils.randInt(32) === 0;
tier = (tierValue >= 52 ? ModifierTier.COMMON : tierValue >= 8 ? ModifierTier.GREAT : tierValue >= 1 ? ModifierTier.ULTRA : ModifierTier.MASTER) + (upgrade ? 1 : 0);
} }
const thresholds = Object.keys(modifierPoolThresholds[tier]); const thresholds = Object.keys(modifierPoolThresholds[tier]);
const totalWeight = parseInt(thresholds[thresholds.length - 1]); const totalWeight = parseInt(thresholds[thresholds.length - 1]);

View File

@ -3,10 +3,11 @@ import { EnemyMovePhase, MessagePhase, ObtainStatusEffectPhase, PlayerMovePhase,
import BattleScene from "./battle-scene"; import BattleScene from "./battle-scene";
import { BattleStat } from "./battle-stat"; import { BattleStat } from "./battle-stat";
import Pokemon, { EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "./pokemon"; import Pokemon, { EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "./pokemon";
import { BattleTagLapseType, BattleTagType } from "./battle-tag"; import { BattleTagType } from "./battle-tag";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import { Type } from "./type"; import { Type } from "./type";
import * as Utils from "./utils"; import * as Utils from "./utils";
import { getPokemonMessage } from "./messages";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -622,10 +623,6 @@ export enum Moves {
FUSION_BOLT FUSION_BOLT
}; };
const enum MoveEffectText {
BUT_IT_FAILED = 'But it failed!'
}
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 {
@ -635,6 +632,21 @@ export abstract class MoveAttr {
} }
export class MoveEffectAttr extends MoveAttr { export class MoveEffectAttr extends MoveAttr {
public selfTarget: boolean;
constructor(selfTarget?: boolean) {
super();
this.selfTarget = !!selfTarget;
}
canApply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]) {
return !!(this.selfTarget ? user.hp && !user.getTag(BattleTagType.FRENZY) : target.hp);
}
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]) {
return this.canApply(scene, user, target, move, args);
}
} }
export class MoveHitEffectAttr extends MoveAttr { export class MoveHitEffectAttr extends MoveAttr {
@ -702,10 +714,12 @@ class StatusEffectAttr extends MoveHitEffectAttr {
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const statusCheck = move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance; const statusCheck = move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance;
if (statusCheck) { if (statusCheck) {
if (!target.status || (target.status.effect === this.effect && move.chance < 0)) if (!target.status || (target.status.effect === this.effect && move.chance < 0)) {
scene.unshiftPhase(new ObtainStatusEffectPhase(scene, target instanceof PlayerPokemon, this.effect)); scene.unshiftPhase(new ObtainStatusEffectPhase(scene, target.isPlayer(), this.effect));
return true;
}
} }
return true; return false;
} }
} }
@ -740,9 +754,9 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
if (!lastMove.length || lastMove[0].move !== move.id || lastMove[0].result !== MoveResult.OTHER) { if (!lastMove.length || lastMove[0].move !== move.id || lastMove[0].result !== MoveResult.OTHER) {
(args[0] as Utils.BooleanHolder).value = true; (args[0] as Utils.BooleanHolder).value = true;
new MoveChargeAnim(this.chargeAnim, move.id, user, target).play(scene, () => { new MoveChargeAnim(this.chargeAnim, move.id, user, target).play(scene, () => {
scene.unshiftPhase(new MessagePhase(scene, `${user instanceof EnemyPokemon ? 'Foe ' : ''}${user.name} ${this.chargeText}`)); scene.unshiftPhase(new MessagePhase(scene, getPokemonMessage(user, ` ${this.chargeText}`)));
if (this.tagType) if (this.tagType)
user.addTag(this.tagType, BattleTagLapseType.MOVE); user.addTag(this.tagType);
if (this.chargeEffect) if (this.chargeEffect)
applyMoveAttrs(MoveEffectAttr, scene, user, target, move); applyMoveAttrs(MoveEffectAttr, scene, user, target, move);
user.summonData.moveHistory.push({ move: move.id, result: MoveResult.OTHER }); user.summonData.moveHistory.push({ move: move.id, result: MoveResult.OTHER });
@ -750,7 +764,7 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
resolve(true); resolve(true);
}); });
} else } else
resolve(true); resolve(false);
}); });
} }
} }
@ -758,29 +772,36 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
export class StatChangeAttr extends MoveEffectAttr { export class StatChangeAttr extends MoveEffectAttr {
public stats: BattleStat[]; public stats: BattleStat[];
public levels: integer; public levels: integer;
public selfTarget: boolean;
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean) { constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean) {
super(); super(selfTarget);
this.stats = typeof(stats) === 'number' this.stats = typeof(stats) === 'number'
? [ stats as BattleStat ] ? [ stats as BattleStat ]
: stats as BattleStat[]; : stats as BattleStat[];
this.levels = levels; this.levels = levels;
this.selfTarget = !!selfTarget;
} }
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) if (!super.apply(scene, user, target, move, args))
scene.unshiftPhase(new StatChangePhase(scene, user instanceof PlayerPokemon === this.selfTarget, this.stats, this.levels)); return false;
return true;
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
scene.unshiftPhase(new StatChangePhase(scene, user.isPlayer() === this.selfTarget, this.stats, this.levels));
return true;
}
return false;
} }
} }
class FlinchAttr extends MoveHitEffectAttr { class FlinchAttr extends MoveHitEffectAttr {
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (Utils.randInt(100) < move.chance) if (Utils.randInt(100) < move.chance) {
target.turnData.flinched = true; target.turnData.flinched = true;
return true; return true;
}
return false;
} }
} }
@ -799,6 +820,73 @@ export class MissEffectAttr extends MoveAttr {
} }
} }
export class FrenzyAttr extends MoveEffectAttr {
constructor() {
super(true);
}
canApply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]) {
return !!(this.selfTarget ? user.hp : target.hp);
}
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(scene, user, target, move, args))
return false;
if (!user.summonData.moveQueue.length) {
if (!user.getTag(BattleTagType.FRENZY)) {
const turnCount = Utils.randInt(2) + 1;
new Array(turnCount).fill(null).map(() => user.summonData.moveQueue.push({ move: move.id, ignorePP: true }));
user.addTag(BattleTagType.FRENZY);
console.log('add frenzy');
} else {
applyMoveAttrs(AddTagAttr, scene, user, target, move, args);
user.lapseTag(BattleTagType.FRENZY);
console.log('remove frenzy');
}
return true;
}
return false;
}
}
const frenzyMissFunc = (scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) => {
while (user.summonData.moveQueue.length && user.summonData.moveQueue[0].move === move.id)
user.summonData.moveQueue.shift();
user.lapseTag(BattleTagType.FRENZY)
};
export class AddTagAttr extends MoveEffectAttr {
public tagType: BattleTagType;
public turnCount: integer;
constructor(tagType: BattleTagType, turnCount: integer, selfTarget?: boolean) {
super(selfTarget);
this.tagType = tagType;
this.turnCount = turnCount;
}
apply(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.apply(scene, user, target, move, args))
return false;
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
(this.selfTarget ? user : target).addTag(this.tagType, this.turnCount);
return true;
}
return false;
}
}
export class ConfuseAttr extends AddTagAttr {
constructor(selfTarget?: boolean) {
super(BattleTagType.CONFUSED, Utils.randInt(4, 1), selfTarget);
}
}
export class HitsTagAttr extends MoveAttr { export class HitsTagAttr extends MoveAttr {
public tagType: BattleTagType; public tagType: BattleTagType;
public doubleDamage: boolean; public doubleDamage: boolean;
@ -811,6 +899,8 @@ export class HitsTagAttr extends MoveAttr {
} }
} }
type AttrPredicate = (moveAttr: MoveAttr) => boolean;
export function applyMoveAttrs(attrType: { new(...args: any[]): MoveAttr }, scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> { export function applyMoveAttrs(attrType: { new(...args: any[]): MoveAttr }, scene: BattleScene, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const attrPromises: Promise<boolean>[] = []; const attrPromises: Promise<boolean>[] = [];
@ -830,7 +920,7 @@ class RandomMoveAttr extends OverrideMoveEffectAttr {
const moveIds = Utils.getEnumValues(Moves).filter(m => m !== move.id); const moveIds = Utils.getEnumValues(Moves).filter(m => m !== move.id);
const moveId = moveIds[Utils.randInt(moveIds.length)]; const moveId = moveIds[Utils.randInt(moveIds.length)];
user.summonData.moveQueue.push({ move: moveId, ignorePP: true }); user.summonData.moveQueue.push({ move: moveId, ignorePP: true });
scene.unshiftPhase(user instanceof PlayerPokemon ? new PlayerMovePhase(scene, user, new PokemonMove(moveId)) : new EnemyMovePhase(scene, user as EnemyPokemon, new PokemonMove(moveId))); scene.unshiftPhase(user.isPlayer() ? new PlayerMovePhase(scene, user as PlayerPokemon, new PokemonMove(moveId)) : new EnemyMovePhase(scene, user as EnemyPokemon, new PokemonMove(moveId)));
initMoveAnim(moveId).then(() => { initMoveAnim(moveId).then(() => {
loadMoveAnimAssets(scene, [ moveId ], true) loadMoveAnimAssets(scene, [ moveId ], true)
.then(() => resolve(true)); .then(() => resolve(true));
@ -879,7 +969,8 @@ export const allMoves = [
new AttackMove(Moves.BODY_SLAM, "Body Slam", Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 66, "May paralyze opponent.", 30, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), new AttackMove(Moves.BODY_SLAM, "Body Slam", Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 66, "May paralyze opponent.", 30, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new AttackMove(Moves.WRAP, "Wrap", Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1), new AttackMove(Moves.WRAP, "Wrap", Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 1),
new AttackMove(Moves.TAKE_DOWN, "Take Down", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 1, "User receives recoil damage.", -1, 1), new AttackMove(Moves.TAKE_DOWN, "Take Down", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 1, "User receives recoil damage.", -1, 1),
new AttackMove(Moves.THRASH, "Thrash", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1), new AttackMove(Moves.THRASH, "Thrash", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1,
new FrenzyAttr(), frenzyMissFunc, new ConfuseAttr(true)), // TODO: Update to still confuse if last hit misses
new AttackMove(Moves.DOUBLE_EDGE, "Double-Edge", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, "User receives recoil damage.", -1, 1), new AttackMove(Moves.DOUBLE_EDGE, "Double-Edge", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, "User receives recoil damage.", -1, 1),
new StatusMove(Moves.TAIL_WHIP, "Tail Whip", Type.NORMAL, 100, 30, -1, "Lowers opponent's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, -1)), new StatusMove(Moves.TAIL_WHIP, "Tail Whip", Type.NORMAL, 100, 30, -1, "Lowers opponent's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, -1)),
new AttackMove(Moves.POISON_STING, "Poison Sting", Type.POISON, MoveCategory.PHYSICAL, 15, 100, 35, -1, "May poison the opponent.", 30, 1, new StatusEffectAttr(StatusEffect.POISON)), new AttackMove(Moves.POISON_STING, "Poison Sting", Type.POISON, MoveCategory.PHYSICAL, 15, 100, 35, -1, "May poison the opponent.", 30, 1, new StatusEffectAttr(StatusEffect.POISON)),
@ -890,7 +981,7 @@ export const allMoves = [
new StatusMove(Moves.GROWL, "Growl", Type.NORMAL, 100, 40, -1, "Lowers opponent's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, -1)), new StatusMove(Moves.GROWL, "Growl", Type.NORMAL, 100, 40, -1, "Lowers opponent's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, -1)),
new StatusMove(Moves.ROAR, "Roar", Type.NORMAL, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 1), new StatusMove(Moves.ROAR, "Roar", Type.NORMAL, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, 1),
new StatusMove(Moves.SING, "Sing", Type.NORMAL, 55, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new StatusMove(Moves.SING, "Sing", Type.NORMAL, 55, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
new StatusMove(Moves.SUPERSONIC, "Supersonic", Type.NORMAL, 55, 20, -1, "Confuses opponent.", -1, 1), new StatusMove(Moves.SUPERSONIC, "Supersonic", Type.NORMAL, 55, 20, -1, "Confuses opponent.", -1, 1, new ConfuseAttr()),
new AttackMove(Moves.SONIC_BOOM, "Sonic Boom", Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, "Always inflicts 20 HP.", -1, 1), new AttackMove(Moves.SONIC_BOOM, "Sonic Boom", Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, "Always inflicts 20 HP.", -1, 1),
new StatusMove(Moves.DISABLE, "Disable", Type.NORMAL, 100, 20, -1, "Opponent can't use its last attack for a few turns.", -1, 1), new StatusMove(Moves.DISABLE, "Disable", Type.NORMAL, 100, 20, -1, "Opponent can't use its last attack for a few turns.", -1, 1),
new AttackMove(Moves.ACID, "Acid", Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)), new AttackMove(Moves.ACID, "Acid", Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)),
@ -902,7 +993,7 @@ export const allMoves = [
new AttackMove(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 1), new AttackMove(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 1),
new AttackMove(Moves.ICE_BEAM, "Ice Beam", Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 135, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)), new AttackMove(Moves.ICE_BEAM, "Ice Beam", Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 135, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)),
new AttackMove(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)), new AttackMove(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 1, new StatusEffectAttr(StatusEffect.FREEZE)),
new AttackMove(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 1), new AttackMove(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 1, new ConfuseAttr()),
new AttackMove(Moves.BUBBLE_BEAM, "Bubble Beam", Type.WATER, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)), new AttackMove(Moves.BUBBLE_BEAM, "Bubble Beam", Type.WATER, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)),
new AttackMove(Moves.AURORA_BEAM, "Aurora Beam", Type.ICE, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Attack.", 10, 1, new StatChangeAttr(BattleStat.ATK, -1)), new AttackMove(Moves.AURORA_BEAM, "Aurora Beam", Type.ICE, MoveCategory.SPECIAL, 65, 100, 20, -1, "May lower opponent's Attack.", 10, 1, new StatChangeAttr(BattleStat.ATK, -1)),
new AttackMove(Moves.HYPER_BEAM, "Hyper Beam", Type.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, 163, "User must recharge next turn.", -1, 1), new AttackMove(Moves.HYPER_BEAM, "Hyper Beam", Type.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, 163, "User must recharge next turn.", -1, 1),
@ -924,7 +1015,8 @@ export const allMoves = [
new StatusMove(Moves.POISON_POWDER, "Poison Powder", Type.POISON, 75, 35, -1, "Poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.POISON)), new StatusMove(Moves.POISON_POWDER, "Poison Powder", Type.POISON, 75, 35, -1, "Poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.POISON)),
new StatusMove(Moves.STUN_SPORE, "Stun Spore", Type.GRASS, 75, 30, -1, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)), new StatusMove(Moves.STUN_SPORE, "Stun Spore", Type.GRASS, 75, 30, -1, "Paralyzes opponent.", -1, 1, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new StatusMove(Moves.SLEEP_POWDER, "Sleep Powder", Type.GRASS, 75, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new StatusMove(Moves.SLEEP_POWDER, "Sleep Powder", Type.GRASS, 75, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
new AttackMove(Moves.PETAL_DANCE, "Petal Dance", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1), new AttackMove(Moves.PETAL_DANCE, "Petal Dance", Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, "User attacks for 2-3 turns but then becomes confused.", -1, 1,
new FrenzyAttr(), frenzyMissFunc, new ConfuseAttr(true)), // TODO: Update to still confuse if last hit misses
new StatusMove(Moves.STRING_SHOT, "String Shot", Type.BUG, 95, 40, -1, "Sharply lowers opponent's Speed.", -1, 1, new StatChangeAttr(BattleStat.SPD, -2)), new StatusMove(Moves.STRING_SHOT, "String Shot", Type.BUG, 95, 40, -1, "Sharply lowers opponent's Speed.", -1, 1, new StatChangeAttr(BattleStat.SPD, -2)),
new AttackMove(Moves.DRAGON_RAGE, "Dragon Rage", Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, "Always inflicts 40 HP.", -1, 1), new AttackMove(Moves.DRAGON_RAGE, "Dragon Rage", Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, "Always inflicts 40 HP.", -1, 1),
new AttackMove(Moves.FIRE_SPIN, "Fire Spin", Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, 24, "Traps opponent, damaging them for 4-5 turns.", 100, 1), new AttackMove(Moves.FIRE_SPIN, "Fire Spin", Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, 24, "Traps opponent, damaging them for 4-5 turns.", 100, 1),
@ -939,7 +1031,7 @@ export const allMoves = [
new AttackMove(Moves.DIG, "Dig", Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, 55, "Digs underground on first turn, attacks on second. Can also escape from caves.", -1, 1, new AttackMove(Moves.DIG, "Dig", Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, 55, "Digs underground on first turn, attacks on second. Can also escape from caves.", -1, 1,
new ChargeAttr(ChargeAnim.DIG_CHARGING, 'dug a hole!', BattleTagType.UNDERGROUND)), new ChargeAttr(ChargeAnim.DIG_CHARGING, 'dug a hole!', BattleTagType.UNDERGROUND)),
new StatusMove(Moves.TOXIC, "Toxic", Type.POISON, 90, 10, -1, "Badly poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.TOXIC)), new StatusMove(Moves.TOXIC, "Toxic", Type.POISON, 90, 10, -1, "Badly poisons opponent.", -1, 1, new StatusEffectAttr(StatusEffect.TOXIC)),
new AttackMove(Moves.CONFUSION, "Confusion", Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, -1, "May confuse opponent.", 10, 1), // TODO new AttackMove(Moves.CONFUSION, "Confusion", Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, -1, "May confuse opponent.", 10, 1, new ConfuseAttr()),
new AttackMove(Moves.PSYCHIC, "Psychic", Type.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 120, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)), new AttackMove(Moves.PSYCHIC, "Psychic", Type.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 120, "May lower opponent's Special Defense.", 10, 1, new StatChangeAttr(BattleStat.SPDEF, -1)),
new StatusMove(Moves.HYPNOSIS, "Hypnosis", Type.PSYCHIC, 60, 20, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new StatusMove(Moves.HYPNOSIS, "Hypnosis", Type.PSYCHIC, 60, 20, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
new StatusMove(Moves.MEDITATE, "Meditate", Type.PSYCHIC, -1, 40, -1, "Raises user's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, 1, true)), new StatusMove(Moves.MEDITATE, "Meditate", Type.PSYCHIC, -1, 40, -1, "Raises user's Attack.", -1, 1, new StatChangeAttr(BattleStat.ATK, 1, true)),
@ -955,7 +1047,7 @@ export const allMoves = [
new StatusMove(Moves.HARDEN, "Harden", Type.NORMAL, -1, 30, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)), new StatusMove(Moves.HARDEN, "Harden", Type.NORMAL, -1, 30, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)),
new StatusMove(Moves.MINIMIZE, "Minimize", Type.NORMAL, -1, 10, -1, "Sharply raises user's Evasiveness.", -1, 1, new StatChangeAttr(BattleStat.EVA, 1, true)), new StatusMove(Moves.MINIMIZE, "Minimize", Type.NORMAL, -1, 10, -1, "Sharply raises user's Evasiveness.", -1, 1, new StatChangeAttr(BattleStat.EVA, 1, true)),
new StatusMove(Moves.SMOKESCREEN, "Smokescreen", Type.NORMAL, 100, 20, -1, "Lowers opponent's Accuracy.", -1, 1, new StatChangeAttr(BattleStat.ACC, -1)), new StatusMove(Moves.SMOKESCREEN, "Smokescreen", Type.NORMAL, 100, 20, -1, "Lowers opponent's Accuracy.", -1, 1, new StatChangeAttr(BattleStat.ACC, -1)),
new StatusMove(Moves.CONFUSE_RAY, "Confuse Ray", Type.GHOST, 100, 10, 17, "Confuses opponent.", -1, 1), // TODO new StatusMove(Moves.CONFUSE_RAY, "Confuse Ray", Type.GHOST, 100, 10, 17, "Confuses opponent.", -1, 1, new ConfuseAttr()),
new StatusMove(Moves.WITHDRAW, "Withdraw", Type.WATER, -1, 40, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)), new StatusMove(Moves.WITHDRAW, "Withdraw", Type.WATER, -1, 40, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)),
new StatusMove(Moves.DEFENSE_CURL, "Defense Curl", Type.NORMAL, -1, 40, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)), new StatusMove(Moves.DEFENSE_CURL, "Defense Curl", Type.NORMAL, -1, 40, -1, "Raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 1, true)),
new StatusMove(Moves.BARRIER, "Barrier", Type.PSYCHIC, -1, 20, -1, "Sharply raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 2, true)), new StatusMove(Moves.BARRIER, "Barrier", Type.PSYCHIC, -1, 20, -1, "Sharply raises user's Defense.", -1, 1, new StatChangeAttr(BattleStat.DEF, 2, true)),
@ -995,7 +1087,7 @@ export const allMoves = [
new ChargeAttr(ChargeAnim.SKY_ATTACK_CHARGING, 'is glowing!'), new HighCritAttr(), new FlinchAttr()), new ChargeAttr(ChargeAnim.SKY_ATTACK_CHARGING, 'is glowing!'), new HighCritAttr(), new FlinchAttr()),
new StatusMove(Moves.TRANSFORM, "Transform", Type.NORMAL, -1, 10, -1, "User takes on the form and attacks of the opponent.", -1, 1), new StatusMove(Moves.TRANSFORM, "Transform", Type.NORMAL, -1, 10, -1, "User takes on the form and attacks of the opponent.", -1, 1),
new AttackMove(Moves.BUBBLE, "Bubble", Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)), new AttackMove(Moves.BUBBLE, "Bubble", Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, -1, "May lower opponent's Speed.", 10, 1, new StatChangeAttr(BattleStat.SPD, -1)),
new AttackMove(Moves.DIZZY_PUNCH, "Dizzy Punch", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, "May confuse opponent.", 20, 1), // TODO new AttackMove(Moves.DIZZY_PUNCH, "Dizzy Punch", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, "May confuse opponent.", 20, 1, new ConfuseAttr()),
new StatusMove(Moves.SPORE, "Spore", Type.GRASS, 100, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)), new StatusMove(Moves.SPORE, "Spore", Type.GRASS, 100, 15, -1, "Puts opponent to sleep.", -1, 1, new StatusEffectAttr(StatusEffect.SLEEP)),
new StatusMove(Moves.FLASH, "Flash", Type.NORMAL, 100, 20, -1, "Lowers opponent's Accuracy.", -1, 1, new StatChangeAttr(BattleStat.ACC, -1)), new StatusMove(Moves.FLASH, "Flash", Type.NORMAL, 100, 20, -1, "Lowers opponent's Accuracy.", -1, 1, new StatChangeAttr(BattleStat.ACC, -1)),
new AttackMove(Moves.PSYWAVE, "Psywave", Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, "Inflicts damage 50-150% of user's level.", -1, 1), new AttackMove(Moves.PSYWAVE, "Psywave", Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, "Inflicts damage 50-150% of user's level.", -1, 1),
@ -1036,7 +1128,7 @@ export const allMoves = [
new AttackMove(Moves.MACH_PUNCH, "Mach Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, "User attacks first.", -1, 2), new AttackMove(Moves.MACH_PUNCH, "Mach Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, "User attacks first.", -1, 2),
new StatusMove(Moves.SCARY_FACE, "Scary Face", Type.NORMAL, 100, 10, 6, "Sharply lowers opponent's Speed.", -1, 2, new StatChangeAttr(BattleStat.SPD, -2)), new StatusMove(Moves.SCARY_FACE, "Scary Face", Type.NORMAL, 100, 10, 6, "Sharply lowers opponent's Speed.", -1, 2, new StatChangeAttr(BattleStat.SPD, -2)),
new AttackMove(Moves.FEINT_ATTACK, "Feint Attack", Type.DARK, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 2), new AttackMove(Moves.FEINT_ATTACK, "Feint Attack", Type.DARK, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 2),
new StatusMove(Moves.SWEET_KISS, "Sweet Kiss", Type.FAIRY, 75, 10, -1, "Confuses opponent.", -1, 2), // TODO new StatusMove(Moves.SWEET_KISS, "Sweet Kiss", Type.FAIRY, 75, 10, -1, "Confuses opponent.", -1, 2, new ConfuseAttr()),
new StatusMove(Moves.BELLY_DRUM, "Belly Drum", Type.NORMAL, -1, 10, -1, "User loses 50% of its max HP, but Attack raises to maximum.", -1, 2), new StatusMove(Moves.BELLY_DRUM, "Belly Drum", Type.NORMAL, -1, 10, -1, "User loses 50% of its max HP, but Attack raises to maximum.", -1, 2),
new AttackMove(Moves.SLUDGE_BOMB, "Sludge Bomb", Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 148, "May poison opponent.", 30, 2, new StatusEffectAttr(StatusEffect.POISON)), new AttackMove(Moves.SLUDGE_BOMB, "Sludge Bomb", Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 148, "May poison opponent.", 30, 2, new StatusEffectAttr(StatusEffect.POISON)),
new AttackMove(Moves.MUD_SLAP, "Mud-Slap", Type.GROUND, MoveCategory.SPECIAL, 20, 100, 10, 5, "Lowers opponent's Accuracy.", 100, 2, new StatChangeAttr(BattleStat.ACC, -1)), new AttackMove(Moves.MUD_SLAP, "Mud-Slap", Type.GROUND, MoveCategory.SPECIAL, 20, 100, 10, 5, "Lowers opponent's Accuracy.", 100, 2, new StatChangeAttr(BattleStat.ACC, -1)),
@ -1050,14 +1142,15 @@ export const allMoves = [
new StatusMove(Moves.DETECT, "Detect", Type.FIGHTING, -1, 5, -1, "Protects the user, but may fail if used consecutively.", -1, 2), new StatusMove(Moves.DETECT, "Detect", Type.FIGHTING, -1, 5, -1, "Protects the user, but may fail if used consecutively.", -1, 2),
new AttackMove(Moves.BONE_RUSH, "Bone Rush", Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, "Hits 2-5 times in one turn.", -1, 2, new MultiHitAttr()), new AttackMove(Moves.BONE_RUSH, "Bone Rush", Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, "Hits 2-5 times in one turn.", -1, 2, new MultiHitAttr()),
new StatusMove(Moves.LOCK_ON, "Lock-On", Type.NORMAL, -1, 5, -1, "User's next attack is guaranteed to hit.", -1, 2), new StatusMove(Moves.LOCK_ON, "Lock-On", Type.NORMAL, -1, 5, -1, "User's next attack is guaranteed to hit.", -1, 2),
new AttackMove(Moves.OUTRAGE, "Outrage", Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, 156, "User attacks for 2-3 turns but then becomes confused.", -1, 2), new AttackMove(Moves.OUTRAGE, "Outrage", Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, 156, "User attacks for 2-3 turns but then becomes confused.", -1, 2,
new FrenzyAttr(), frenzyMissFunc, new ConfuseAttr(true)), // TODO: Update to still confuse if last hit misses
new StatusMove(Moves.SANDSTORM, "Sandstorm", Type.ROCK, -1, 10, 51, "Creates a sandstorm for 5 turns.", -1, 2), new StatusMove(Moves.SANDSTORM, "Sandstorm", Type.ROCK, -1, 10, 51, "Creates a sandstorm for 5 turns.", -1, 2),
new AttackMove(Moves.GIGA_DRAIN, "Giga Drain", Type.GRASS, MoveCategory.SPECIAL, 75, 100, 10, 111, "User recovers half the HP inflicted on opponent.", -1, 2), new AttackMove(Moves.GIGA_DRAIN, "Giga Drain", Type.GRASS, MoveCategory.SPECIAL, 75, 100, 10, 111, "User recovers half the HP inflicted on opponent.", -1, 2),
new StatusMove(Moves.ENDURE, "Endure", Type.NORMAL, -1, 10, 47, "Always left with at least 1 HP, but may fail if used consecutively.", -1, 2), new StatusMove(Moves.ENDURE, "Endure", Type.NORMAL, -1, 10, 47, "Always left with at least 1 HP, but may fail if used consecutively.", -1, 2),
new StatusMove(Moves.CHARM, "Charm", Type.FAIRY, 100, 20, 2, "Sharply lowers opponent's Attack.", -1, 2, new StatChangeAttr(BattleStat.ATK, -2)), new StatusMove(Moves.CHARM, "Charm", Type.FAIRY, 100, 20, 2, "Sharply lowers opponent's Attack.", -1, 2, new StatChangeAttr(BattleStat.ATK, -2)),
new AttackMove(Moves.ROLLOUT, "Rollout", Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, "Doubles in power each turn for 5 turns.", -1, 2), new AttackMove(Moves.ROLLOUT, "Rollout", Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, "Doubles in power each turn for 5 turns.", -1, 2),
new AttackMove(Moves.FALSE_SWIPE, "False Swipe", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, 57, "Always leaves opponent with at least 1 HP.", -1, 2), new AttackMove(Moves.FALSE_SWIPE, "False Swipe", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, 57, "Always leaves opponent with at least 1 HP.", -1, 2),
new StatusMove(Moves.SWAGGER, "Swagger", Type.NORMAL, 85, 15, -1, "Confuses opponent, but sharply raises its Attack.", -1, 2, new StatChangeAttr(BattleStat.ATK, 2)), // todo new StatusMove(Moves.SWAGGER, "Swagger", Type.NORMAL, 85, 15, -1, "Confuses opponent, but sharply raises its Attack.", -1, 2, new StatChangeAttr(BattleStat.ATK, 2), new ConfuseAttr()),
new StatusMove(Moves.MILK_DRINK, "Milk Drink", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 2), new StatusMove(Moves.MILK_DRINK, "Milk Drink", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 2),
new AttackMove(Moves.SPARK, "Spark", Type.ELECTRIC, MoveCategory.PHYSICAL, 65, 100, 20, -1, "May paralyze opponent.", 30, 2, new StatusEffectAttr(StatusEffect.PARALYSIS)), new AttackMove(Moves.SPARK, "Spark", Type.ELECTRIC, MoveCategory.PHYSICAL, 65, 100, 20, -1, "May paralyze opponent.", 30, 2, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new AttackMove(Moves.FURY_CUTTER, "Fury Cutter", Type.BUG, MoveCategory.PHYSICAL, 40, 95, 20, -1, "Power increases each turn.", -1, 2), new AttackMove(Moves.FURY_CUTTER, "Fury Cutter", Type.BUG, MoveCategory.PHYSICAL, 40, 95, 20, -1, "Power increases each turn.", -1, 2),
@ -1073,7 +1166,7 @@ export const allMoves = [
new StatusMove(Moves.PAIN_SPLIT, "Pain Split", Type.NORMAL, -1, 20, -1, "The user's and opponent's HP becomes the average of both.", -1, 2), new StatusMove(Moves.PAIN_SPLIT, "Pain Split", Type.NORMAL, -1, 20, -1, "The user's and opponent's HP becomes the average of both.", -1, 2),
new AttackMove(Moves.SACRED_FIRE, "Sacred Fire", Type.FIRE, MoveCategory.PHYSICAL, 100, 95, 5, -1, "May burn opponent.", 50, 2, new StatusEffectAttr(StatusEffect.BURN)), new AttackMove(Moves.SACRED_FIRE, "Sacred Fire", Type.FIRE, MoveCategory.PHYSICAL, 100, 95, 5, -1, "May burn opponent.", 50, 2, new StatusEffectAttr(StatusEffect.BURN)),
new AttackMove(Moves.MAGNITUDE, "Magnitude", Type.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, -1, "Hits with random power.", -1, 2), new AttackMove(Moves.MAGNITUDE, "Magnitude", Type.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, -1, "Hits with random power.", -1, 2),
new AttackMove(Moves.DYNAMIC_PUNCH, "Dynamic Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, -1, "Confuses opponent.", 100, 2), // TODO new AttackMove(Moves.DYNAMIC_PUNCH, "Dynamic Punch", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, -1, "Confuses opponent.", 100, 2, new ConfuseAttr()),
new AttackMove(Moves.MEGAHORN, "Megahorn", Type.BUG, MoveCategory.PHYSICAL, 120, 85, 10, -1, "", -1, 2), new AttackMove(Moves.MEGAHORN, "Megahorn", Type.BUG, MoveCategory.PHYSICAL, 120, 85, 10, -1, "", -1, 2),
new AttackMove(Moves.DRAGON_BREATH, "Dragon Breath", Type.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, -1, "May paralyze opponent.", 30, 2, new StatusEffectAttr(StatusEffect.PARALYSIS)), new AttackMove(Moves.DRAGON_BREATH, "Dragon Breath", Type.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, -1, "May paralyze opponent.", 30, 2, new StatusEffectAttr(StatusEffect.PARALYSIS)),
new StatusMove(Moves.BATON_PASS, "Baton Pass", Type.NORMAL, -1, 40, 132, "User switches out and gives stat changes to the incoming Pokémon.", -1, 2), new StatusMove(Moves.BATON_PASS, "Baton Pass", Type.NORMAL, -1, 40, 132, "User switches out and gives stat changes to the incoming Pokémon.", -1, 2),
@ -1105,14 +1198,14 @@ export const allMoves = [
new AttackMove(Moves.WHIRLPOOL, "Whirlpool", Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 2), new AttackMove(Moves.WHIRLPOOL, "Whirlpool", Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, "Traps opponent, damaging them for 4-5 turns.", 100, 2),
new AttackMove(Moves.BEAT_UP, "Beat Up", Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, "Each Pokémon in user's party attacks.", -1, 2), new AttackMove(Moves.BEAT_UP, "Beat Up", Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, "Each Pokémon in user's party attacks.", -1, 2),
new AttackMove(Moves.FAKE_OUT, "Fake Out", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 10, -1, "User attacks first, foe flinches. Only usable on first turn.", 100, 3, new FlinchAttr()), // TODO new AttackMove(Moves.FAKE_OUT, "Fake Out", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 10, -1, "User attacks first, foe flinches. Only usable on first turn.", 100, 3, new FlinchAttr()), // TODO
new AttackMove(Moves.UPROAR, "Uproar", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, "User attacks for 3 turns and prevents sleep.", -1, 3), new AttackMove(Moves.UPROAR, "Uproar", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, "User attacks for 3 turns and prevents sleep.", -1, 3), // TODO
new StatusMove(Moves.STOCKPILE, "Stockpile", Type.NORMAL, -1, 20, -1, "Stores energy for use with Spit Up and Swallow.", -1, 3), new StatusMove(Moves.STOCKPILE, "Stockpile", Type.NORMAL, -1, 20, -1, "Stores energy for use with Spit Up and Swallow.", -1, 3),
new AttackMove(Moves.SPIT_UP, "Spit Up", Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, -1, "Power depends on how many times the user performed Stockpile.", -1, 3), new AttackMove(Moves.SPIT_UP, "Spit Up", Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, -1, "Power depends on how many times the user performed Stockpile.", -1, 3),
new StatusMove(Moves.SWALLOW, "Swallow", Type.NORMAL, -1, 10, -1, "The more times the user has performed Stockpile, the more HP is recovered.", -1, 3), new StatusMove(Moves.SWALLOW, "Swallow", Type.NORMAL, -1, 10, -1, "The more times the user has performed Stockpile, the more HP is recovered.", -1, 3),
new AttackMove(Moves.HEAT_WAVE, "Heat Wave", Type.FIRE, MoveCategory.SPECIAL, 95, 90, 10, 118, "May burn opponent.", 10, 3, new StatusEffectAttr(StatusEffect.BURN)), new AttackMove(Moves.HEAT_WAVE, "Heat Wave", Type.FIRE, MoveCategory.SPECIAL, 95, 90, 10, 118, "May burn opponent.", 10, 3, new StatusEffectAttr(StatusEffect.BURN)),
new StatusMove(Moves.HAIL, "Hail", Type.ICE, -1, 10, -1, "Non-Ice types are damaged for 5 turns.", -1, 3), new StatusMove(Moves.HAIL, "Hail", Type.ICE, -1, 10, -1, "Non-Ice types are damaged for 5 turns.", -1, 3),
new StatusMove(Moves.TORMENT, "Torment", Type.DARK, 100, 15, -1, "Opponent cannot use the same move in a row.", -1, 3), new StatusMove(Moves.TORMENT, "Torment", Type.DARK, 100, 15, -1, "Opponent cannot use the same move in a row.", -1, 3),
new StatusMove(Moves.FLATTER, "Flatter", Type.DARK, 100, 15, -1, "Confuses opponent, but raises its Special Attack.", -1, 3, new StatChangeAttr(BattleStat.SPATK, 1)), // TODO new StatusMove(Moves.FLATTER, "Flatter", Type.DARK, 100, 15, -1, "Confuses opponent, but raises its Special Attack.", -1, 3, new StatChangeAttr(BattleStat.SPATK, 1), new ConfuseAttr()),
new StatusMove(Moves.WILL_O_WISP, "Will-O-Wisp", Type.FIRE, 85, 15, 107, "Burns opponent.", -1, 3, new StatusEffectAttr(StatusEffect.BURN)), new StatusMove(Moves.WILL_O_WISP, "Will-O-Wisp", Type.FIRE, 85, 15, 107, "Burns opponent.", -1, 3, new StatusEffectAttr(StatusEffect.BURN)),
new StatusMove(Moves.MEMENTO, "Memento", Type.DARK, 100, 10, -1, "User faints, sharply lowers opponent's Attack and Special Attack.", -1, 3, new StatusMove(Moves.MEMENTO, "Memento", Type.DARK, 100, 10, -1, "User faints, sharply lowers opponent's Attack and Special Attack.", -1, 3,
new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], -2)), // TODO new StatChangeAttr([ BattleStat.ATK, BattleStat.SPATK ], -2)), // TODO
@ -1153,7 +1246,7 @@ export const allMoves = [
new AttackMove(Moves.LUSTER_PURGE, "Luster Purge", Type.PSYCHIC, MoveCategory.SPECIAL, 70, 100, 5, -1, "May lower opponent's Special Defense.", 50, 3, new StatChangeAttr(BattleStat.SPDEF, -1)), new AttackMove(Moves.LUSTER_PURGE, "Luster Purge", Type.PSYCHIC, MoveCategory.SPECIAL, 70, 100, 5, -1, "May lower opponent's Special Defense.", 50, 3, new StatChangeAttr(BattleStat.SPDEF, -1)),
new AttackMove(Moves.MIST_BALL, "Mist Ball", Type.PSYCHIC, MoveCategory.SPECIAL, 70, 100, 5, -1, "May lower opponent's Special Attack.", 50, 3, new StatChangeAttr(BattleStat.SPATK, -1)), new AttackMove(Moves.MIST_BALL, "Mist Ball", Type.PSYCHIC, MoveCategory.SPECIAL, 70, 100, 5, -1, "May lower opponent's Special Attack.", 50, 3, new StatChangeAttr(BattleStat.SPATK, -1)),
new StatusMove(Moves.FEATHER_DANCE, "Feather Dance", Type.FLYING, 100, 15, -1, "Sharply lowers opponent's Attack.", -1, 3, new StatChangeAttr(BattleStat.ATK, -2)), new StatusMove(Moves.FEATHER_DANCE, "Feather Dance", Type.FLYING, 100, 15, -1, "Sharply lowers opponent's Attack.", -1, 3, new StatChangeAttr(BattleStat.ATK, -2)),
new StatusMove(Moves.TEETER_DANCE, "Teeter Dance", Type.NORMAL, 100, 20, -1, "Confuses all Pokémon.", -1, 3), // TODO new StatusMove(Moves.TEETER_DANCE, "Teeter Dance", Type.NORMAL, 100, 20, -1, "Confuses all Pokémon.", -1, 3, new ConfuseAttr(true), new ConfuseAttr()),
new AttackMove(Moves.BLAZE_KICK, "Blaze Kick", Type.FIRE, MoveCategory.PHYSICAL, 85, 90, 10, -1, "High critical hit ratio. May burn opponent.", 10, 3, new HighCritAttr(), new StatusEffectAttr(StatusEffect.BURN)), new AttackMove(Moves.BLAZE_KICK, "Blaze Kick", Type.FIRE, MoveCategory.PHYSICAL, 85, 90, 10, -1, "High critical hit ratio. May burn opponent.", 10, 3, new HighCritAttr(), new StatusEffectAttr(StatusEffect.BURN)),
new StatusMove(Moves.MUD_SPORT, "Mud Sport", Type.GROUND, -1, 15, -1, "Weakens the power of Electric-type moves.", -1, 3), new StatusMove(Moves.MUD_SPORT, "Mud Sport", Type.GROUND, -1, 15, -1, "Weakens the power of Electric-type moves.", -1, 3),
new AttackMove(Moves.ICE_BALL, "Ice Ball", Type.ICE, MoveCategory.PHYSICAL, 30, 90, 20, -1, "Doubles in power each turn for 5 turns.", -1, 3), new AttackMove(Moves.ICE_BALL, "Ice Ball", Type.ICE, MoveCategory.PHYSICAL, 30, 90, 20, -1, "Doubles in power each turn for 5 turns.", -1, 3),
@ -1182,7 +1275,7 @@ export const allMoves = [
new StatusMove(Moves.COSMIC_POWER, "Cosmic Power", Type.PSYCHIC, -1, 20, -1, "Raises user's Defense and Special Defense.", -1, 3, new StatusMove(Moves.COSMIC_POWER, "Cosmic Power", Type.PSYCHIC, -1, 20, -1, "Raises user's Defense and Special Defense.", -1, 3,
new StatChangeAttr([ BattleStat.DEF, BattleStat.SPDEF ], 1, true)), new StatChangeAttr([ BattleStat.DEF, BattleStat.SPDEF ], 1, true)),
new AttackMove(Moves.WATER_SPOUT, "Water Spout", Type.WATER, MoveCategory.SPECIAL, 150, 100, 5, -1, "The higher the user's HP, the higher the damage caused.", -1, 3), new AttackMove(Moves.WATER_SPOUT, "Water Spout", Type.WATER, MoveCategory.SPECIAL, 150, 100, 5, -1, "The higher the user's HP, the higher the damage caused.", -1, 3),
new AttackMove(Moves.SIGNAL_BEAM, "Signal Beam", Type.BUG, MoveCategory.SPECIAL, 75, 100, 15, -1, "May confuse opponent.", 10, 3), // TODO new AttackMove(Moves.SIGNAL_BEAM, "Signal Beam", Type.BUG, MoveCategory.SPECIAL, 75, 100, 15, -1, "May confuse opponent.", 10, 3, new ConfuseAttr()),
new AttackMove(Moves.SHADOW_PUNCH, "Shadow Punch", Type.GHOST, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3), new AttackMove(Moves.SHADOW_PUNCH, "Shadow Punch", Type.GHOST, MoveCategory.PHYSICAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3),
new AttackMove(Moves.EXTRASENSORY, "Extrasensory", Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, -1, "May cause flinching.", 10, 3, new FlinchAttr()), new AttackMove(Moves.EXTRASENSORY, "Extrasensory", Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, -1, "May cause flinching.", 10, 3, new FlinchAttr()),
new AttackMove(Moves.SKY_UPPERCUT, "Sky Uppercut", Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, "Hits the opponent, even during Fly.", -1, 3, new HitsTagAttr(BattleTagType.FLYING)), new AttackMove(Moves.SKY_UPPERCUT, "Sky Uppercut", Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, "Hits the opponent, even during Fly.", -1, 3, new HitsTagAttr(BattleTagType.FLYING)),
@ -1215,7 +1308,7 @@ export const allMoves = [
new StatChangeAttr([ BattleStat.ATK, BattleStat.SPD ], 1, true)), new StatChangeAttr([ BattleStat.ATK, BattleStat.SPD ], 1, true)),
new AttackMove(Moves.ROCK_BLAST, "Rock Blast", Type.ROCK, MoveCategory.PHYSICAL, 25, 90, 10, 76, "Hits 2-5 times in one turn.", -1, 3, new MultiHitAttr()), new AttackMove(Moves.ROCK_BLAST, "Rock Blast", Type.ROCK, MoveCategory.PHYSICAL, 25, 90, 10, 76, "Hits 2-5 times in one turn.", -1, 3, new MultiHitAttr()),
new AttackMove(Moves.SHOCK_WAVE, "Shock Wave", Type.ELECTRIC, MoveCategory.SPECIAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3), new AttackMove(Moves.SHOCK_WAVE, "Shock Wave", Type.ELECTRIC, MoveCategory.SPECIAL, 60, 999, 20, -1, "Ignores Accuracy and Evasiveness.", -1, 3),
new AttackMove(Moves.WATER_PULSE, "Water Pulse", Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 11, "May confuse opponent.", 20, 3), new AttackMove(Moves.WATER_PULSE, "Water Pulse", Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 11, "May confuse opponent.", 20, 3, new ConfuseAttr()),
new AttackMove(Moves.DOOM_DESIRE, "Doom Desire", Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, "Damage occurs 2 turns later.", -1, 3, new AttackMove(Moves.DOOM_DESIRE, "Doom Desire", Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, "Damage occurs 2 turns later.", -1, 3,
new ChargeAttr(ChargeAnim.DOOM_DESIRE_CHARGING, 'chose\nDOOM DESIRE as its destiny!')), new ChargeAttr(ChargeAnim.DOOM_DESIRE_CHARGING, 'chose\nDOOM DESIRE as its destiny!')),
new AttackMove(Moves.PSYCHO_BOOST, "Psycho Boost", Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, "Sharply lowers user's Special Attack.", 100, 3, new StatChangeAttr(BattleStat.SPATK, -2, true)), new AttackMove(Moves.PSYCHO_BOOST, "Psycho Boost", Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, "Sharply lowers user's Special Attack.", 100, 3, new StatChangeAttr(BattleStat.SPATK, -2, true)),
@ -1231,7 +1324,7 @@ export const allMoves = [
new AttackMove(Moves.FEINT, "Feint", Type.NORMAL, MoveCategory.PHYSICAL, 30, 100, 10, -1, "Only hits if opponent uses Protect or Detect in the same turn.", -1, 4), new AttackMove(Moves.FEINT, "Feint", Type.NORMAL, MoveCategory.PHYSICAL, 30, 100, 10, -1, "Only hits if opponent uses Protect or Detect in the same turn.", -1, 4),
new AttackMove(Moves.PLUCK, "Pluck", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 20, -1, "If the opponent is holding a berry, its effect is stolen by user.", -1, 4), new AttackMove(Moves.PLUCK, "Pluck", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 20, -1, "If the opponent is holding a berry, its effect is stolen by user.", -1, 4),
new StatusMove(Moves.TAILWIND, "Tailwind", Type.FLYING, -1, 15, 113, "Doubles Speed for 4 turns.", -1, 4), new StatusMove(Moves.TAILWIND, "Tailwind", Type.FLYING, -1, 15, 113, "Doubles Speed for 4 turns.", -1, 4),
new StatusMove(Moves.ACUPRESSURE, "Acupressure", Type.NORMAL, -1, 30, -1, "Sharply raises a random stat.", -1, 4, new StatChangeAttr(BattleStat.RAND, 2, true)), // TODO new StatusMove(Moves.ACUPRESSURE, "Acupressure", Type.NORMAL, -1, 30, -1, "Sharply raises a random stat.", -1, 4, new StatChangeAttr(BattleStat.RAND, 2, true)),
new AttackMove(Moves.METAL_BURST, "Metal Burst", Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, "Deals damage equal to 1.5x opponent's attack.", -1, 4), new AttackMove(Moves.METAL_BURST, "Metal Burst", Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, "Deals damage equal to 1.5x opponent's attack.", -1, 4),
new AttackMove(Moves.U_TURN, "U-turn", Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, 60, "User switches out immediately after attacking.", -1, 4), new AttackMove(Moves.U_TURN, "U-turn", Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, 60, "User switches out immediately after attacking.", -1, 4),
new AttackMove(Moves.CLOSE_COMBAT, "Close Combat", Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, 167, "Lowers user's Defense and Special Defense.", 100, 4, new AttackMove(Moves.CLOSE_COMBAT, "Close Combat", Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, 167, "Lowers user's Defense and Special Defense.", 100, 4,
@ -1296,7 +1389,7 @@ export const allMoves = [
new AttackMove(Moves.ZEN_HEADBUTT, "Zen Headbutt", Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 90, 15, 59, "May cause flinching.", 20, 4, new FlinchAttr()), new AttackMove(Moves.ZEN_HEADBUTT, "Zen Headbutt", Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 90, 15, 59, "May cause flinching.", 20, 4, new FlinchAttr()),
new AttackMove(Moves.MIRROR_SHOT, "Mirror Shot", Type.STEEL, MoveCategory.SPECIAL, 65, 85, 10, -1, "May lower opponent's Accuracy.", 30, 4, new StatChangeAttr(BattleStat.ACC, -1)), new AttackMove(Moves.MIRROR_SHOT, "Mirror Shot", Type.STEEL, MoveCategory.SPECIAL, 65, 85, 10, -1, "May lower opponent's Accuracy.", 30, 4, new StatChangeAttr(BattleStat.ACC, -1)),
new AttackMove(Moves.FLASH_CANNON, "Flash Cannon", Type.STEEL, MoveCategory.SPECIAL, 80, 100, 10, 93, "May lower opponent's Special Defense.", 10, 4, new StatChangeAttr(BattleStat.SPDEF, -1)), new AttackMove(Moves.FLASH_CANNON, "Flash Cannon", Type.STEEL, MoveCategory.SPECIAL, 80, 100, 10, 93, "May lower opponent's Special Defense.", 10, 4, new StatChangeAttr(BattleStat.SPDEF, -1)),
new AttackMove(Moves.ROCK_CLIMB, "Rock Climb", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, "May confuse opponent.", 20, 4), // TODO new AttackMove(Moves.ROCK_CLIMB, "Rock Climb", Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, "May confuse opponent.", 20, 4, new ConfuseAttr()),
new StatusMove(Moves.DEFOG, "Defog", Type.FLYING, -1, 15, -1, "Lowers opponent's Evasiveness and clears fog.", -1, 4, new StatChangeAttr(BattleStat.EVA, -1)), // TODO new StatusMove(Moves.DEFOG, "Defog", Type.FLYING, -1, 15, -1, "Lowers opponent's Evasiveness and clears fog.", -1, 4, new StatChangeAttr(BattleStat.EVA, -1)), // TODO
new StatusMove(Moves.TRICK_ROOM, "Trick Room", Type.PSYCHIC, -1, 5, 161, "Slower Pokémon move first in the turn for 5 turns.", -1, 4), new StatusMove(Moves.TRICK_ROOM, "Trick Room", Type.PSYCHIC, -1, 5, 161, "Slower Pokémon move first in the turn for 5 turns.", -1, 4),
new AttackMove(Moves.DRACO_METEOR, "Draco Meteor", Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, 169, "Sharply lowers user's Special Attack.", 100, 4, new StatChangeAttr(BattleStat.SPATK, -2, true)), new AttackMove(Moves.DRACO_METEOR, "Draco Meteor", Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, 169, "Sharply lowers user's Special Attack.", 100, 4, new StatChangeAttr(BattleStat.SPATK, -2, true)),
@ -1314,7 +1407,7 @@ export const allMoves = [
new StatusMove(Moves.CAPTIVATE, "Captivate", Type.NORMAL, 100, 20, -1, "Sharply lowers opponent's Special Attack if opposite gender.", -1, 4), // TODO XX new StatusMove(Moves.CAPTIVATE, "Captivate", Type.NORMAL, 100, 20, -1, "Sharply lowers opponent's Special Attack if opposite gender.", -1, 4), // TODO XX
new StatusMove(Moves.STEALTH_ROCK, "Stealth Rock", Type.ROCK, -1, 20, 116, "Damages opponent switching into battle.", -1, 4), new StatusMove(Moves.STEALTH_ROCK, "Stealth Rock", Type.ROCK, -1, 20, 116, "Damages opponent switching into battle.", -1, 4),
new AttackMove(Moves.GRASS_KNOT, "Grass Knot", Type.GRASS, MoveCategory.SPECIAL, -1, 100, 20, 81, "The heavier the opponent, the stronger the attack.", -1, 4), new AttackMove(Moves.GRASS_KNOT, "Grass Knot", Type.GRASS, MoveCategory.SPECIAL, -1, 100, 20, 81, "The heavier the opponent, the stronger the attack.", -1, 4),
new AttackMove(Moves.CHATTER, "Chatter", Type.FLYING, MoveCategory.SPECIAL, 65, 100, 20, -1, "Confuses opponent.", 100, 4), new AttackMove(Moves.CHATTER, "Chatter", Type.FLYING, MoveCategory.SPECIAL, 65, 100, 20, -1, "Confuses opponent.", 100, 4, new ConfuseAttr()),
new AttackMove(Moves.JUDGMENT, "Judgment", Type.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, "Type depends on the Arceus Plate being held.", -1, 4), new AttackMove(Moves.JUDGMENT, "Judgment", Type.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, "Type depends on the Arceus Plate being held.", -1, 4),
new AttackMove(Moves.BUG_BITE, "Bug Bite", Type.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, "Receives the effect from the opponent's held berry.", -1, 4), new AttackMove(Moves.BUG_BITE, "Bug Bite", Type.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, "Receives the effect from the opponent's held berry.", -1, 4),
new AttackMove(Moves.CHARGE_BEAM, "Charge Beam", Type.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 23, "May raise user's Special Attack.", 70, 4, new StatChangeAttr(BattleStat.SPATK, 1, true)), new AttackMove(Moves.CHARGE_BEAM, "Charge Beam", Type.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 23, "May raise user's Special Attack.", 70, 4, new StatChangeAttr(BattleStat.SPATK, 1, true)),
@ -1418,7 +1511,7 @@ export const allMoves = [
new AttackMove(Moves.NIGHT_DAZE, "Night Daze", Type.DARK, MoveCategory.SPECIAL, 85, 95, 10, -1, "May lower opponent's Accuracy.", 40, 5, new StatChangeAttr(BattleStat.ACC, -1)), new AttackMove(Moves.NIGHT_DAZE, "Night Daze", Type.DARK, MoveCategory.SPECIAL, 85, 95, 10, -1, "May lower opponent's Accuracy.", 40, 5, new StatChangeAttr(BattleStat.ACC, -1)),
new AttackMove(Moves.PSYSTRIKE, "Psystrike", Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 10, -1, "Inflicts damage based on the target's Defense, not Special Defense.", -1, 5), new AttackMove(Moves.PSYSTRIKE, "Psystrike", Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 10, -1, "Inflicts damage based on the target's Defense, not Special Defense.", -1, 5),
new AttackMove(Moves.TAIL_SLAP, "Tail Slap", Type.NORMAL, MoveCategory.PHYSICAL, 25, 85, 10, -1, "Hits 2-5 times in one turn.", -1, 5, new MultiHitAttr()), new AttackMove(Moves.TAIL_SLAP, "Tail Slap", Type.NORMAL, MoveCategory.PHYSICAL, 25, 85, 10, -1, "Hits 2-5 times in one turn.", -1, 5, new MultiHitAttr()),
new AttackMove(Moves.HURRICANE, "Hurricane", Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 160, "May confuse opponent.", 30, 5), new AttackMove(Moves.HURRICANE, "Hurricane", Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 160, "May confuse opponent.", 30, 5, new ConfuseAttr()),
new AttackMove(Moves.HEAD_CHARGE, "Head Charge", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, "User receives recoil damage.", -1, 5), new AttackMove(Moves.HEAD_CHARGE, "Head Charge", Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, "User receives recoil damage.", -1, 5),
new AttackMove(Moves.GEAR_GRIND, "Gear Grind", Type.STEEL, MoveCategory.PHYSICAL, 50, 85, 15, -1, "Hits twice in one turn.", -1, 5, new MultiHitAttr(MultiHitType._2)), new AttackMove(Moves.GEAR_GRIND, "Gear Grind", Type.STEEL, MoveCategory.PHYSICAL, 50, 85, 15, -1, "Hits twice in one turn.", -1, 5, new MultiHitAttr(MultiHitType._2)),
new AttackMove(Moves.SEARING_SHOT, "Searing Shot", Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, "May burn opponent.", 30, 5, new StatusEffectAttr(StatusEffect.BURN)), new AttackMove(Moves.SEARING_SHOT, "Searing Shot", Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, "May burn opponent.", 30, 5, new StatusEffectAttr(StatusEffect.BURN)),

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, HitsTagAttr } from './move'; import { default as Move, allMoves, MoveCategory, Moves, StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs } 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';
@ -15,9 +15,9 @@ import { initMoveAnim, loadMoveAnimAssets } from './battle-anims';
import { Status, StatusEffect } from './status-effect'; import { Status, StatusEffect } from './status-effect';
import { tmSpecies } from './tms'; import { tmSpecies } from './tms';
import { pokemonEvolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './pokemon-evolutions'; import { pokemonEvolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './pokemon-evolutions';
import { MessagePhase } from './battle-phases'; import { DamagePhase, MessagePhase } from './battle-phases';
import { BattleStat } from './battle-stat'; import { BattleStat } from './battle-stat';
import { BattleTag, BattleTagLapseType, BattleTagType } from './battle-tag'; import { BattleTag, BattleTagLapseType, BattleTagType, getBattleTag } from './battle-tag';
import { Species } from './species'; import { Species } from './species';
export default abstract class Pokemon extends Phaser.GameObjects.Container { export default abstract class Pokemon extends Phaser.GameObjects.Container {
@ -411,135 +411,128 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate); this.levelExp = this.exp - getLevelTotalExp(this.level, this.species.growthRate);
} }
apply(source: Pokemon, battlerMove: PokemonMove): Promise<MoveResult> { apply(source: Pokemon, battlerMove: PokemonMove): MoveResult {
return new Promise(resolve => { let result: MoveResult = MoveResult.STATUS;
let result: MoveResult = MoveResult.STATUS; let success = false;
let success = false; const move = battlerMove.getMove();
const move = battlerMove.getMove(); const moveCategory = move.category;
const moveCategory = move.category; let damage = 0;
let damage = 0; switch (moveCategory) {
switch (moveCategory) { case MoveCategory.PHYSICAL:
case MoveCategory.PHYSICAL: case MoveCategory.SPECIAL:
case MoveCategory.SPECIAL: const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const isPhysical = moveCategory === MoveCategory.PHYSICAL; const power = new Utils.NumberHolder(move.power);
const power = new Utils.NumberHolder(move.power); this.scene.applyModifiers(AttackTypeBoosterModifier, source, power);
this.scene.applyModifiers(AttackTypeBoosterModifier, source, power); const critChance = new Utils.IntegerHolder(16);
const critChance = new Utils.IntegerHolder(16); applyMoveAttrs(HighCritAttr, this.scene, source, this, move, critChance);
applyMoveAttrs(HighCritAttr, this.scene, source, this, move, critChance); const isCritical = Utils.randInt(critChance.value) === 0;
const isCritical = Utils.randInt(critChance.value) === 0; const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK);
const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK); const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF);
const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF); const stabMultiplier = source.species.type1 === move.type || (source.species.type2 > -1 && source.species.type2 === move.type) ? 1.5 : 1;
const stabMultiplier = source.species.type1 === move.type || (source.species.type2 > -1 && source.species.type2 === move.type) ? 1.5 : 1; const typeMultiplier = getTypeDamageMultiplier(move.type, this.species.type1) * (this.species.type2 > -1 ? getTypeDamageMultiplier(move.type, this.species.type2) : 1);
const typeMultiplier = getTypeDamageMultiplier(move.type, this.species.type1) * (this.species.type2 > -1 ? getTypeDamageMultiplier(move.type, this.species.type2) : 1); const criticalMultiplier = isCritical ? 2 : 1;
const criticalMultiplier = isCritical ? 2 : 1; damage = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier;
damage = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier; if (isPhysical && source.status && source.status.effect === StatusEffect.BURN)
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) damage = Math.floor(damage / 2);
damage = Math.floor(damage / 2); move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { if (this.getTag(hta.tagType))
if (this.getTag(hta.tagType)) { damage *= 2;
console.log('ye'); });
damage *= 2; console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
}
}); if (typeMultiplier >= 2)
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef); result = MoveResult.SUPER_EFFECTIVE;
if (damage) { else if (typeMultiplier >= 1)
this.hp = Math.max(this.hp - damage, 0); result = MoveResult.EFFECTIVE;
source.turnData.damageDealt += damage; else if (typeMultiplier > 0)
if (isCritical) result = MoveResult.NOT_VERY_EFFECTIVE;
this.scene.unshiftPhase(new MessagePhase(this.scene, 'A critical hit!')); else
} result = MoveResult.NO_EFFECT;
if (typeMultiplier >= 2)
result = MoveResult.SUPER_EFFECTIVE;
else if (typeMultiplier >= 1)
result = MoveResult.EFFECTIVE;
else if (typeMultiplier > 0)
result = MoveResult.NOT_VERY_EFFECTIVE;
else
result = MoveResult.NO_EFFECT;
switch (result) {
case MoveResult.EFFECTIVE:
this.scene.sound.play('hit');
success = true;
break;
case MoveResult.SUPER_EFFECTIVE:
this.scene.sound.play('hit_strong');
this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s super effective!'));
success = true;
break;
case MoveResult.NOT_VERY_EFFECTIVE:
this.scene.sound.play('hit_weak');
this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s not very effective!'))
success = true;
break;
case MoveResult.NO_EFFECT:
this.scene.unshiftPhase(new MessagePhase(this.scene, `It doesn\'t affect ${this.name}!`))
success = true;
break;
}
break;
case MoveCategory.STATUS:
result = MoveResult.STATUS;
success = true;
break;
}
if (success) { if (damage) {
if (result <= MoveResult.NOT_VERY_EFFECTIVE) { this.hp = Math.max(this.hp - damage, 0);
const flashTimer = this.scene.time.addEvent({ source.turnData.damageDealt += damage;
delay: 100, this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), result as DamageResult))
repeat: 5, if (isCritical)
startAt: 200, this.scene.unshiftPhase(new MessagePhase(this.scene, 'A critical hit!'));
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); switch (result) {
}); case MoveResult.EFFECTIVE:
success = true;
break;
case MoveResult.SUPER_EFFECTIVE:
this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s super effective!'));
success = true;
break;
case MoveResult.NOT_VERY_EFFECTIVE:
this.scene.unshiftPhase(new MessagePhase(this.scene, 'It\'s not very effective!'))
success = true;
break;
case MoveResult.NO_EFFECT:
this.scene.unshiftPhase(new MessagePhase(this.scene, `It doesn\'t affect ${this.name}!`))
success = true;
break;
}
break;
case MoveCategory.STATUS:
result = MoveResult.STATUS;
success = true;
break;
}
return result;
} }
addTag(tagType: BattleTagType, lapseType: BattleTagLapseType, turnCount?: integer): boolean { addTag(tagType: BattleTagType, turnCount?: integer): boolean {
if (this.getTag(tagType)) const existingTag = this.getTag(tagType);
if (existingTag) {
existingTag.onOverlap(this);
return false; return false;
}
const newTag = new BattleTag(tagType, lapseType || BattleTagLapseType.FAINT, turnCount || 1); const newTag = getBattleTag(tagType, turnCount || 1);
this.summonData.tags.push(newTag); this.summonData.tags.push(newTag);
if (newTag.isHidden()) newTag.onAdd(this);
this.setVisible(false);
} }
getTag(tagFilter: BattleTagType | ((tag: BattleTag) => boolean)): BattleTag { getTag(tagType: BattleTagType | { new(...args: any[]): BattleTag }): BattleTag {
return typeof(tagFilter) === 'number' return typeof(tagType) === 'number'
? this.summonData.tags.find(t => t.tagType === tagFilter) ? this.summonData.tags.find(t => t.tagType === tagType)
: this.summonData.tags.find(t => tagFilter(t)); : this.summonData.tags.find(t => t instanceof tagType);
} }
getTags(tagFilter: BattleTagType | ((tag: BattleTag) => boolean)): BattleTag[] { findTag(tagFilter: ((tag: BattleTag) => boolean)) {
return typeof(tagFilter) === 'number' return this.summonData.tags.find(t => tagFilter(t));
? this.summonData.tags.filter(t => t.tagType === tagFilter)
: this.summonData.tags.filter(t => tagFilter(t));
} }
lapseTags(lapseType: BattleTagLapseType) { getTags(tagType: BattleTagType | { new(...args: any[]): BattleTag }): BattleTag[] {
return typeof(tagType) === 'number'
? this.summonData.tags.filter(t => t.tagType === tagType)
: this.summonData.tags.filter(t => t instanceof tagType);
}
findTags(tagFilter: ((tag: BattleTag) => boolean)) {
return this.summonData.tags.filter(t => tagFilter(t));
}
lapseTag(tagType: BattleTagType): void {
const tags = this.summonData.tags; 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 tag = tags.find(t => t.tagType === tagType);
const visible = !this.getTag(t => t.isHidden()); if (tag && !(tag.lapse(this))) {
if (visible && !this.visible) { tag.onRemove(this);
// Wait 2 frames before setting visible for battle animations that don't immediately show the sprite invisible tags.splice(tags.indexOf(tag), 1);
this.scene.tweens.addCounter({ }
duration: 2, }
useFrames: true,
onComplete: () => this.setVisible(true) lapseTags(lapseType: BattleTagLapseType): void {
}); const tags = this.summonData.tags;
} else tags.filter(t => lapseType === BattleTagLapseType.FAINT || ((t.lapseType === lapseType) && !(t.lapse(this)))).forEach(t => {
this.setVisible(visible); t.onRemove(this);
tags.splice(tags.indexOf(t), 1);
});
} }
getLastXMoves(turnCount?: integer): TurnMove[] { getLastXMoves(turnCount?: integer): TurnMove[] {
@ -914,8 +907,6 @@ export class PokemonSummonData {
public moveHistory: TurnMove[] = []; public moveHistory: TurnMove[] = [];
public moveQueue: QueuedMove[] = []; public moveQueue: QueuedMove[] = [];
public tags: BattleTag[] = []; public tags: BattleTag[] = [];
public charging: boolean;
public confusionTurns: integer;
} }
export class PokemonBattleSummonData { export class PokemonBattleSummonData {
@ -947,6 +938,8 @@ export enum MoveResult {
OTHER OTHER
}; };
export type DamageResult = MoveResult.EFFECTIVE | MoveResult.SUPER_EFFECTIVE | MoveResult.NOT_VERY_EFFECTIVE;
export class PokemonMove { export class PokemonMove {
public moveId: Moves; public moveId: Moves;
public ppUsed: integer; public ppUsed: integer;