Improvements to double battles

This commit is contained in:
Flashfyre 2023-05-15 12:25:19 -04:00
parent afc37ab45d
commit 38005e9cba
13 changed files with 429 additions and 196 deletions

View File

@ -1,11 +1,11 @@
import BattleScene, { maxExpLevel, startingLevel, startingWave } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition } from "./pokemon";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult } from "./pokemon";
import * as Utils from './utils';
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr } from "./data/move";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets } from "./data/move";
import { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat";
import { BerryModifier, ContactHeldItemTransferChanceModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HeldItemTransferModifier, HitHealModifier, MapModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { BerryModifier, ContactHeldItemTransferChanceModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, MapModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballName, getPokeballTintColor, PokeballType } from "./data/pokeball";
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims";
@ -28,7 +28,7 @@ import { ArenaTagType, ArenaTrapTag, TrickRoomTag } from "./data/arena-tag";
import { CheckTrappedAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, SuppressWeatherEffectAbAttr, applyCheckTrappedAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./arena";
import { BattleTarget } from "./battle";
import { BattleTarget, TurnCommand } from "./battle";
export class CheckLoadPhase extends BattlePhase {
private loaded: boolean;
@ -472,21 +472,28 @@ export class SummonPhase extends PartyMemberPokemonPhase {
} else
playerPokemon.setFieldPosition(!this.scene.currentBattle.double ? FieldPosition.CENTER : FieldPosition.LEFT);
const xOffset = playerPokemon.getFieldPositionOffset()[0];
pokeball.setVisible(true);
this.scene.tweens.add({
targets: pokeball,
duration: 650,
x: 100 + xOffset
});
this.scene.tweens.add({
targets: pokeball,
ease: 'Cubic.easeOut',
duration: 150,
x: 54,
ease: 'Cubic.easeOut',
y: 70,
onComplete: () => {
this.scene.tweens.add({
targets: pokeball,
duration: 500,
angle: 1440,
x: 100,
y: 132,
ease: 'Cubic.easeIn',
angle: 1440,
y: 132,
onComplete: () => {
this.scene.sound.play('pb_rel');
pokeball.destroy();
@ -734,11 +741,15 @@ export class CommandPhase extends FieldPhase {
switch (command) {
case Command.FIGHT:
const targetIndex = Utils.randInt(enemyField.length); // TODO: Let user select this
if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean)) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.FIGHT, cursor: cursor,
move: cursor > -1 ? { move: playerPokemon.moveset[cursor].moveId } : null, targetIndex: targetIndex, args: args }; // TODO: Struggle logic
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor,
move: cursor > -1 ? { move: playerPokemon.moveset[cursor].moveId } : null, args: args }; // TODO: Struggle logic
const moveTargets = getMoveTargets(playerPokemon, playerPokemon.moveset[cursor].moveId);
if (moveTargets.targets.length <= 1 || moveTargets.multiple)
turnCommand.targets = moveTargets.targets;
else
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex));
this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
success = true;
} else if (cursor < playerPokemon.getMoveset().length) {
const move = playerPokemon.getMoveset()[cursor];
@ -761,8 +772,8 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.setMode(Mode.COMMAND);
}, null, true);
} else if (cursor < 4) {
const targetIndex = Utils.randInt(enemyField.length); // TODO: Let user select this
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor, targetIndex: targetIndex };
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor };
this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex))
success = true;
}
break;
@ -814,14 +825,39 @@ export class EnemyCommandPhase extends FieldPhase {
super.start();
const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex];
const playerField = this.scene.getPlayerField();
this.scene.currentBattle.turnCommands[this.fieldIndex + BattleTarget.ENEMY] = { command: Command.FIGHT, move: enemyPokemon.getNextMove(), targetIndex: Utils.randInt(playerField.length) }
const nextMove = enemyPokemon.getNextMove();
const moveTargets = getMoveTargets(enemyPokemon, nextMove.move);
this.scene.currentBattle.turnCommands[this.fieldIndex + BattleTarget.ENEMY] =
{ command: Command.FIGHT, move: nextMove, targets: !moveTargets.multiple ? [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ] : moveTargets.targets };
this.end();
}
}
export class SelectTargetPhase extends PokemonPhase {
constructor(scene: BattleScene, fieldIndex: integer) {
super(scene, true, fieldIndex);
}
start() {
super.start();
const move = this.scene.currentBattle.turnCommands[this.fieldIndex].move?.move || Moves.NONE;
this.scene.ui.setMode(Mode.TARGET_SELECT, this.fieldIndex, move, (cursor: integer) => {
this.scene.ui.setMode(Mode.MESSAGE);
if (cursor === -1) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = null;
this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex));
} else
this.scene.currentBattle.turnCommands[this.fieldIndex].targets = [ cursor ];
console.log(cursor, this.fieldIndex, this.scene.currentBattle.turnCommands[this.fieldIndex].targets)
this.end();
});
}
}
export class TurnStartPhase extends FieldPhase {
constructor(scene: BattleScene) {
super(scene);
@ -874,16 +910,16 @@ export class TurnStartPhase extends FieldPhase {
const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move);
if (pokemon.isPlayer()) {
if (turnCommand.cursor === -1)
this.scene.pushPhase(new PlayerMovePhase(this.scene, pokemon as PlayerPokemon, turnCommand.targetIndex, move));
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets, move));
else {
const playerPhase = new PlayerMovePhase(this.scene, pokemon as PlayerPokemon, turnCommand.targetIndex, move, false, queuedMove.ignorePP);
const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets, move, false, queuedMove.ignorePP);
this.scene.pushPhase(playerPhase);
}
} else
this.scene.pushPhase(new EnemyMovePhase(this.scene, pokemon as EnemyPokemon, turnCommand.targetIndex, move, false, queuedMove.ignorePP));
this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets, move, false, queuedMove.ignorePP));
break;
case Command.BALL:
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targetIndex, turnCommand.cursor));
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets[0] % 2, turnCommand.cursor));
break;
case Command.POKEMON:
case Command.RUN:
@ -996,29 +1032,27 @@ export class CommonAnimPhase extends PokemonPhase {
}
}
export abstract class MovePhase extends BattlePhase {
export class MovePhase extends BattlePhase {
protected pokemon: Pokemon;
protected targetIndex: integer;
protected targets: BattleTarget[];
protected move: PokemonMove;
protected followUp: boolean;
protected ignorePp: boolean;
protected cancelled: boolean;
constructor(scene: BattleScene, pokemon: Pokemon, targetIndex: integer, move: PokemonMove, followUp?: boolean, ignorePp?: boolean) {
constructor(scene: BattleScene, pokemon: Pokemon, targets: BattleTarget[], move: PokemonMove, followUp?: boolean, ignorePp?: boolean) {
super(scene);
this.pokemon = pokemon;
this.targetIndex = targetIndex;
this.targets = targets;
this.move = move;
this.followUp = !!followUp;
this.ignorePp = !!ignorePp;
this.cancelled = false;
}
abstract getEffectPhase(): MoveEffectPhase;
canMove(): boolean {
return !!this.pokemon.hp && this.move.isUsable(this.ignorePp);
return !!this.pokemon.hp && this.move.isUsable(this.ignorePp) && !!this.targets.length;
}
cancel(): void {
@ -1030,6 +1064,8 @@ export abstract class MovePhase extends BattlePhase {
console.log(Moves[this.move.moveId]);
console.log(this.scene.currentBattle.turnCommands);
if (!this.canMove()) {
if (this.move.isDisabled())
this.scene.queueMessage(`${this.move.getName()} is disabled!`);
@ -1037,7 +1073,9 @@ export abstract class MovePhase extends BattlePhase {
return;
}
const target = this.pokemon.getOpponent(this.targetIndex);
console.log(this.targets);
const targets = this.scene.getField().filter(p => p && p.hp && this.targets.indexOf(p.getBattleTarget()) > -1);
if (!this.followUp && this.canMove())
this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
@ -1045,13 +1083,13 @@ export abstract class MovePhase extends BattlePhase {
const doMove = () => {
const moveQueue = this.pokemon.getMoveQueue();
if (moveQueue.length && moveQueue[0].move === Moves.NONE) {
if ((moveQueue.length && moveQueue[0].move === Moves.NONE) || !targets.length) {
moveQueue.shift();
this.cancel();
}
if (this.cancelled) {
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAILED });
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
this.end();
return;
}
@ -1060,13 +1098,14 @@ export abstract class MovePhase extends BattlePhase {
if (!moveQueue.length || !moveQueue.shift().ignorePP)
this.move.ppUsed++;
let success = this.move.getMove().applyConditions(this.pokemon, target, this.move.getMove());
// Assume conditions affecting targets only apply to moves with a single target
let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove());
if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove()))
success = false;
if (success)
this.scene.unshiftPhase(this.getEffectPhase());
else {
this.pokemon.pushMoveHistory({ move: this.move.moveId, result: MoveResult.FAILED, virtual: this.move.virtual });
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
this.scene.queueMessage('But it failed!');
}
@ -1086,7 +1125,7 @@ export abstract class MovePhase extends BattlePhase {
}
break;
case StatusEffect.SLEEP:
applyMoveAttrs(BypassSleepAttr, this.pokemon, target, this.move.getMove());
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove());
healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn;
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
this.cancelled = activated;
@ -1099,7 +1138,7 @@ export abstract class MovePhase extends BattlePhase {
}
if (activated) {
this.scene.queueMessage(getPokemonMessage(this.pokemon, getStatusEffectActivationText(this.pokemon.status.effect)));
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex(), this.targetIndex, CommonAnim.POISON + (this.pokemon.status.effect - 1)));
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex(), this.pokemon.getBattleTarget(), CommonAnim.POISON + (this.pokemon.status.effect - 1)));
doMove();
} else {
if (healed) {
@ -1113,6 +1152,10 @@ export abstract class MovePhase extends BattlePhase {
doMove();
}
getEffectPhase(): MoveEffectPhase {
return new MoveEffectPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex(), this.targets, this.move);
}
end() {
if (!this.followUp && this.canMove())
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex()));
@ -1121,46 +1164,27 @@ export abstract class MovePhase extends BattlePhase {
}
}
export class PlayerMovePhase extends MovePhase {
constructor(scene: BattleScene, pokemon: PlayerPokemon, targetIndex: integer, move: PokemonMove, followUp?: boolean, ignorePp?: boolean) {
super(scene, pokemon, targetIndex, move, followUp, ignorePp);
}
getEffectPhase(): MoveEffectPhase {
return new PlayerMoveEffectPhase(this.scene, this.pokemon.getFieldIndex(), this.targetIndex, this.move);
}
}
export class EnemyMovePhase extends MovePhase {
constructor(scene: BattleScene, pokemon: EnemyPokemon, targetIndex: integer, move: PokemonMove, followUp?: boolean, ignorePp?: boolean) {
super(scene, pokemon, targetIndex, move, followUp, ignorePp);
}
getEffectPhase(): MoveEffectPhase {
return new EnemyMoveEffectPhase(this.scene, this.pokemon.getFieldIndex(), this.targetIndex, this.move);
}
}
abstract class MoveEffectPhase extends PokemonPhase {
class MoveEffectPhase extends PokemonPhase {
protected move: PokemonMove;
protected targetIndex: integer;
protected targets: BattleTarget[];
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, targetIndex: integer, move: PokemonMove) {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, targets: BattleTarget[], move: PokemonMove) {
super(scene, player, fieldIndex);
this.move = move;
this.targetIndex = targetIndex;
this.targets = targets;
}
start() {
super.start();
const user = this.getUserPokemon();
const target = this.getTargetPokemon();
const targets = this.getTargets();
const overridden = new Utils.BooleanHolder(false);
applyMoveAttrs(OverrideMoveEffectAttr, user, target, this.move.getMove(), overridden).then(() => {
// Assume single target for override
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getTarget(), this.move.getMove(), overridden).then(() => {
if (overridden.value) {
this.end();
@ -1171,39 +1195,57 @@ abstract class MoveEffectPhase extends PokemonPhase {
if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1);
applyMoveAttrs(MultiHitAttr, user, target, this.move.getMove(), hitCount);
// Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), this.move.getMove(), hitCount);
user.turnData.hitCount = 0;
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value;
}
if (!this.hitCheck()) {
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
user.pushMoveHistory(moveHistoryEntry);
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattleTarget(), this.hitCheck(p) ]));
if (targets.length === 1 && !targetHitChecks[this.targets[0]]) {
this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));
user.pushMoveHistory({ move: this.move.moveId, result: MoveResult.MISSED, virtual: this.move.virtual });
applyMoveAttrs(MissEffectAttr, user, target, this.move.getMove());
moveHistoryEntry.result = MoveResult.MISS;
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
this.end();
return;
}
const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.lapseTag(BattlerTagType.PROTECTED);
new MoveAnim(this.move.getMove().id as Moves, user, this.targetIndex).play(this.scene, () => {
const result = !isProtected ? target.apply(user, this.move) : MoveResult.NO_EFFECT;
user.pushMoveHistory({ move: this.move.moveId, result: result, virtual: this.move.virtual });
if (result !== MoveResult.NO_EFFECT && result !== MoveResult.FAILED) {
applyMoveAttrs(MoveEffectAttr, user, target, this.move.getMove());
if (result < MoveResult.NO_EFFECT) {
const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
if (flinched.value)
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
// Move animation only needs one target
new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattleTarget()).play(this.scene, () => {
for (let target of targets) {
if (!targetHitChecks[target.getBattleTarget()]) {
this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));
if (moveHistoryEntry.result === MoveResult.PENDING)
moveHistoryEntry.result = MoveResult.MISS;
applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
continue;
}
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
if (!isProtected && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove());
if (target.hp)
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, result);
if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT))
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.lapseTag(BattlerTagType.PROTECTED);
moveHistoryEntry.result = MoveResult.SUCCESS;
const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT;
if (hitResult !== HitResult.NO_EFFECT && hitResult !== HitResult.FAIL) {
applyMoveAttrs(MoveEffectAttr, user, target, this.move.getMove());
if (hitResult < HitResult.NO_EFFECT) {
const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
if (flinched.value)
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
}
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
if (!isProtected && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove());
if (target.hp)
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult);
if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT))
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
}
}
}
this.end();
@ -1213,7 +1255,7 @@ abstract class MoveEffectPhase extends PokemonPhase {
end() {
const user = this.getUserPokemon();
if (--user.turnData.hitsLeft >= 1 && this.getTargetPokemon().hp)
if (--user.turnData.hitsLeft >= 1 && this.getTarget()?.hp)
this.scene.unshiftPhase(this.getNewHitPhase());
else {
if (user.turnData.hitCount > 1)
@ -1224,11 +1266,11 @@ abstract class MoveEffectPhase extends PokemonPhase {
super.end();
}
hitCheck(): boolean {
hitCheck(target: Pokemon): boolean {
if (this.move.getMove().moveTarget === MoveTarget.USER)
return true;
const hiddenTag = this.getTargetPokemon().getTag(HiddenTag);
const hiddenTag = target.getTag(HiddenTag);
if (hiddenTag) {
if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length)
return false;
@ -1242,14 +1284,14 @@ abstract class MoveEffectPhase extends PokemonPhase {
if (moveAccuracy.value === -1)
return true;
applyMoveAttrs(VariableAccuracyAttr, this.getUserPokemon(), this.getTargetPokemon(), this.move.getMove(), moveAccuracy);
applyMoveAttrs(VariableAccuracyAttr, this.getUserPokemon(), target, this.move.getMove(), moveAccuracy);
if (!this.move.getMove().getAttrs(OneHitKOAttr).length && this.scene.arena.getTag(ArenaTagType.GRAVITY))
moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67);
if (this.move.getMove().category !== MoveCategory.STATUS) {
const userAccuracyLevel = new Utils.IntegerHolder(this.getUserPokemon().summonData.battleStats[BattleStat.ACC]);
const targetEvasionLevel = new Utils.IntegerHolder(this.getTargetPokemon().summonData.battleStats[BattleStat.EVA]);
const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]);
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel);
const rand = Utils.randInt(100, 1);
let accuracyMultiplier = 1;
@ -1264,40 +1306,20 @@ abstract class MoveEffectPhase extends PokemonPhase {
return true;
}
abstract getUserPokemon(): Pokemon;
getTargetPokemon(): Pokemon {
return this.getUserPokemon().getOpponent(this.targetIndex);
}
abstract getNewHitPhase(): MoveEffectPhase;
}
export class PlayerMoveEffectPhase extends MoveEffectPhase {
constructor(scene: BattleScene, fieldIndex: integer, targetIndex: integer, move: PokemonMove) {
super(scene, true, fieldIndex, targetIndex, move);
}
getUserPokemon(): Pokemon {
return this.scene.getPlayerField()[this.fieldIndex];
return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex];
}
getTargets(): Pokemon[] {
return this.scene.getField().filter(p => this.targets.indexOf(p.getBattleTarget()) > -1);
}
getTarget(): Pokemon {
return this.getTargets().find(() => true);
}
getNewHitPhase() {
return new PlayerMoveEffectPhase(this.scene, this.fieldIndex, this.targetIndex, this.move);
}
}
export class EnemyMoveEffectPhase extends MoveEffectPhase {
constructor(scene: BattleScene, fieldIndex: integer, targetIndex: integer, move: PokemonMove) {
super(scene, false, fieldIndex, targetIndex, move);
}
getUserPokemon(): Pokemon {
return this.scene.getEnemyField()[this.fieldIndex];
}
getNewHitPhase() {
return new EnemyMoveEffectPhase(this.scene, this.fieldIndex, this.targetIndex, this.move);
return new MoveEffectPhase(this.scene, this.player, this.fieldIndex, this.targets, this.move);
}
}
@ -1371,6 +1393,8 @@ export class StatChangePhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, selfTarget: boolean, stats: BattleStat[], levels: integer) {
super(scene, player, fieldIndex);
console.log(this.player, this.fieldIndex);
const allStats = Utils.getEnumValues(BattleStat);
this.selfTarget = selfTarget;
this.stats = stats.map(s => s !== BattleStat.RAND ? s : allStats[Utils.randInt(BattleStat.SPD + 1)]);
@ -1594,25 +1618,25 @@ export class DamagePhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, damageResult?: DamageResult) {
super(scene, player, fieldIndex);
this.damageResult = damageResult || MoveResult.EFFECTIVE;
this.damageResult = damageResult || HitResult.EFFECTIVE;
}
start() {
super.start();
switch (this.damageResult) {
case MoveResult.EFFECTIVE:
case HitResult.EFFECTIVE:
this.scene.sound.play('hit');
break;
case MoveResult.SUPER_EFFECTIVE:
case HitResult.SUPER_EFFECTIVE:
this.scene.sound.play('hit_strong');
break;
case MoveResult.NOT_VERY_EFFECTIVE:
case HitResult.NOT_VERY_EFFECTIVE:
this.scene.sound.play('hit_weak');
break;
}
if (this.damageResult !== MoveResult.OTHER) {
if (this.damageResult !== HitResult.OTHER) {
const flashTimer = this.scene.time.addEvent({
delay: 100,
repeat: 5,

View File

@ -847,7 +847,7 @@ export default class BattleScene extends Phaser.Scene {
count = Math.max(count, Math.floor(chances / 2));
const enemyField = this.getEnemyField();
getEnemyModifierTypesForWave(waveIndex, count, this.getEnemyField())
.map(mt => mt.newModifier(enemyField[enemyField.length === 1 ? 0 : Utils.randInt(enemyField.length)]).add(this.enemyModifiers, false));
.map(mt => mt.newModifier(enemyField[Utils.randInt(enemyField.length)]).add(this.enemyModifiers, false));
this.updateModifiers(false).then(() => resolve());
});

View File

@ -9,11 +9,11 @@ export enum BattleTarget {
ENEMY_2
}
interface TurnCommand {
export interface TurnCommand {
command: Command;
cursor?: integer;
move?: QueuedMove;
targetIndex?: integer;
targets?: BattleTarget[];
args?: any[];
};

View File

@ -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";
@ -232,14 +232,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 +267,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 +290,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);
@ -654,7 +654,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(), pokemon.getFieldIndex(), MoveResult.OTHER));
scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), HitResult.OTHER));
pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor)));
return true;
}
@ -727,7 +727,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;
@ -737,7 +737,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);

