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:
Samuel H authored and GitHub committed 2023-05-18 11:11:06 -04:00
1 parent e2d6890072
commit b9f7ba173d
31 files changed
+1583 -702

No files matched your search

+279 -45
View File
@@ -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;
}),