Implement Double Battles (#1)
* Add WiP logic for double battles * Minor changes for double battles * More fixes for double battles * Show battle info for both in double battles * Improvements to double battles * Add double battle version of party UI * Fix some issues with double battles * Updates to double battles * More work on double battles for stability * Fix issues with ability bar and evolution screen * Add chance for double battles
This commit is contained in:
1 parent
e2d6890072
commit
b9f7ba173d
31 files changed
+1583
-702
No files matched your search
+49
-26
@@ -1,4 +1,4 @@
|
||||
import Pokemon, { MoveResult, PokemonMove } from "../pokemon";
|
||||
import Pokemon, { HitResult, MoveResult, PokemonMove } from "../pokemon";
|
||||
import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
import { BattleStat, getBattleStatName } from "./battle-stat";
|
||||
@@ -83,6 +83,18 @@ export class BlockRecoilDamageAttr extends AbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class DoubleBattleChanceAbAttr extends AbAttr {
|
||||
constructor() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const doubleChance = (args[0] as Utils.IntegerHolder);
|
||||
doubleChance.value = Math.max(doubleChance.value / 2, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class PreDefendAbAttr extends AbAttr {
|
||||
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
return false;
|
||||
@@ -155,9 +167,10 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
|
||||
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args);
|
||||
|
||||
if (ret && pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
if (ret) {
|
||||
if (pokemon.getHpRatio() < 1)
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -181,7 +194,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
|
||||
|
||||
if (ret) {
|
||||
cancelled.value = true;
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ this.stat ], this.levels));
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -232,14 +245,14 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
|
||||
}
|
||||
|
||||
export class PostDefendAbAttr extends AbAttr {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
if (moveResult < MoveResult.NO_EFFECT) {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT) {
|
||||
const type = move.getMove().type;
|
||||
const pokemonTypes = pokemon.getTypes();
|
||||
if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) {
|
||||
@@ -267,7 +280,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.getMove().hasFlag(MoveFlags.MAKES_CONTACT) && Utils.randInt(100) < this.chance) {
|
||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[Utils.randInt(this.effects.length)];
|
||||
return attacker.trySetStatus(effect);
|
||||
@@ -290,7 +303,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
||||
this.turnCount = turnCount;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.getMove().hasFlag(MoveFlags.MAKES_CONTACT) && Utils.randInt(100) < this.chance)
|
||||
return attacker.addTag(this.tagType, this.turnCount, move.moveId, pokemon.id);
|
||||
|
||||
@@ -406,13 +419,21 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
|
||||
}
|
||||
|
||||
applyPostSummon(pokemon: Pokemon, args: any[]): boolean {
|
||||
const statChangePhase = new StatChangePhase(pokemon.scene, pokemon.isPlayer() === this.selfTarget, this.selfTarget, this.stats, this.levels);
|
||||
const statChangePhases: StatChangePhase[] = [];
|
||||
|
||||
if (!this.selfTarget && !pokemon.getOpponent()?.summonData)
|
||||
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
|
||||
else
|
||||
pokemon.scene.unshiftPhase(statChangePhase);
|
||||
if (this.selfTarget)
|
||||
statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
|
||||
else {
|
||||
for (let opponent of pokemon.getOpponents())
|
||||
statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels));
|
||||
}
|
||||
|
||||
for (let statChangePhase of statChangePhases) {
|
||||
if (!this.selfTarget && !statChangePhase.getPokemon().summonData)
|
||||
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
|
||||
else
|
||||
pokemon.scene.unshiftPhase(statChangePhase);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -576,7 +597,7 @@ export class PostTurnAbAttr extends AbAttr {
|
||||
|
||||
export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr {
|
||||
applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ BattleStat.SPD ], 1));
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.SPD ], 1));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -594,7 +615,8 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr {
|
||||
applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
|
||||
if (pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
|
||||
Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -632,7 +654,8 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
|
||||
applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean {
|
||||
if (pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
|
||||
Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -653,7 +676,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
|
||||
if (pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${pokemon.getAbility()}!`));
|
||||
scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), MoveResult.OTHER));
|
||||
scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
|
||||
pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor)));
|
||||
return true;
|
||||
}
|
||||
@@ -685,7 +708,6 @@ export function applyAbAttrs(attrType: { new(...args: any[]): AbAttr }, pokemon:
|
||||
|
||||
const ability = pokemon.getAbility();
|
||||
const attrs = ability.getAttrs(attrType) as AbAttr[];
|
||||
console.log(attrs, ability);
|
||||
for (let attr of attrs) {
|
||||
if (!canApplyAttr(pokemon, attr))
|
||||
continue;
|
||||
@@ -726,7 +748,7 @@ export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefend
|
||||
}
|
||||
|
||||
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr },
|
||||
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, ...args: any[]): void {
|
||||
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): void {
|
||||
if (!pokemon.canApplyAbility())
|
||||
return;
|
||||
|
||||
@@ -736,7 +758,7 @@ export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefe
|
||||
if (!canApplyAttr(pokemon, attr))
|
||||
continue;
|
||||
pokemon.scene.setPhaseQueueSplice();
|
||||
if (attr.applyPostDefend(pokemon, attacker, move, moveResult, args)) {
|
||||
if (attr.applyPostDefend(pokemon, attacker, move, hitResult, args)) {
|
||||
if (attr.showAbility)
|
||||
queueShowAbility(pokemon);
|
||||
const message = attr.getTriggerMessage(pokemon, attacker, move);
|
||||
@@ -988,7 +1010,7 @@ function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean {
|
||||
}
|
||||
|
||||
function queueShowAbility(pokemon: Pokemon): void {
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.scene.clearPhaseQueueSplice();
|
||||
}
|
||||
|
||||
@@ -1210,7 +1232,8 @@ export function initAbilities() {
|
||||
new Ability(Abilities.HUSTLE, "Hustle (N)", "Boosts the ATTACK stat, but lowers accuracy.", 3),
|
||||
new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter", "Prevents other POKéMON from lowering ATTACK stat.", 3)
|
||||
.attr(ProtectStatAbAttr, BattleStat.ATK),
|
||||
new Ability(Abilities.ILLUMINATE, "Illuminate (N)", "Raises the likelihood of meeting wild POKéMON.", 3),
|
||||
new Ability(Abilities.ILLUMINATE, "Illuminate", "Raises the likelihood of an encounter being a double battle.", 3)
|
||||
.attr(DoubleBattleChanceAbAttr),
|
||||
new Ability(Abilities.IMMUNITY, "Immunity", "Prevents the POKéMON from getting poisoned.", 3)
|
||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON),
|
||||
new Ability(Abilities.INNER_FOCUS, "Inner Focus", "The POKéMON is protected from flinching.", 3)
|
||||
@@ -1232,8 +1255,8 @@ export function initAbilities() {
|
||||
new Ability(Abilities.MAGMA_ARMOR, "Magma Armor", "Prevents the POKéMON from becoming frozen.", 3)
|
||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE),
|
||||
new Ability(Abilities.MAGNET_PULL, "Magnet Pull", "Prevents STEEL-type POKéMON from escaping.", 3)
|
||||
.attr(ArenaTrapAbAttr)
|
||||
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL)),
|
||||
/*.attr(ArenaTrapAbAttr)
|
||||
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL))*/, // TODO: Rework
|
||||
new Ability(Abilities.MARVEL_SCALE, "Marvel Scale (N)", "Ups DEFENSE if there is a status problem.", 3),
|
||||
new Ability(Abilities.MINUS, "Minus (N)", "Ups SP. ATK if another POKéMON has PLUS or MINUS.", 3),
|
||||
new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status problems heal when it switches out.", 3),
|
||||
|
||||
+9
-12
@@ -3,7 +3,7 @@ import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
import { Moves, allMoves } from "./move";
|
||||
import { getPokemonMessage } from "../messages";
|
||||
import Pokemon, { DamageResult, MoveResult } from "../pokemon";
|
||||
import Pokemon, { DamageResult, HitResult, MoveResult } from "../pokemon";
|
||||
import { DamagePhase, ObtainStatusEffectPhase } from "../battle-phases";
|
||||
import { StatusEffect } from "./status-effect";
|
||||
import { BattlerTagType } from "./battler-tag";
|
||||
@@ -143,8 +143,7 @@ class SpikesTag extends ArenaTrapTag {
|
||||
super.onAdd(arena);
|
||||
|
||||
const source = arena.scene.getPokemonById(this.sourceId);
|
||||
const target = source.getOpponent();
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
@@ -152,7 +151,7 @@ class SpikesTag extends ArenaTrapTag {
|
||||
const damageHpRatio = 1 / (10 - 2 * this.layers);
|
||||
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!'));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), MoveResult.OTHER));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
|
||||
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
|
||||
return true;
|
||||
}
|
||||
@@ -170,15 +169,14 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
||||
super.onAdd(arena);
|
||||
|
||||
const source = arena.scene.getPokemonById(this.sourceId);
|
||||
const target = source.getOpponent();
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
if (!pokemon.status && (!pokemon.isOfType(Type.FLYING) || pokemon.getTag(BattlerTagType.IGNORE_FLYING) || pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))) {
|
||||
const toxic = this.layers > 1;
|
||||
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(),
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
!toxic ? StatusEffect.POISON : StatusEffect.TOXIC, null, `the ${this.getMoveName()}`));
|
||||
return true;
|
||||
}
|
||||
@@ -196,8 +194,7 @@ class StealthRockTag extends ArenaTrapTag {
|
||||
super.onAdd(arena);
|
||||
|
||||
const source = arena.scene.getPokemonById(this.sourceId);
|
||||
const target = source.getOpponent();
|
||||
arena.scene.queueMessage(`Pointed stones float in the air\naround ${target.name}!`);
|
||||
arena.scene.queueMessage(`Pointed stones float in the air\naround ${source.getOpponentDescriptor()}!`);
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
@@ -228,7 +225,7 @@ class StealthRockTag extends ArenaTrapTag {
|
||||
|
||||
if (damageHpRatio) {
|
||||
pokemon.scene.queueMessage(`Pointed stones dug into\n${pokemon.name}!`);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), MoveResult.OTHER));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
|
||||
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
|
||||
}
|
||||
|
||||
@@ -242,8 +239,8 @@ export class TrickRoomTag extends ArenaTag {
|
||||
}
|
||||
|
||||
apply(args: any[]): boolean {
|
||||
const speedDelayed = args[0] as Utils.BooleanHolder;
|
||||
speedDelayed.value = !speedDelayed.value;
|
||||
const speedReversed = args[0] as Utils.BooleanHolder;
|
||||
speedReversed.value = !speedReversed.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//import { battleAnimRawData } from "./battle-anim-raw-data";
|
||||
import BattleScene from "../battle-scene";
|
||||
import { ChargeAttr, Moves, allMoves, getMoveTarget } from "./move";
|
||||
import { ChargeAttr, Moves, allMoves } from "./move";
|
||||
import Pokemon from "../pokemon";
|
||||
import * as Utils from "../utils";
|
||||
import { BattlerIndex } from "../battle";
|
||||
//import fs from 'vite-plugin-fs/browser';
|
||||
|
||||
export enum AnimFrameTarget {
|
||||
@@ -831,8 +832,8 @@ export class CommonBattleAnim extends BattleAnim {
|
||||
export class MoveAnim extends BattleAnim {
|
||||
public move: Moves;
|
||||
|
||||
constructor(move: Moves, user: Pokemon) {
|
||||
super(user, getMoveTarget(user, move));
|
||||
constructor(move: Moves, user: Pokemon, target: BattlerIndex) {
|
||||
super(user, user.scene.getField()[target]);
|
||||
|
||||
this.move = move;
|
||||
}
|
||||
@@ -852,7 +853,7 @@ export class MoveChargeAnim extends MoveAnim {
|
||||
private chargeAnim: ChargeAnim;
|
||||
|
||||
constructor(chargeAnim: ChargeAnim, move: Moves, user: Pokemon) {
|
||||
super(move, user);
|
||||
super(move, user, 0);
|
||||
|
||||
this.chargeAnim = chargeAnim;
|
||||
}
|
||||
|
||||
+18
-16
@@ -103,7 +103,7 @@ export class RechargingTag extends BattlerTag {
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
pokemon.getMoveQueue().push({ move: Moves.NONE })
|
||||
pokemon.getMoveQueue().push({ move: Moves.NONE, targets: [] })
|
||||
}
|
||||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
@@ -177,7 +177,7 @@ export class ConfusedTag extends BattlerTag {
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' became\nconfused!'));
|
||||
}
|
||||
|
||||
@@ -198,14 +198,14 @@ export class ConfusedTag extends BattlerTag {
|
||||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!'));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, 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.scene.queueMessage('It hurt itself in its\nconfusion!');
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
|
||||
}
|
||||
@@ -245,7 +245,7 @@ export class InfatuatedTag extends BattlerTag {
|
||||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.ATTRACT));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
|
||||
|
||||
if (Utils.randInt(2)) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!'));
|
||||
@@ -282,12 +282,13 @@ export class SeedTag extends BattlerTag {
|
||||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||
|
||||
if (ret) {
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, !pokemon.isPlayer(), CommonAnim.LEECH_SEED));
|
||||
const source = pokemon.scene.getPokemonById(this.sourceId);
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
|
||||
|
||||
const damage = Math.max(Math.floor(pokemon.getMaxHp() / 8), 1);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, !pokemon.isPlayer(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -320,10 +321,10 @@ export class NightmareTag extends BattlerTag {
|
||||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a NIGHTMARE!'));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CURSE)); // TODO: Update animation type
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
|
||||
|
||||
const damage = Math.ceil(pokemon.getMaxHp() / 4);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
}
|
||||
|
||||
@@ -344,7 +345,7 @@ export class IngrainTag extends TrappedTag {
|
||||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||
|
||||
if (ret)
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 16),
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), Math.floor(pokemon.getMaxHp() / 16),
|
||||
getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true));
|
||||
|
||||
return ret;
|
||||
@@ -374,7 +375,8 @@ export class AquaRingTag extends BattlerTag {
|
||||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||
|
||||
if (ret)
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -393,7 +395,7 @@ export class DrowsyTag extends BattlerTag {
|
||||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
if (!super.lapse(pokemon, lapseType)) {
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), StatusEffect.SLEEP));
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(), StatusEffect.SLEEP));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -423,10 +425,10 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
||||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), this.commonAnim));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
|
||||
|
||||
const damage = Math.ceil(pokemon.getMaxHp() / 16);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
}
|
||||
|
||||
@@ -541,7 +543,7 @@ export class TruantTag extends BattlerTag {
|
||||
|
||||
if (lastMove && lastMove.move !== Moves.NONE) {
|
||||
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!'));
|
||||
}
|
||||
|
||||
|
||||
+6
-10
@@ -1,6 +1,6 @@
|
||||
import { PokemonHealPhase, StatChangePhase } from "../battle-phases";
|
||||
import { getPokemonMessage } from "../messages";
|
||||
import Pokemon, { MoveResult } from "../pokemon";
|
||||
import Pokemon, { HitResult, MoveResult } from "../pokemon";
|
||||
import { getBattleStatName } from "./battle-stat";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
import { BattlerTagType } from "./battler-tag";
|
||||
@@ -54,12 +54,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
||||
case BerryType.LUM:
|
||||
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
|
||||
case BerryType.ENIGMA:
|
||||
return (pokemon: Pokemon) => {
|
||||
const opponent = pokemon.getOpponent();
|
||||
const opponentLastMove = opponent ? opponent.getLastXMoves(1).find(() => true) : null; // TODO: Update so this works even if opponent has fainted
|
||||
|
||||
return opponentLastMove && opponentLastMove.turn === pokemon.scene.currentBattle?.turn - 1 && opponentLastMove.result === MoveResult.SUPER_EFFECTIVE;
|
||||
};
|
||||
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
|
||||
case BerryType.LIECHI:
|
||||
case BerryType.GANLON:
|
||||
case BerryType.SALAC:
|
||||
@@ -83,7 +78,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||
case BerryType.SITRUS:
|
||||
case BerryType.ENIGMA:
|
||||
return (pokemon: Pokemon) => {
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
|
||||
};
|
||||
case BerryType.LUM:
|
||||
return (pokemon: Pokemon) => {
|
||||
@@ -101,13 +97,13 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
||||
case BerryType.APICOT:
|
||||
return (pokemon: Pokemon) => {
|
||||
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ battleStat ], 1));
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], 1));
|
||||
};
|
||||
case BerryType.LANSAT:
|
||||
return (pokemon: Pokemon) => {
|
||||
pokemon.addTag(BattlerTagType.CRIT_BOOST);
|
||||
};
|
||||
case BerryType.STARF:
|
||||
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ BattleStat.RAND ], 2));
|
||||
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], 2));
|
||||
}
|
||||
}
|
||||
+279
-45
@@ -1,9 +1,9 @@
|
||||
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
|
||||
import { DamagePhase, EnemyMovePhase, ObtainStatusEffectPhase, PlayerMovePhase, PokemonHealPhase, StatChangePhase } from "../battle-phases";
|
||||
import { DamagePhase, MovePhase, ObtainStatusEffectPhase, PokemonHealPhase, StatChangePhase } from "../battle-phases";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
import { BattlerTagType } from "./battler-tag";
|
||||
import { getPokemonMessage } from "../messages";
|
||||
import Pokemon, { AttackMoveResult, EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../pokemon";
|
||||
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../pokemon";
|
||||
import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
|
||||
import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
@@ -11,6 +11,8 @@ import { WeatherType } from "./weather";
|
||||
import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
|
||||
import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability";
|
||||
import { PokemonHeldItemModifier } from "../modifier/modifier";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Stat } from "./pokemon-stat";
|
||||
|
||||
export enum MoveCategory {
|
||||
PHYSICAL,
|
||||
@@ -24,14 +26,13 @@ export enum MoveTarget {
|
||||
ALL_OTHERS,
|
||||
NEAR_OTHER,
|
||||
ALL_NEAR_OTHERS,
|
||||
ENEMY,
|
||||
NEAR_ENEMY,
|
||||
ALL_NEAR_ENEMIES,
|
||||
RANDOM_NEAR_ENEMY,
|
||||
ALL_ENEMIES,
|
||||
ATTACKER,
|
||||
ALLY,
|
||||
NEAR_ALLY,
|
||||
ALLY,
|
||||
USER_OR_NEAR_ALLY,
|
||||
USER_AND_ALLIES,
|
||||
ALL,
|
||||
@@ -47,6 +48,7 @@ export enum MoveFlags {
|
||||
}
|
||||
|
||||
type MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||
type UserMoveCondition = (user: Pokemon, move: Move) => boolean;
|
||||
|
||||
export default class Move {
|
||||
public id: Moves;
|
||||
@@ -159,12 +161,67 @@ export default class Move {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
let score = 0;
|
||||
|
||||
for (let attr of this.attrs)
|
||||
score += attr.getUserBenefitScore(user, target, move);
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
let score = 0;
|
||||
|
||||
for (let attr of this.attrs)
|
||||
score += attr.getTargetBenefitScore(user, target, move);
|
||||
|
||||
return score;
|
||||
}
|
||||
}
|
||||
|
||||
export class AttackMove extends Move {
|
||||
constructor(id: Moves, name: string, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, tm: integer, effect: string, chance: integer, priority: integer, generation: integer) {
|
||||
super(id, name, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, tm, effect, chance, priority, generation);
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
let ret = super.getTargetBenefitScore(user, target, move);
|
||||
|
||||
let attackScore = 0;
|
||||
|
||||
const effectiveness = target.getAttackMoveEffectiveness(this.type);
|
||||
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
|
||||
if (attackScore) {
|
||||
if (this.category === MoveCategory.PHYSICAL) {
|
||||
if (user.getBattleStat(Stat.ATK) > user.getBattleStat(Stat.SPATK)) {
|
||||
const statRatio = user.getBattleStat(Stat.SPATK) / user.getBattleStat(Stat.ATK);
|
||||
if (statRatio <= 0.75)
|
||||
attackScore *= 2;
|
||||
else if (statRatio <= 0.875)
|
||||
attackScore *= 1.5;
|
||||
}
|
||||
} else {
|
||||
if (user.getBattleStat(Stat.SPATK) > user.getBattleStat(Stat.ATK)) {
|
||||
const statRatio = user.getBattleStat(Stat.ATK) / user.getBattleStat(Stat.SPATK);
|
||||
if (statRatio <= 0.75)
|
||||
attackScore *= 2;
|
||||
else if (statRatio <= 0.875)
|
||||
attackScore *= 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
const power = new Utils.NumberHolder(this.power);
|
||||
applyMoveAttrs(VariablePowerAttr, user, target, move, power);
|
||||
|
||||
attackScore += Math.floor(power.value / 5);
|
||||
}
|
||||
|
||||
ret -= attackScore;
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusMove extends Move {
|
||||
@@ -755,6 +812,14 @@ export abstract class MoveAttr {
|
||||
getCondition(): MoveCondition {
|
||||
return null;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveEffectAttr extends MoveAttr {
|
||||
@@ -792,6 +857,10 @@ export class HighCritAttr extends MoveAttr {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
export class CritOnlyAttr extends MoveAttr {
|
||||
@@ -800,6 +869,10 @@ export class CritOnlyAttr extends MoveAttr {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
export class FixedDamageAttr extends MoveAttr {
|
||||
@@ -889,12 +962,16 @@ export class RecoilAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
|
||||
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) / 4), 1);
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), MoveResult.OTHER));
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
|
||||
user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
|
||||
user.damage(recoilDamage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return Math.floor((move.power / 5) / -4);
|
||||
}
|
||||
}
|
||||
|
||||
export class SacrificialAttr extends MoveEffectAttr {
|
||||
@@ -906,11 +983,15 @@ export class SacrificialAttr extends MoveEffectAttr {
|
||||
if (!super.apply(user, target, move, args))
|
||||
return false;
|
||||
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), MoveResult.OTHER));
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
|
||||
user.damage(user.getMaxHp());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return Math.ceil((1 - user.getHpRatio()) * 10) - 10;
|
||||
}
|
||||
}
|
||||
|
||||
export enum MultiHitType {
|
||||
@@ -936,7 +1017,12 @@ export class HealAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
addHealPhase(user: Pokemon, healRatio: number) {
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
|
||||
Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return (1 - (this.selfTarget ? user : target).getHpRatio()) * 20;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,9 +1063,14 @@ export class HitHealAttr extends MoveHitEffectAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
|
||||
Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * ((move.power / 5) / 4));
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiHitAttr extends MoveAttr {
|
||||
@@ -1016,6 +1107,10 @@ export class MultiHitAttr extends MoveAttr {
|
||||
(args[0] as Utils.IntegerHolder).value = hitTimes;
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusEffectAttr extends MoveHitEffectAttr {
|
||||
@@ -1034,12 +1129,16 @@ export class StatusEffectAttr extends MoveHitEffectAttr {
|
||||
if (statusCheck) {
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) {
|
||||
user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.isPlayer(), this.effect, this.cureTurn));
|
||||
user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.getBattlerIndex(), this.effect, this.cureTurn));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return !(this.selfTarget ? user : target).status ? Math.floor(move.chance * -0.1) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class StealHeldItemAttr extends MoveHitEffectAttr {
|
||||
@@ -1048,8 +1147,7 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const heldItems = user.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
if (heldItems.length) {
|
||||
const stolenItem = heldItems[Utils.randInt(heldItems.length)];
|
||||
user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false);
|
||||
@@ -1060,6 +1158,21 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
return heldItems.length ? 5 : 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
return heldItems.length ? -5 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class HealStatusEffectAttr extends MoveEffectAttr {
|
||||
@@ -1086,6 +1199,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return user.status ? 10 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class BypassSleepAttr extends MoveAttr {
|
||||
@@ -1170,8 +1287,8 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
|
||||
user.addTag(this.tagType, 1, move.id, user.id);
|
||||
if (this.chargeEffect)
|
||||
applyMoveAttrs(MoveEffectAttr, user, target, move);
|
||||
user.pushMoveHistory({ move: move.id, result: MoveResult.OTHER });
|
||||
user.getMoveQueue().push({ move: move.id, ignorePP: true });
|
||||
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
|
||||
user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
|
||||
resolve(true);
|
||||
});
|
||||
} else
|
||||
@@ -1214,7 +1331,7 @@ export class StatChangeAttr extends MoveEffectAttr {
|
||||
|
||||
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
|
||||
const levels = this.getLevels(user);
|
||||
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, this.selfTarget, this.stats, levels));
|
||||
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1224,6 +1341,12 @@ export class StatChangeAttr extends MoveEffectAttr {
|
||||
getLevels(_user: Pokemon): integer {
|
||||
return this.levels;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
// TODO: Add awareness of level limits
|
||||
const levels = this.getLevels(user);
|
||||
return (levels * 4) + (levels > 0 ? -2 : 2);
|
||||
}
|
||||
}
|
||||
|
||||
export class GrowthStatChangeAttr extends StatChangeAttr {
|
||||
@@ -1273,7 +1396,7 @@ export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultipl
|
||||
let count = 0;
|
||||
let turnMove: TurnMove;
|
||||
|
||||
while (((turnMove = moveHistory.shift())?.move === move.id || (comboMoves.length && comboMoves.indexOf(turnMove?.move) > -1)) && (!resetOnFail || turnMove.result < MoveResult.NO_EFFECT)) {
|
||||
while (((turnMove = moveHistory.shift())?.move === move.id || (comboMoves.length && comboMoves.indexOf(turnMove?.move) > -1)) && (!resetOnFail || turnMove.result === MoveResult.SUCCESS)) {
|
||||
if (count < (limit - 1))
|
||||
count++;
|
||||
else if (resetOnLimit)
|
||||
@@ -1458,16 +1581,16 @@ export class BlizzardAccuracyAttr extends VariableAccuracyAttr {
|
||||
}
|
||||
|
||||
export class MissEffectAttr extends MoveAttr {
|
||||
private missEffectFunc: MoveCondition;
|
||||
private missEffectFunc: UserMoveCondition;
|
||||
|
||||
constructor(missEffectFunc: MoveCondition) {
|
||||
constructor(missEffectFunc: UserMoveCondition) {
|
||||
super();
|
||||
|
||||
this.missEffectFunc = missEffectFunc;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
this.missEffectFunc(user, target, move);
|
||||
this.missEffectFunc(user, move);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1530,7 +1653,7 @@ export class FrenzyAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
||||
return !!(this.selfTarget ? user.hp : target.hp);
|
||||
return !(this.selfTarget ? user : target).isFainted();
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
@@ -1540,7 +1663,7 @@ export class FrenzyAttr extends MoveEffectAttr {
|
||||
if (!user.getMoveQueue().length) {
|
||||
if (!user.getTag(BattlerTagType.FRENZY)) {
|
||||
const turnCount = Utils.randInt(2) + 1;
|
||||
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, ignorePP: true }));
|
||||
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
|
||||
user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id);
|
||||
} else {
|
||||
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
|
||||
@@ -1553,7 +1676,7 @@ export class FrenzyAttr extends MoveEffectAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export const frenzyMissFunc: MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
export const frenzyMissFunc: UserMoveCondition = (user: Pokemon, move: Move) => {
|
||||
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id)
|
||||
user.getMoveQueue().shift();
|
||||
user.lapseTag(BattlerTagType.FRENZY);
|
||||
@@ -1596,6 +1719,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
||||
? (user: Pokemon, target: Pokemon, move: Move) => !(this.selfTarget ? user : target).getTag(this.tagType)
|
||||
: null;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
switch (this.tagType) {
|
||||
case BattlerTagType.FLINCHED:
|
||||
return -5;
|
||||
case BattlerTagType.CONFUSED:
|
||||
return -5;
|
||||
case BattlerTagType.INFATUATED:
|
||||
return -5;
|
||||
case BattlerTagType.SEEDED:
|
||||
return -3;
|
||||
case BattlerTagType.NIGHTMARE:
|
||||
return -5;
|
||||
case BattlerTagType.FRENZY:
|
||||
return -2;
|
||||
case BattlerTagType.INGRAIN:
|
||||
return 3;
|
||||
case BattlerTagType.AQUA_RING:
|
||||
return 3;
|
||||
case BattlerTagType.DROWSY:
|
||||
return -5;
|
||||
case BattlerTagType.TRAPPED:
|
||||
case BattlerTagType.BIND:
|
||||
case BattlerTagType.WRAP:
|
||||
case BattlerTagType.FIRE_SPIN:
|
||||
case BattlerTagType.WHIRLPOOL:
|
||||
case BattlerTagType.CLAMP:
|
||||
case BattlerTagType.SAND_TOMB:
|
||||
case BattlerTagType.MAGMA_STORM:
|
||||
return -3;
|
||||
case BattlerTagType.PROTECTED:
|
||||
return 10;
|
||||
case BattlerTagType.FLYING:
|
||||
return 5;
|
||||
case BattlerTagType.CRIT_BOOST:
|
||||
return 5;
|
||||
case BattlerTagType.NO_CRIT:
|
||||
return -5;
|
||||
case BattlerTagType.IGNORE_ACCURACY:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LapseBattlerTagAttr extends MoveEffectAttr {
|
||||
@@ -1646,7 +1811,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
||||
let timesUsed = 0;
|
||||
const moveHistory = user.getLastXMoves(-1);
|
||||
let turnMove: TurnMove;
|
||||
while (moveHistory.length && (turnMove = moveHistory.shift()).move === move.id && turnMove.result === MoveResult.STATUS)
|
||||
while (moveHistory.length && (turnMove = moveHistory.shift()).move === move.id && turnMove.result === MoveResult.SUCCESS)
|
||||
timesUsed++;
|
||||
if (timesUsed)
|
||||
return !Utils.randInt(Math.pow(2, timesUsed));
|
||||
@@ -1680,6 +1845,10 @@ export class HitsTagAttr extends MoveAttr {
|
||||
this.tagType = tagType;
|
||||
this.doubleDamage = !!doubleDamage;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class AddArenaTagAttr extends MoveEffectAttr {
|
||||
@@ -1768,10 +1937,16 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
|
||||
if (moves.length) {
|
||||
const move = moves[Utils.randInt(moves.length)];
|
||||
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId);
|
||||
user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset });
|
||||
user.scene.unshiftPhase(user.isPlayer()
|
||||
? new PlayerMovePhase(user.scene, user as PlayerPokemon, moveset[moveIndex], true)
|
||||
: new EnemyMovePhase(user.scene, user as EnemyPokemon, moveset[moveIndex], true));
|
||||
const moveTargets = getMoveTargets(user, move.moveId);
|
||||
if (!moveTargets.targets.length)
|
||||
return false;
|
||||
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
||||
? moveTargets.targets
|
||||
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
||||
? [ target.getBattlerIndex() ]
|
||||
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
|
||||
user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: this.enemyMoveset });
|
||||
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1784,10 +1959,19 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
|
||||
return new Promise(resolve => {
|
||||
const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL));
|
||||
const moveId = moveIds[Utils.randInt(moveIds.length)];
|
||||
user.getMoveQueue().push({ move: moveId, ignorePP: true });
|
||||
user.scene.unshiftPhase(user.isPlayer()
|
||||
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId, 0, 0, true), true)
|
||||
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId, 0, 0, true), true));
|
||||
|
||||
const moveTargets = getMoveTargets(user, moveId);
|
||||
if (!moveTargets.targets.length) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
||||
? moveTargets.targets
|
||||
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
||||
? [ target.getBattlerIndex() ]
|
||||
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
|
||||
user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true });
|
||||
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true));
|
||||
initMoveAnim(moveId).then(() => {
|
||||
loadMoveAnimAssets(user.scene, [ moveId ], true)
|
||||
.then(() => resolve(true));
|
||||
@@ -1822,10 +2006,18 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
|
||||
|
||||
const copiedMove = targetMoves[0];
|
||||
|
||||
user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true });
|
||||
user.scene.unshiftPhase(user.isPlayer()
|
||||
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true)
|
||||
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true));
|
||||
const moveTargets = getMoveTargets(user, copiedMove.move);
|
||||
if (!moveTargets.targets.length)
|
||||
return false;
|
||||
|
||||
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
||||
? moveTargets.targets
|
||||
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
||||
? [ target.getBattlerIndex() ]
|
||||
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
|
||||
user.getMoveQueue().push({ move: copiedMove.move, targets: targets, ignorePP: true });
|
||||
|
||||
user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(copiedMove.move, 0, 0, true), true));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1932,20 +2124,60 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
|
||||
return applyMoveAttrsInternal(attrFilter, user, target, move, args);
|
||||
}
|
||||
|
||||
export function getMoveTarget(user: Pokemon, move: Moves): Pokemon {
|
||||
const moveTarget = allMoves[move].moveTarget;
|
||||
export type MoveTargetSet = {
|
||||
targets: BattlerIndex[];
|
||||
multiple: boolean;
|
||||
}
|
||||
|
||||
const other = user.getOpponent();
|
||||
export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
|
||||
const moveTarget = move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
|
||||
const opponents = user.getOpponents();
|
||||
|
||||
let set: BattlerIndex[] = [];
|
||||
let multiple = false;
|
||||
|
||||
switch (moveTarget) {
|
||||
case MoveTarget.USER:
|
||||
set = [ user.getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.NEAR_OTHER:
|
||||
case MoveTarget.OTHER:
|
||||
case MoveTarget.ALL_NEAR_OTHERS:
|
||||
case MoveTarget.ALL_OTHERS:
|
||||
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattlerIndex());
|
||||
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS
|
||||
break;
|
||||
case MoveTarget.NEAR_ENEMY:
|
||||
case MoveTarget.ALL_NEAR_ENEMIES:
|
||||
case MoveTarget.ALL_ENEMIES:
|
||||
case MoveTarget.ENEMY_SIDE:
|
||||
set = opponents.map(p => p.getBattlerIndex());
|
||||
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
|
||||
break;
|
||||
case MoveTarget.RANDOM_NEAR_ENEMY:
|
||||
set = [ opponents[Utils.randInt(opponents.length)].getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.ATTACKER:
|
||||
set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.NEAR_ALLY:
|
||||
case MoveTarget.ALLY:
|
||||
set = [ user.getAlly()?.getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.USER_OR_NEAR_ALLY:
|
||||
case MoveTarget.USER_AND_ALLIES:
|
||||
case MoveTarget.USER_SIDE:
|
||||
return user;
|
||||
default:
|
||||
return other;
|
||||
set = [ user, user.getAlly() ].map(p => p?.getBattlerIndex());
|
||||
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
|
||||
break;
|
||||
case MoveTarget.ALL:
|
||||
case MoveTarget.BOTH_SIDES:
|
||||
set = [ user, user.getAlly() ].concat(user.getOpponents()).map(p => p?.getBattlerIndex());
|
||||
multiple = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return { targets: set.filter(t => t !== undefined), multiple };
|
||||
}
|
||||
|
||||
export const allMoves = [
|
||||
@@ -2002,7 +2234,7 @@ export function initMoves() {
|
||||
.attr(MultiHitAttr, MultiHitType._2),
|
||||
new AttackMove(Moves.MEGA_KICK, "Mega Kick", Type.NORMAL, MoveCategory.PHYSICAL, 120, 75, 5, -1, "", -1, 0, 1),
|
||||
new AttackMove(Moves.JUMP_KICK, "Jump Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, "If it misses, the user loses half their HP.", -1, 0, 1)
|
||||
.attr(MissEffectAttr, (user: Pokemon, target: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.attr(MissEffectAttr, (user: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.condition(failOnGravityCondition),
|
||||
new AttackMove(Moves.ROLLING_KICK, "Rolling Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, -1, "May cause flinching.", 30, 0, 1)
|
||||
.attr(FlinchAttr),
|
||||
@@ -2070,13 +2302,15 @@ export function initMoves() {
|
||||
.target(MoveTarget.USER_SIDE),
|
||||
new AttackMove(Moves.WATER_GUN, "Water Gun", Type.WATER, MoveCategory.SPECIAL, 40, 100, 25, -1, "", -1, 0, 1),
|
||||
new AttackMove(Moves.HYDRO_PUMP, "Hydro Pump", Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, 142, "", -1, 0, 1),
|
||||
new AttackMove(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 0, 1), // TODO
|
||||
new AttackMove(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 0, 1)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.ICE_BEAM, "Ice Beam", Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 135, "May freeze opponent.", 10, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 0, 1)
|
||||
.attr(BlizzardAccuracyAttr)
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE), // TODO: 30% chance to hit protect/detect in hail
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE) // TODO: 30% chance to hit protect/detect in hail
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 0, 1)
|
||||
.attr(ConfuseAttr)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
@@ -2253,7 +2487,7 @@ export function initMoves() {
|
||||
new SelfStatusMove(Moves.SOFT_BOILED, "Soft-Boiled", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 0, 1)
|
||||
.attr(HealAttr, 0.5),
|
||||
new AttackMove(Moves.HIGH_JUMP_KICK, "High Jump Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 130, 90, 10, -1, "If it misses, the user loses half their HP.", -1, 0, 1)
|
||||
.attr(MissEffectAttr, (user: Pokemon, target: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.attr(MissEffectAttr, (user: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.condition(failOnGravityCondition),
|
||||
new StatusMove(Moves.GLARE, "Glare", Type.NORMAL, 100, 30, -1, "Paralyzes opponent.", -1, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||
@@ -2337,7 +2571,7 @@ export function initMoves() {
|
||||
.ignoresVirtual(),
|
||||
new AttackMove(Moves.TRIPLE_KICK, "Triple Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, "Hits thrice in one turn at increasing power.", -1, 0, 2)
|
||||
.attr(MultiHitAttr, MultiHitType._3_INCR)
|
||||
.attr(MissEffectAttr, (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
.attr(MissEffectAttr, (user: Pokemon, move: Move) => {
|
||||
user.turnData.hitsLeft = 0;
|
||||
return true;
|
||||
}),
|
||||
|
||||
+3
-10
@@ -100,17 +100,10 @@ export class Weather {
|
||||
}
|
||||
|
||||
isEffectSuppressed(scene: BattleScene): boolean {
|
||||
const playerPokemon = scene.getPlayerPokemon();
|
||||
const enemyPokemon = scene.getEnemyPokemon();
|
||||
const field = scene.getField().filter(p => p);
|
||||
|
||||
if (playerPokemon) {
|
||||
const suppressWeatherEffectAbAttr = playerPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
|
||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (enemyPokemon) {
|
||||
const suppressWeatherEffectAbAttr = enemyPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
|
||||
for (let pokemon of field) {
|
||||
const suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
|
||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user