View File

@ -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";
@ -151,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(), pokemon.getFieldIndex(), MoveResult.OTHER));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), HitResult.OTHER));
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
return true;
}
@ -225,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(), pokemon.getFieldIndex(), MoveResult.OTHER));
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), pokemon.getFieldIndex(), HitResult.OTHER));
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
}

View File

@ -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 { BattleTarget } 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, targetIndex: integer) {
super(user, getMoveTarget(user, targetIndex, move));
constructor(move: Moves, user: Pokemon, target: BattleTarget) {
super(user, user.scene.getField()[target]);
this.move = move;
}

View File

@ -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,7 +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) => !!pokemon.turnData.attacksReceived.filter(a => a.result === MoveResult.SUPER_EFFECTIVE).length;
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
case BerryType.LIECHI:
case BerryType.GANLON:
case BerryType.SALAC:

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,7 @@ import { WeatherType } from "./weather";
import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability";
import { PokemonHeldItemModifier } from "../modifier/modifier";
import { BattleTarget } from "../battle";
export enum MoveCategory {
PHYSICAL,
@ -24,14 +25,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 +47,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;
@ -889,7 +890,7 @@ 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(), user.getFieldIndex(), MoveResult.OTHER));
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), user.getFieldIndex(), HitResult.OTHER));
user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
user.damage(recoilDamage);
@ -906,7 +907,7 @@ export class SacrificialAttr extends MoveEffectAttr {
if (!super.apply(user, target, move, args))
return false;
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), user.getFieldIndex(), MoveResult.OTHER));
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), user.getFieldIndex(), HitResult.OTHER));
user.damage(user.getMaxHp());
return true;
@ -1172,7 +1173,7 @@ 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.pushMoveHistory({ move: move.id, targets: [ target.getBattleTarget() ], result: MoveResult.OTHER });
user.getMoveQueue().push({ move: move.id, ignorePP: true });
resolve(true);
});
@ -1275,7 +1276,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)
@ -1460,16 +1461,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;
}
}
@ -1555,7 +1556,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);
@ -1648,7 +1649,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));
@ -1771,9 +1772,11 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
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, target.getFieldIndex(), moveset[moveIndex], true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), moveset[moveIndex], true));
const moveTargets = getMoveTargets(user, move.moveId);
if (!moveTargets.targets.length)
return false;
const targets = moveTargets.multiple ? moveTargets.targets : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true));
return true;
}
@ -1787,9 +1790,13 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
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, target.getFieldIndex(), new PokemonMove(moveId, 0, 0, true), true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), 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 : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
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));
@ -1825,9 +1832,14 @@ 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, target.getFieldIndex(), new PokemonMove(copiedMove.move, 0, 0, true), true)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), 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 : [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(copiedMove.move, 0, 0, true), true));
return true;
}
@ -1934,20 +1946,64 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
return applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export function getMoveTarget(user: Pokemon, targetIndex: integer, move: Moves): Pokemon {
const moveTarget = allMoves[move].moveTarget;
export type MoveTargetSet = {
targets: BattleTarget[];
multiple: boolean;
}
const other = user.getOpponent(targetIndex);
export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
const moveTarget = move ? allMoves[move].moveTarget : MoveTarget.NEAR_ENEMY;
const opponents = user.getOpponents();
let set: BattleTarget[];
let multiple = false;
switch (moveTarget) {
case MoveTarget.USER:
set = [ user.getBattleTarget() ];
break;
case MoveTarget.OTHER:
set = user.getLastXMoves().find(() => true)?.targets || [];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattleTarget());
multiple = moveTarget !== MoveTarget.NEAR_OTHER;
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents.map(p => p.getBattleTarget());
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [ opponents[Utils.randInt(opponents.length)].getBattleTarget() ];
break;
case MoveTarget.ATTACKER:
set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattleTarget() ];
break;
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = [ user.getAlly()?.getBattleTarget() ];
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?.getBattleTarget());
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?.getBattleTarget());
multiple = true;
break;
}
console.log(set, multiple, MoveTarget[moveTarget]);
return { targets: set.filter(t => t !== undefined), multiple };
}
export const allMoves = [
@ -2004,7 +2060,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),
@ -2072,13 +2128,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),
@ -2255,7 +2313,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),
@ -2339,7 +2397,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;
}),

