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 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 * 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 { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat"; 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 PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballName, getPokeballTintColor, PokeballType } from "./data/pokeball"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballName, getPokeballTintColor, PokeballType } from "./data/pokeball";
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; 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 { 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 { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./arena"; import { getBiomeKey } from "./arena";
import { BattleTarget } from "./battle"; import { BattleTarget, TurnCommand } from "./battle";
export class CheckLoadPhase extends BattlePhase { export class CheckLoadPhase extends BattlePhase {
private loaded: boolean; private loaded: boolean;
@ -472,21 +472,28 @@ export class SummonPhase extends PartyMemberPokemonPhase {
} else } else
playerPokemon.setFieldPosition(!this.scene.currentBattle.double ? FieldPosition.CENTER : FieldPosition.LEFT); playerPokemon.setFieldPosition(!this.scene.currentBattle.double ? FieldPosition.CENTER : FieldPosition.LEFT);
const xOffset = playerPokemon.getFieldPositionOffset()[0];
pokeball.setVisible(true); pokeball.setVisible(true);
this.scene.tweens.add({
targets: pokeball,
duration: 650,
x: 100 + xOffset
});
this.scene.tweens.add({ this.scene.tweens.add({
targets: pokeball, targets: pokeball,
ease: 'Cubic.easeOut',
duration: 150, duration: 150,
x: 54, ease: 'Cubic.easeOut',
y: 70, y: 70,
onComplete: () => { onComplete: () => {
this.scene.tweens.add({ this.scene.tweens.add({
targets: pokeball, targets: pokeball,
duration: 500, duration: 500,
angle: 1440,
x: 100,
y: 132,
ease: 'Cubic.easeIn', ease: 'Cubic.easeIn',
angle: 1440,
y: 132,
onComplete: () => { onComplete: () => {
this.scene.sound.play('pb_rel'); this.scene.sound.play('pb_rel');
pokeball.destroy(); pokeball.destroy();
@ -734,11 +741,15 @@ export class CommandPhase extends FieldPhase {
switch (command) { switch (command) {
case Command.FIGHT: case Command.FIGHT:
const targetIndex = Utils.randInt(enemyField.length); // TODO: Let user select this
if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean)) { if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean)) {
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.FIGHT, cursor: cursor, const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor,
move: cursor > -1 ? { move: playerPokemon.moveset[cursor].moveId } : null, targetIndex: targetIndex, args: args }; // TODO: Struggle logic 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; success = true;
} else if (cursor < playerPokemon.getMoveset().length) { } else if (cursor < playerPokemon.getMoveset().length) {
const move = playerPokemon.getMoveset()[cursor]; const move = playerPokemon.getMoveset()[cursor];
@ -761,8 +772,8 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.setMode(Mode.COMMAND); this.scene.ui.setMode(Mode.COMMAND);
}, null, true); }, null, true);
} else if (cursor < 4) { } 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 };
this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor, targetIndex: targetIndex }; this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex))
success = true; success = true;
} }
break; break;
@ -814,14 +825,39 @@ export class EnemyCommandPhase extends FieldPhase {
super.start(); super.start();
const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; 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(); 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 { export class TurnStartPhase extends FieldPhase {
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
super(scene); 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); const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move);
if (pokemon.isPlayer()) { if (pokemon.isPlayer()) {
if (turnCommand.cursor === -1) 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 { 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); this.scene.pushPhase(playerPhase);
} }
} else } 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; break;
case Command.BALL: 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; break;
case Command.POKEMON: case Command.POKEMON:
case Command.RUN: 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 pokemon: Pokemon;
protected targetIndex: integer; protected targets: BattleTarget[];
protected move: PokemonMove; protected move: PokemonMove;
protected followUp: boolean; protected followUp: boolean;
protected ignorePp: boolean; protected ignorePp: boolean;
protected cancelled: 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); super(scene);
this.pokemon = pokemon; this.pokemon = pokemon;
this.targetIndex = targetIndex; this.targets = targets;
this.move = move; this.move = move;
this.followUp = !!followUp; this.followUp = !!followUp;
this.ignorePp = !!ignorePp; this.ignorePp = !!ignorePp;
this.cancelled = false; this.cancelled = false;
} }
abstract getEffectPhase(): MoveEffectPhase;
canMove(): boolean { 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 { cancel(): void {
@ -1030,6 +1064,8 @@ export abstract class MovePhase extends BattlePhase {
console.log(Moves[this.move.moveId]); console.log(Moves[this.move.moveId]);
console.log(this.scene.currentBattle.turnCommands);
if (!this.canMove()) { if (!this.canMove()) {
if (this.move.isDisabled()) if (this.move.isDisabled())
this.scene.queueMessage(`${this.move.getName()} is disabled!`); this.scene.queueMessage(`${this.move.getName()} is disabled!`);
@ -1037,7 +1073,9 @@ export abstract class MovePhase extends BattlePhase {
return; 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()) if (!this.followUp && this.canMove())
this.pokemon.lapseTags(BattlerTagLapseType.MOVE); this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
@ -1045,13 +1083,13 @@ export abstract class MovePhase extends BattlePhase {
const doMove = () => { const doMove = () => {
const moveQueue = this.pokemon.getMoveQueue(); 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(); moveQueue.shift();
this.cancel(); this.cancel();
} }
if (this.cancelled) { if (this.cancelled) {
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAILED }); this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL });
this.end(); this.end();
return; return;
} }
@ -1060,13 +1098,14 @@ export abstract class MovePhase extends BattlePhase {
if (!moveQueue.length || !moveQueue.shift().ignorePP) if (!moveQueue.length || !moveQueue.shift().ignorePP)
this.move.ppUsed++; 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())) if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove()))
success = false; success = false;
if (success) if (success)
this.scene.unshiftPhase(this.getEffectPhase()); this.scene.unshiftPhase(this.getEffectPhase());
else { 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!'); this.scene.queueMessage('But it failed!');
} }
@ -1086,7 +1125,7 @@ export abstract class MovePhase extends BattlePhase {
} }
break; break;
case StatusEffect.SLEEP: 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; healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn;
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
this.cancelled = activated; this.cancelled = activated;
@ -1099,7 +1138,7 @@ export abstract class MovePhase extends BattlePhase {
} }
if (activated) { if (activated) {
this.scene.queueMessage(getPokemonMessage(this.pokemon, getStatusEffectActivationText(this.pokemon.status.effect))); 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(); doMove();
} else { } else {
if (healed) { if (healed) {
@ -1113,6 +1152,10 @@ export abstract class MovePhase extends BattlePhase {
doMove(); doMove();
} }
getEffectPhase(): MoveEffectPhase {
return new MoveEffectPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex(), this.targets, this.move);
}
end() { end() {
if (!this.followUp && this.canMove()) if (!this.followUp && this.canMove())
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.isPlayer(), this.pokemon.getFieldIndex())); 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 { class MoveEffectPhase extends PokemonPhase {
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 {
protected move: PokemonMove; 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); super(scene, player, fieldIndex);
this.move = move; this.move = move;
this.targetIndex = targetIndex; this.targets = targets;
} }
start() { start() {
super.start(); super.start();
const user = this.getUserPokemon(); const user = this.getUserPokemon();
const target = this.getTargetPokemon(); const targets = this.getTargets();
const overridden = new Utils.BooleanHolder(false); 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) { if (overridden.value) {
this.end(); this.end();
@ -1171,39 +1195,57 @@ abstract class MoveEffectPhase extends PokemonPhase {
if (user.turnData.hitsLeft === undefined) { if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1); 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.hitCount = 0;
user.turnData.hitsLeft = user.turnData.hitCount = hitCount.value; 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!')); this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));
user.pushMoveHistory({ move: this.move.moveId, result: MoveResult.MISSED, virtual: this.move.virtual }); moveHistoryEntry.result = MoveResult.MISS;
applyMoveAttrs(MissEffectAttr, user, target, this.move.getMove()); applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
this.end(); this.end();
return; return;
} }
const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.lapseTag(BattlerTagType.PROTECTED); // Move animation only needs one target
new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattleTarget()).play(this.scene, () => {
new MoveAnim(this.move.getMove().id as Moves, user, this.targetIndex).play(this.scene, () => { for (let target of targets) {
const result = !isProtected ? target.apply(user, this.move) : MoveResult.NO_EFFECT; if (!targetHitChecks[target.getBattleTarget()]) {
user.pushMoveHistory({ move: this.move.moveId, result: result, virtual: this.move.virtual }); this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));
if (result !== MoveResult.NO_EFFECT && result !== MoveResult.FAILED) { if (moveHistoryEntry.result === MoveResult.PENDING)
applyMoveAttrs(MoveEffectAttr, user, target, this.move.getMove()); moveHistoryEntry.result = MoveResult.MISS;
if (result < MoveResult.NO_EFFECT) { applyMoveAttrs(MissEffectAttr, user, null, this.move.getMove());
const flinched = new Utils.BooleanHolder(false); continue;
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) { const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.lapseTag(BattlerTagType.PROTECTED);
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove());
if (target.hp) moveHistoryEntry.result = MoveResult.SUCCESS;
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, result);
if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT;
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
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(); this.end();
@ -1213,7 +1255,7 @@ abstract class MoveEffectPhase extends PokemonPhase {
end() { end() {
const user = this.getUserPokemon(); 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()); this.scene.unshiftPhase(this.getNewHitPhase());
else { else {
if (user.turnData.hitCount > 1) if (user.turnData.hitCount > 1)
@ -1224,11 +1266,11 @@ abstract class MoveEffectPhase extends PokemonPhase {
super.end(); super.end();
} }
hitCheck(): boolean { hitCheck(target: Pokemon): boolean {
if (this.move.getMove().moveTarget === MoveTarget.USER) if (this.move.getMove().moveTarget === MoveTarget.USER)
return true; return true;
const hiddenTag = this.getTargetPokemon().getTag(HiddenTag); const hiddenTag = target.getTag(HiddenTag);
if (hiddenTag) { if (hiddenTag) {
if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length) if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length)
return false; return false;
@ -1242,14 +1284,14 @@ abstract class MoveEffectPhase extends PokemonPhase {
if (moveAccuracy.value === -1) if (moveAccuracy.value === -1)
return true; 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)) if (!this.move.getMove().getAttrs(OneHitKOAttr).length && this.scene.arena.getTag(ArenaTagType.GRAVITY))
moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67); moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67);
if (this.move.getMove().category !== MoveCategory.STATUS) { if (this.move.getMove().category !== MoveCategory.STATUS) {
const userAccuracyLevel = new Utils.IntegerHolder(this.getUserPokemon().summonData.battleStats[BattleStat.ACC]); 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); this.scene.applyModifiers(TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel);
const rand = Utils.randInt(100, 1); const rand = Utils.randInt(100, 1);
let accuracyMultiplier = 1; let accuracyMultiplier = 1;
@ -1264,40 +1306,20 @@ abstract class MoveEffectPhase extends PokemonPhase {
return true; 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 { 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() { getNewHitPhase() {
return new PlayerMoveEffectPhase(this.scene, this.fieldIndex, this.targetIndex, this.move); return new MoveEffectPhase(this.scene, this.player, this.fieldIndex, this.targets, 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);
} }
} }
@ -1371,6 +1393,8 @@ export class StatChangePhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean, fieldIndex: integer, selfTarget: boolean, stats: BattleStat[], levels: integer) { constructor(scene: BattleScene, player: boolean, fieldIndex: integer, selfTarget: boolean, stats: BattleStat[], levels: integer) {
super(scene, player, fieldIndex); super(scene, player, fieldIndex);
console.log(this.player, this.fieldIndex);
const allStats = Utils.getEnumValues(BattleStat); const allStats = Utils.getEnumValues(BattleStat);
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
this.stats = stats.map(s => s !== BattleStat.RAND ? s : allStats[Utils.randInt(BattleStat.SPD + 1)]); 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) { constructor(scene: BattleScene, player: boolean, fieldIndex: integer, damageResult?: DamageResult) {
super(scene, player, fieldIndex); super(scene, player, fieldIndex);
this.damageResult = damageResult || MoveResult.EFFECTIVE; this.damageResult = damageResult || HitResult.EFFECTIVE;
} }
start() { start() {
super.start(); super.start();
switch (this.damageResult) { switch (this.damageResult) {
case MoveResult.EFFECTIVE: case HitResult.EFFECTIVE:
this.scene.sound.play('hit'); this.scene.sound.play('hit');
break; break;
case MoveResult.SUPER_EFFECTIVE: case HitResult.SUPER_EFFECTIVE:
this.scene.sound.play('hit_strong'); this.scene.sound.play('hit_strong');
break; break;
case MoveResult.NOT_VERY_EFFECTIVE: case HitResult.NOT_VERY_EFFECTIVE:
this.scene.sound.play('hit_weak'); this.scene.sound.play('hit_weak');
break; break;
} }
if (this.damageResult !== MoveResult.OTHER) { if (this.damageResult !== HitResult.OTHER) {
const flashTimer = this.scene.time.addEvent({ const flashTimer = this.scene.time.addEvent({
delay: 100, delay: 100,
repeat: 5, repeat: 5,

View File

@ -847,7 +847,7 @@ export default class BattleScene extends Phaser.Scene {
count = Math.max(count, Math.floor(chances / 2)); count = Math.max(count, Math.floor(chances / 2));
const enemyField = this.getEnemyField(); const enemyField = this.getEnemyField();
getEnemyModifierTypesForWave(waveIndex, count, 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()); this.updateModifiers(false).then(() => resolve());
}); });

View File

@ -9,11 +9,11 @@ export enum BattleTarget {
ENEMY_2 ENEMY_2
} }
interface TurnCommand { export interface TurnCommand {
command: Command; command: Command;
cursor?: integer; cursor?: integer;
move?: QueuedMove; move?: QueuedMove;
targetIndex?: integer; targets?: BattleTarget[];
args?: any[]; 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 { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { BattleStat, getBattleStatName } from "./battle-stat"; import { BattleStat, getBattleStatName } from "./battle-stat";
@ -232,14 +232,14 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
} }
export class PostDefendAbAttr extends AbAttr { 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; return false;
} }
} }
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
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 (moveResult < MoveResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT) {
const type = move.getMove().type; const type = move.getMove().type;
const pokemonTypes = pokemon.getTypes(); const pokemonTypes = pokemon.getTypes();
if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) { if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) {
@ -267,7 +267,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
this.effects = effects; 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) { 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)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[Utils.randInt(this.effects.length)];
return attacker.trySetStatus(effect); return attacker.trySetStatus(effect);
@ -290,7 +290,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
this.turnCount = turnCount; 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) if (move.getMove().hasFlag(MoveFlags.MAKES_CONTACT) && Utils.randInt(100) < this.chance)
return attacker.addTag(this.tagType, this.turnCount, move.moveId, pokemon.id); return attacker.addTag(this.tagType, this.turnCount, move.moveId, pokemon.id);
@ -654,7 +654,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; const scene = pokemon.scene;
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${pokemon.getAbility()}!`)); 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))); pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor)));
return true; return true;
} }
@ -727,7 +727,7 @@ export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefend
} }
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr }, 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()) if (!pokemon.canApplyAbility())
return; return;
@ -737,7 +737,7 @@ export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefe
if (!canApplyAttr(pokemon, attr)) if (!canApplyAttr(pokemon, attr))
continue; continue;
pokemon.scene.setPhaseQueueSplice(); pokemon.scene.setPhaseQueueSplice();
if (attr.applyPostDefend(pokemon, attacker, move, moveResult, args)) { if (attr.applyPostDefend(pokemon, attacker, move, hitResult, args)) {
if (attr.showAbility) if (attr.showAbility)
queueShowAbility(pokemon); queueShowAbility(pokemon);
const message = attr.getTriggerMessage(pokemon, attacker, move); const message = attr.getTriggerMessage(pokemon, attacker, move);

View File

@ -3,7 +3,7 @@ import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { Moves, allMoves } from "./move"; import { Moves, allMoves } from "./move";
import { getPokemonMessage } from "../messages"; import { getPokemonMessage } from "../messages";
import Pokemon, { DamageResult, MoveResult } from "../pokemon"; import Pokemon, { DamageResult, HitResult, MoveResult } from "../pokemon";
import { DamagePhase, ObtainStatusEffectPhase } from "../battle-phases"; import { DamagePhase, ObtainStatusEffectPhase } from "../battle-phases";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import { BattlerTagType } from "./battler-tag"; import { BattlerTagType } from "./battler-tag";
@ -151,7 +151,7 @@ class SpikesTag extends ArenaTrapTag {
const damageHpRatio = 1 / (10 - 2 * this.layers); const damageHpRatio = 1 / (10 - 2 * this.layers);
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!')); 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)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
return true; return true;
} }
@ -225,7 +225,7 @@ class StealthRockTag extends ArenaTrapTag {
if (damageHpRatio) { if (damageHpRatio) {
pokemon.scene.queueMessage(`Pointed stones dug into\n${pokemon.name}!`); 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)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
} }

View File

@ -1,8 +1,9 @@
//import { battleAnimRawData } from "./battle-anim-raw-data"; //import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import { ChargeAttr, Moves, allMoves, getMoveTarget } from "./move"; import { ChargeAttr, Moves, allMoves } from "./move";
import Pokemon from "../pokemon"; import Pokemon from "../pokemon";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { BattleTarget } from "../battle";
//import fs from 'vite-plugin-fs/browser'; //import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget { export enum AnimFrameTarget {
@ -831,8 +832,8 @@ export class CommonBattleAnim extends BattleAnim {
export class MoveAnim extends BattleAnim { export class MoveAnim extends BattleAnim {
public move: Moves; public move: Moves;
constructor(move: Moves, user: Pokemon, targetIndex: integer) { constructor(move: Moves, user: Pokemon, target: BattleTarget) {
super(user, getMoveTarget(user, targetIndex, move)); super(user, user.scene.getField()[target]);
this.move = move; this.move = move;
} }

View File

@ -1,6 +1,6 @@
import { PokemonHealPhase, StatChangePhase } from "../battle-phases"; import { PokemonHealPhase, StatChangePhase } from "../battle-phases";
import { getPokemonMessage } from "../messages"; import { getPokemonMessage } from "../messages";
import Pokemon, { MoveResult } from "../pokemon"; import Pokemon, { HitResult, MoveResult } from "../pokemon";
import { getBattleStatName } from "./battle-stat"; import { getBattleStatName } from "./battle-stat";
import { BattleStat } from "./battle-stat"; import { BattleStat } from "./battle-stat";
import { BattlerTagType } from "./battler-tag"; import { BattlerTagType } from "./battler-tag";
@ -54,7 +54,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.LUM: case BerryType.LUM:
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
case BerryType.ENIGMA: 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.LIECHI:
case BerryType.GANLON: case BerryType.GANLON:
case BerryType.SALAC: case BerryType.SALAC:

View File

@ -1,9 +1,9 @@
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; 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 { BattleStat } from "./battle-stat";
import { BattlerTagType } from "./battler-tag"; import { BattlerTagType } from "./battler-tag";
import { getPokemonMessage } from "../messages"; 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 { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
import { Type } from "./type"; import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
@ -11,6 +11,7 @@ import { WeatherType } from "./weather";
import { ArenaTagType, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability"; import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability";
import { PokemonHeldItemModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier } from "../modifier/modifier";
import { BattleTarget } from "../battle";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -24,14 +25,13 @@ export enum MoveTarget {
ALL_OTHERS, ALL_OTHERS,
NEAR_OTHER, NEAR_OTHER,
ALL_NEAR_OTHERS, ALL_NEAR_OTHERS,
ENEMY,
NEAR_ENEMY, NEAR_ENEMY,
ALL_NEAR_ENEMIES, ALL_NEAR_ENEMIES,
RANDOM_NEAR_ENEMY, RANDOM_NEAR_ENEMY,
ALL_ENEMIES, ALL_ENEMIES,
ATTACKER, ATTACKER,
ALLY,
NEAR_ALLY, NEAR_ALLY,
ALLY,
USER_OR_NEAR_ALLY, USER_OR_NEAR_ALLY,
USER_AND_ALLIES, USER_AND_ALLIES,
ALL, ALL,
@ -47,6 +47,7 @@ export enum MoveFlags {
} }
type MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean; type MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveCondition = (user: Pokemon, move: Move) => boolean;
export default class Move { export default class Move {
public id: Moves; public id: Moves;
@ -889,7 +890,7 @@ export class RecoilAttr extends MoveEffectAttr {
return false; return false;
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) / 4), 1); 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.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
user.damage(recoilDamage); user.damage(recoilDamage);
@ -906,7 +907,7 @@ export class SacrificialAttr extends MoveEffectAttr {
if (!super.apply(user, target, move, args)) if (!super.apply(user, target, move, args))
return false; 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()); user.damage(user.getMaxHp());
return true; return true;
@ -1172,7 +1173,7 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
user.addTag(this.tagType, 1, move.id, user.id); user.addTag(this.tagType, 1, move.id, user.id);
if (this.chargeEffect) if (this.chargeEffect)
applyMoveAttrs(MoveEffectAttr, user, target, move); 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 }); user.getMoveQueue().push({ move: move.id, ignorePP: true });
resolve(true); resolve(true);
}); });
@ -1275,7 +1276,7 @@ export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultipl
let count = 0; let count = 0;
let turnMove: TurnMove; 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)) if (count < (limit - 1))
count++; count++;
else if (resetOnLimit) else if (resetOnLimit)
@ -1460,16 +1461,16 @@ export class BlizzardAccuracyAttr extends VariableAccuracyAttr {
} }
export class MissEffectAttr extends MoveAttr { export class MissEffectAttr extends MoveAttr {
private missEffectFunc: MoveCondition; private missEffectFunc: UserMoveCondition;
constructor(missEffectFunc: MoveCondition) { constructor(missEffectFunc: UserMoveCondition) {
super(); super();
this.missEffectFunc = missEffectFunc; this.missEffectFunc = missEffectFunc;
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
this.missEffectFunc(user, target, move); this.missEffectFunc(user, move);
return true; 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) while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id)
user.getMoveQueue().shift(); user.getMoveQueue().shift();
user.lapseTag(BattlerTagType.FRENZY); user.lapseTag(BattlerTagType.FRENZY);
@ -1648,7 +1649,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
let timesUsed = 0; let timesUsed = 0;
const moveHistory = user.getLastXMoves(-1); const moveHistory = user.getLastXMoves(-1);
let turnMove: TurnMove; 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++; timesUsed++;
if (timesUsed) if (timesUsed)
return !Utils.randInt(Math.pow(2, 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 move = moves[Utils.randInt(moves.length)];
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId); const moveIndex = moveset.findIndex(m => m.moveId === move.moveId);
user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset }); user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset });
user.scene.unshiftPhase(user.isPlayer() const moveTargets = getMoveTargets(user, move.moveId);
? new PlayerMovePhase(user.scene, user as PlayerPokemon, target.getFieldIndex(), moveset[moveIndex], true) if (!moveTargets.targets.length)
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), moveset[moveIndex], true)); 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; 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 moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL));
const moveId = moveIds[Utils.randInt(moveIds.length)]; const moveId = moveIds[Utils.randInt(moveIds.length)];
user.getMoveQueue().push({ move: moveId, ignorePP: true }); user.getMoveQueue().push({ move: moveId, ignorePP: true });
user.scene.unshiftPhase(user.isPlayer() const moveTargets = getMoveTargets(user, moveId);
? new PlayerMovePhase(user.scene, user as PlayerPokemon, target.getFieldIndex(), new PokemonMove(moveId, 0, 0, true), true) if (!moveTargets.targets.length) {
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), new PokemonMove(moveId, 0, 0, true), true)); 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(() => { initMoveAnim(moveId).then(() => {
loadMoveAnimAssets(user.scene, [ moveId ], true) loadMoveAnimAssets(user.scene, [ moveId ], true)
.then(() => resolve(true)); .then(() => resolve(true));
@ -1825,9 +1832,14 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
const copiedMove = targetMoves[0]; const copiedMove = targetMoves[0];
user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true }); 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) const moveTargets = getMoveTargets(user, copiedMove.move);
: new EnemyMovePhase(user.scene, user as EnemyPokemon, target.getFieldIndex(), new PokemonMove(copiedMove.move, 0, 0, true), true)); 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; return true;
} }
@ -1934,20 +1946,64 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
return applyMoveAttrsInternal(attrFilter, user, target, move, args); return applyMoveAttrsInternal(attrFilter, user, target, move, args);
} }
export function getMoveTarget(user: Pokemon, targetIndex: integer, move: Moves): Pokemon { export type MoveTargetSet = {
const moveTarget = allMoves[move].moveTarget; 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) { switch (moveTarget) {
case MoveTarget.USER: 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_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES: case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE: case MoveTarget.USER_SIDE:
return user; set = [ user, user.getAlly() ].map(p => p?.getBattleTarget());
default: multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
return other; 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 = [ export const allMoves = [
@ -2004,7 +2060,7 @@ export function initMoves() {
.attr(MultiHitAttr, MultiHitType._2), .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.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) 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), .condition(failOnGravityCondition),
new AttackMove(Moves.ROLLING_KICK, "Rolling Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, -1, "May cause flinching.", 30, 0, 1) new AttackMove(Moves.ROLLING_KICK, "Rolling Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, -1, "May cause flinching.", 30, 0, 1)
.attr(FlinchAttr), .attr(FlinchAttr),
@ -2072,13 +2128,15 @@ export function initMoves() {
.target(MoveTarget.USER_SIDE), .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.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.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) 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) .attr(StatusEffectAttr, StatusEffect.FREEZE)
.target(MoveTarget.ALL_NEAR_OTHERS), .target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 0, 1) new AttackMove(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 0, 1)
.attr(BlizzardAccuracyAttr) .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) new AttackMove(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 0, 1)
.attr(ConfuseAttr) .attr(ConfuseAttr)
.target(MoveTarget.ALL_NEAR_ENEMIES), .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) 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), .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) 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), .condition(failOnGravityCondition),
new StatusMove(Moves.GLARE, "Glare", Type.NORMAL, 100, 30, -1, "Paralyzes opponent.", -1, 0, 1) new StatusMove(Moves.GLARE, "Glare", Type.NORMAL, 100, 30, -1, "Paralyzes opponent.", -1, 0, 1)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS), .attr(StatusEffectAttr, StatusEffect.PARALYSIS),
@ -2339,7 +2397,7 @@ export function initMoves() {
.ignoresVirtual(), .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) 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(MultiHitAttr, MultiHitType._3_INCR)
.attr(MissEffectAttr, (user: Pokemon, target: Pokemon, move: Move) => { .attr(MissEffectAttr, (user: Pokemon, move: Move) => {
user.turnData.hitsLeft = 0; user.turnData.hitsLeft = 0;
return true; return true;
}), }),

View File

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

View File

@ -24,7 +24,7 @@ export function initAutoPlay() {
const commandUiHandler = this.ui.handlers[Mode.COMMAND] as CommandUiHandler; const commandUiHandler = this.ui.handlers[Mode.COMMAND] as CommandUiHandler;
const fightUiHandler = this.ui.handlers[Mode.FIGHT] as FightUiHandler; const fightUiHandler = this.ui.handlers[Mode.FIGHT] as FightUiHandler;
const partyUiHandler = this.ui.handlers[Mode.PARTY] as PartyUiHandler; 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 modifierSelectUiHandler = this.ui.handlers[Mode.MODIFIER_SELECT] as ModifierSelectUiHandler;
const getBestPartyMemberIndex = () => { const getBestPartyMemberIndex = () => {
@ -153,15 +153,15 @@ export function initAutoPlay() {
} }
}; };
const originalSwitchCheckUiHandlerShow = switchCheckUiHandler.show; const originalSwitchCheckUiHandlerShow = confirmUiHandler.show;
switchCheckUiHandler.show = function (args: any[]) { confirmUiHandler.show = function (args: any[]) {
originalSwitchCheckUiHandlerShow.apply(this, [ args ]); originalSwitchCheckUiHandlerShow.apply(this, [ args ]);
if (thisArg.auto) { if (thisArg.auto) {
const bestPartyMemberIndex = getBestPartyMemberIndex(); const bestPartyMemberIndex = getBestPartyMemberIndex();
thisArg.time.delayedCall(20, () => { thisArg.time.delayedCall(20, () => {
if (bestPartyMemberIndex) if (bestPartyMemberIndex)
nextPartyMemberIndex = bestPartyMemberIndex; nextPartyMemberIndex = bestPartyMemberIndex;
switchCheckUiHandler.setCursor(bestPartyMemberIndex ? 1 : 0); confirmUiHandler.setCursor(bestPartyMemberIndex ? 1 : 0);
thisArg.time.delayedCall(20, () => this.processInput(Button.ACTION)); 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 StarterSelectUiHandler from './starter-select-ui-handler';
import EvolutionSceneHandler from './evolution-scene-handler'; import EvolutionSceneHandler from './evolution-scene-handler';
import BiomeSelectUiHandler from './biome-select-ui-handler'; import BiomeSelectUiHandler from './biome-select-ui-handler';
import TargetSelectUiHandler from './target-select-ui-handler';
export enum Mode { export enum Mode {
MESSAGE, MESSAGE,
COMMAND, COMMAND,
FIGHT, FIGHT,
BALL, BALL,
TARGET_SELECT,
MODIFIER_SELECT, MODIFIER_SELECT,
PARTY, PARTY,
SUMMARY, SUMMARY,
@ -54,6 +56,7 @@ export default class UI extends Phaser.GameObjects.Container {
new CommandUiHandler(scene), new CommandUiHandler(scene),
new FightUiHandler(scene), new FightUiHandler(scene),
new BallUiHandler(scene), new BallUiHandler(scene),
new TargetSelectUiHandler(scene),
new ModifierSelectUiHandler(scene), new ModifierSelectUiHandler(scene),
new PartyUiHandler(scene), new PartyUiHandler(scene),
new SummaryUiHandler(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 { export function randInt(range: integer, min?: integer): integer {
if (!min) if (!min)
min = 0; min = 0;
if (range === 1)
return min;
return Math.floor(Math.random() * range) + min; return Math.floor(Math.random() * range) + min;
} }