View File

@ -25,6 +25,7 @@ import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag';
import { Biome } from './data/biome';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, PreApplyBattlerTagAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import PokemonData from './system/pokemon-data';
import { BattleTarget } from './battle';
export enum FieldPosition {
CENTER,
@ -191,6 +192,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
abstract getFieldIndex(): integer;
abstract getBattleTarget(): BattleTarget;
loadAssets(): Promise<void> {
return new Promise(resolve => {
const moveIds = this.getMoveset().map(m => m.getMove().id);
@ -572,8 +575,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.isPlayer() ? 'the opposing team' : 'your team';
}
apply(source: Pokemon, battlerMove: PokemonMove): MoveResult {
let result: MoveResult;
getAlly(): Pokemon {
return (this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.getFieldIndex() ? 0 : 1];
}
apply(source: Pokemon, battlerMove: PokemonMove): HitResult {
let result: HitResult;
const move = battlerMove.getMove();
const moveCategory = move.category;
let damage = 0;
@ -595,7 +602,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
if (cancelled.value)
result = MoveResult.NO_EFFECT;
result = HitResult.NO_EFFECT;
else {
if (source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === move.type))
power.value *= 1.5;
@ -636,20 +643,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (damage && fixedDamage.value) {
damage = fixedDamage.value;
isCritical = false;
result = MoveResult.EFFECTIVE;
result = HitResult.EFFECTIVE;
}
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
if (!result) {
if (typeMultiplier.value >= 2)
result = MoveResult.SUPER_EFFECTIVE;
result = HitResult.SUPER_EFFECTIVE;
else if (typeMultiplier.value >= 1)
result = MoveResult.EFFECTIVE;
result = HitResult.EFFECTIVE;
else if (typeMultiplier.value > 0)
result = MoveResult.NOT_VERY_EFFECTIVE;
result = HitResult.NOT_VERY_EFFECTIVE;
else
result = MoveResult.NO_EFFECT;
result = HitResult.NO_EFFECT;
}
if (damage) {
@ -662,20 +669,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
switch (result) {
case MoveResult.SUPER_EFFECTIVE:
case HitResult.SUPER_EFFECTIVE:
this.scene.queueMessage('It\'s super effective!');
break;
case MoveResult.NOT_VERY_EFFECTIVE:
case HitResult.NOT_VERY_EFFECTIVE:
this.scene.queueMessage('It\'s not very effective!');
break;
case MoveResult.NO_EFFECT:
case HitResult.NO_EFFECT:
this.scene.queueMessage(`It doesn\'t affect ${this.name}!`);
break;
}
}
break;
case MoveCategory.STATUS:
result = MoveResult.STATUS;
result = HitResult.STATUS;
break;
}
@ -1018,6 +1025,10 @@ export class PlayerPokemon extends Pokemon {
return this.scene.getPlayerField().indexOf(this);
}
getBattleTarget(): BattleTarget {
return this.getFieldIndex();
}
generateCompatibleTms(): void {
this.compatibleTms = [];
@ -1203,6 +1214,10 @@ export class EnemyPokemon extends Pokemon {
return this.scene.getEnemyField().indexOf(this);
}
getBattleTarget(): BattleTarget {
return BattleTarget.ENEMY + this.getFieldIndex();
}
addToParty() {
const party = this.scene.getParty();
let ret: PlayerPokemon = null;
@ -1219,6 +1234,7 @@ export class EnemyPokemon extends Pokemon {
export interface TurnMove {
move: Moves;
targets?: BattleTarget[];
result: MoveResult;
virtual?: boolean;
turn?: integer;
@ -1264,17 +1280,25 @@ export enum AiType {
}
export enum MoveResult {
PENDING,
SUCCESS,
FAIL,
MISS,
OTHER
}
export enum HitResult {
EFFECTIVE = 1,
SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE,
NO_EFFECT,
STATUS,
FAILED,
MISSED,
FAIL,
MISS,
OTHER
}
export type DamageResult = MoveResult.EFFECTIVE | MoveResult.SUPER_EFFECTIVE | MoveResult.NOT_VERY_EFFECTIVE | MoveResult.OTHER;
export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.OTHER;
export class PokemonMove {
public moveId: Moves;

View File

@ -24,7 +24,7 @@ export function initAutoPlay() {
const commandUiHandler = this.ui.handlers[Mode.COMMAND] as CommandUiHandler;
const fightUiHandler = this.ui.handlers[Mode.FIGHT] as FightUiHandler;
const partyUiHandler = this.ui.handlers[Mode.PARTY] as PartyUiHandler;
const switchCheckUiHandler = this.ui.handlers[Mode.CONFIRM] as ConfirmUiHandler;
const confirmUiHandler = this.ui.handlers[Mode.CONFIRM] as ConfirmUiHandler;
const modifierSelectUiHandler = this.ui.handlers[Mode.MODIFIER_SELECT] as ModifierSelectUiHandler;
const getBestPartyMemberIndex = () => {
@ -153,15 +153,15 @@ export function initAutoPlay() {
}
};
const originalSwitchCheckUiHandlerShow = switchCheckUiHandler.show;
switchCheckUiHandler.show = function (args: any[]) {
const originalSwitchCheckUiHandlerShow = confirmUiHandler.show;
confirmUiHandler.show = function (args: any[]) {
originalSwitchCheckUiHandlerShow.apply(this, [ args ]);
if (thisArg.auto) {
const bestPartyMemberIndex = getBestPartyMemberIndex();
thisArg.time.delayedCall(20, () => {
if (bestPartyMemberIndex)
nextPartyMemberIndex = bestPartyMemberIndex;
switchCheckUiHandler.setCursor(bestPartyMemberIndex ? 1 : 0);
confirmUiHandler.setCursor(bestPartyMemberIndex ? 1 : 0);
thisArg.time.delayedCall(20, () => this.processInput(Button.ACTION));
});
}

View File

@ -0,0 +1,121 @@
import { BattleTarget } from "../battle";
import BattleScene, { Button } from "../battle-scene";
import { Moves, getMoveTargets } from "../data/move";
import { Mode } from "./ui";
import UiHandler from "./uiHandler";
import * as Utils from "../utils";
export type TargetSelectCallback = (cursor: integer) => void;
export default class TargetSelectUiHandler extends UiHandler {
private fieldIndex: integer;
private move: Moves;
private targetSelectCallback: TargetSelectCallback;
private targets: BattleTarget[];
private targetFlashTween: Phaser.Tweens.Tween;
constructor(scene: BattleScene) {
super(scene, Mode.TARGET_SELECT);
}
setup(): void { }
show(args: any[]) {
if (args.length < 3)
return;
super.show(args);
this.fieldIndex = args[0] as integer;
this.move = args[1] as Moves;
this.targetSelectCallback = args[2] as TargetSelectCallback;
this.targets = getMoveTargets(this.scene.getPlayerField()[this.fieldIndex], this.move).targets;
if (!this.targets.length)
return;
console.log(this.targets);
this.setCursor(this.targets.indexOf(this.cursor) > -1 ? this.cursor : this.targets[0]);
}
processInput(button: Button) {
const ui = this.getUi();
let success = false;
if (button === Button.ACTION || button === Button.CANCEL) {
this.targetSelectCallback(button === Button.ACTION ? this.cursor : -1);
success = true;
} else {
switch (button) {
case Button.UP:
if (this.cursor < BattleTarget.ENEMY && this.targets.find(t => t >= BattleTarget.ENEMY))
success = this.setCursor(this.targets.find(t => t >= BattleTarget.ENEMY));
break;
case Button.DOWN:
if (this.cursor >= BattleTarget.ENEMY && this.targets.find(t => t < BattleTarget.ENEMY))
success = this.setCursor(this.targets.find(t => t < BattleTarget.ENEMY));
break;
case Button.LEFT:
if (this.cursor % 2 && this.targets.find(t => t === this.cursor - 1))
success = this.setCursor(this.cursor - 1);
break;
case Button.RIGHT:
if (!(this.cursor % 2) && this.targets.find(t => t === this.cursor + 1))
success = this.setCursor(this.cursor + 1);
break;
}
}
if (success)
ui.playSelect();
}
setCursor(cursor: integer): boolean {
const lastCursor = this.cursor;
const ret = super.setCursor(cursor);
if (this.targetFlashTween) {
this.targetFlashTween.stop();
const lastTarget = this.scene.getField()[lastCursor];
if (lastTarget)
lastTarget.setAlpha(1);
}
const target = this.scene.getField()[cursor];
this.targetFlashTween = this.scene.tweens.add({
targets: [ target ],
alpha: 0,
loop: -1,
duration: new Utils.FixedInt(250) as unknown as integer,
ease: 'Sine.easeIn',
yoyo: true,
onUpdate: t => {
if (target)
target.setAlpha(t.getValue());
}
});
return ret;
}
eraseCursor() {
const target = this.scene.getField()[this.cursor];
if (this.targetFlashTween) {
this.targetFlashTween.stop();
this.targetFlashTween = null;
}
if (target)
target.setAlpha(1);
}
clear() {
super.clear();
this.eraseCursor();
}
}

View File

@ -12,12 +12,14 @@ import SummaryUiHandler from './summary-ui-handler';
import StarterSelectUiHandler from './starter-select-ui-handler';
import EvolutionSceneHandler from './evolution-scene-handler';
import BiomeSelectUiHandler from './biome-select-ui-handler';
import TargetSelectUiHandler from './target-select-ui-handler';
export enum Mode {
MESSAGE,
COMMAND,
FIGHT,
BALL,
TARGET_SELECT,
MODIFIER_SELECT,
PARTY,
SUMMARY,
@ -54,6 +56,7 @@ export default class UI extends Phaser.GameObjects.Container {
new CommandUiHandler(scene),
new FightUiHandler(scene),
new BallUiHandler(scene),
new TargetSelectUiHandler(scene),
new ModifierSelectUiHandler(scene),
new PartyUiHandler(scene),
new SummaryUiHandler(scene),

View File

@ -25,6 +25,8 @@ export function padInt(value: integer, length: integer, padWith?: string): strin
export function randInt(range: integer, min?: integer): integer {
if (!min)
min = 0;
if (range === 1)
return min;
return Math.floor(Math.random() * range) + min;
}