Implement Double Battles (#1)

* Add WiP logic for double battles

* Minor changes for double battles

* More fixes for double battles

* Show battle info for both in double battles

* Improvements to double battles

* Add double battle version of party UI

* Fix some issues with double battles

* Updates to double battles

* More work on double battles for stability

* Fix issues with ability bar and evolution screen

* Add chance for double battles
pull/2/head
Samuel H 2023-05-18 11:11:06 -04:00 committed by GitHub
parent e2d6890072
commit b9f7ba173d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 1583 additions and 702 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -161,7 +161,7 @@ export class Arena {
this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null; this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null;
if (this.weather) { if (this.weather) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, true, CommonAnim.SUNNY + (weather - 1))); this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather)); this.scene.queueMessage(getWeatherStartMessage(weather));
} else } else
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType)); this.scene.queueMessage(getWeatherClearMessage(oldWeatherType));

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,14 @@
import Phaser from 'phaser'; import Phaser from 'phaser';
import { Biome } from './data/biome'; import { Biome } from './data/biome';
import UI from './ui/ui'; import UI from './ui/ui';
import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, CheckLoadPhase } from './battle-phases'; import { EncounterPhase, SummonPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, CheckLoadPhase, TurnInitPhase, ReturnPhase, ToggleDoublePositionPhase, CheckSwitchPhase } from './battle-phases';
import Pokemon, { PlayerPokemon, EnemyPokemon } from './pokemon'; import Pokemon, { PlayerPokemon, EnemyPokemon, FieldPosition } from './pokemon';
import PokemonSpecies, { allSpecies, getPokemonSpecies, initSpecies } from './data/pokemon-species'; import PokemonSpecies, { allSpecies, getPokemonSpecies, initSpecies } from './data/pokemon-species';
import * as Utils from './utils'; import * as Utils from './utils';
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate } from './modifier/modifier'; import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate } from './modifier/modifier';
import { PokeballType } from './data/pokeball'; import { PokeballType } from './data/pokeball';
import { Species } from './data/species'; import { Species } from './data/species';
import { initAutoPlay } from './system/auto-play'; import { initAutoPlay } from './system/auto-play';
import { Battle } from './battle';
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './data/battle-anims'; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './data/battle-anims';
import { BattlePhase } from './battle-phase'; import { BattlePhase } from './battle-phase';
import { initGameSpeed } from './system/game-speed'; import { initGameSpeed } from './system/game-speed';
@ -20,7 +19,8 @@ import { TextStyle, addTextObject } from './ui/text';
import { Moves, initMoves } from './data/move'; import { Moves, initMoves } from './data/move';
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type';
import AbilityBar from './ui/ability-bar'; import AbilityBar from './ui/ability-bar';
import { BlockItemTheftAbAttr, applyAbAttrs, initAbilities } from './data/ability'; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, applyAbAttrs, initAbilities } from './data/ability';
import Battle from './battle';
const enableAuto = true; const enableAuto = true;
const quickStart = false; const quickStart = false;
@ -156,7 +156,8 @@ export default class BattleScene extends Phaser.Scene {
this.loadAtlas('prompt', 'ui'); this.loadAtlas('prompt', 'ui');
this.loadImage('cursor', 'ui'); this.loadImage('cursor', 'ui');
this.loadImage('pbinfo_player', 'ui'); this.loadImage('pbinfo_player', 'ui');
this.loadImage('pbinfo_enemy', 'ui'); this.loadImage('pbinfo_player_mini', 'ui');
this.loadImage('pbinfo_enemy_mini', 'ui');
this.loadImage('overlay_lv', 'ui'); this.loadImage('overlay_lv', 'ui');
this.loadAtlas('numbers', 'ui'); this.loadAtlas('numbers', 'ui');
this.loadAtlas('overlay_hp', 'ui'); this.loadAtlas('overlay_hp', 'ui');
@ -168,6 +169,7 @@ export default class BattleScene extends Phaser.Scene {
this.loadImage('boolean_window', 'ui'); this.loadImage('boolean_window', 'ui');
this.loadImage('party_bg', 'ui'); this.loadImage('party_bg', 'ui');
this.loadImage('party_bg_double', 'ui');
this.loadAtlas('party_slot_main', 'ui'); this.loadAtlas('party_slot_main', 'ui');
this.loadAtlas('party_slot', 'ui'); this.loadAtlas('party_slot', 'ui');
this.loadImage('party_slot_overlay_lv', 'ui'); this.loadImage('party_slot_overlay_lv', 'ui');
@ -209,6 +211,8 @@ export default class BattleScene extends Phaser.Scene {
this.loadImage('starter_select_gen_cursor', 'ui'); this.loadImage('starter_select_gen_cursor', 'ui');
this.loadImage('starter_select_gen_cursor_highlight', 'ui'); this.loadImage('starter_select_gen_cursor_highlight', 'ui');
this.loadImage('default_bg', 'arenas');
// Load arena images // Load arena images
Utils.getEnumValues(Biome).map(bt => { Utils.getEnumValues(Biome).map(bt => {
const btKey = Biome[bt].toLowerCase(); const btKey = Biome[bt].toLowerCase();
@ -446,21 +450,35 @@ export default class BattleScene extends Phaser.Scene {
return this.party; return this.party;
} }
getEnemyParty(): EnemyPokemon[] { getPlayerPokemon(): PlayerPokemon {
return this.getEnemyPokemon() ? [ this.getEnemyPokemon() ] : []; return this.getPlayerField().find(p => p.isActive());
} }
getPlayerPokemon(): PlayerPokemon { getPlayerField(): PlayerPokemon[] {
return this.getParty()[0]; const party = this.getParty();
return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1));
} }
getEnemyPokemon(): EnemyPokemon { getEnemyPokemon(): EnemyPokemon {
return this.currentBattle?.enemyPokemon; return this.getEnemyField().find(p => p.isActive());
}
getEnemyField(): EnemyPokemon[] {
return this.currentBattle?.enemyField || [];
}
getField(): Pokemon[] {
const ret = new Array(4).fill(null);
const playerField = this.getPlayerField();
const enemyField = this.getEnemyField();
ret.splice(0, playerField.length, ...playerField);
ret.splice(2, enemyField.length, ...enemyField);
return ret;
} }
getPokemonById(pokemonId: integer): Pokemon { getPokemonById(pokemonId: integer): Pokemon {
const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId); const findInParty = (party: Pokemon[]) => party.find(p => p.id === pokemonId);
return findInParty(this.getParty()) || findInParty(this.getEnemyParty()); return findInParty(this.getParty()) || findInParty(this.getEnemyField());
} }
reset(): void { reset(): void {
@ -475,7 +493,7 @@ export default class BattleScene extends Phaser.Scene {
for (let p of this.getParty()) for (let p of this.getParty())
p.destroy(); p.destroy();
this.party = []; this.party = [];
for (let p of this.getEnemyParty()) for (let p of this.getEnemyField())
p.destroy(); p.destroy();
this.currentBattle = null; this.currentBattle = null;
@ -493,11 +511,26 @@ export default class BattleScene extends Phaser.Scene {
this.trainer.setPosition(406, 132); this.trainer.setPosition(406, 132);
} }
newBattle(waveIndex?: integer): Battle { newBattle(waveIndex?: integer, double?: boolean): Battle {
let newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1);
let newDouble: boolean;
if (double === undefined) {
const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8);
this.getPlayerField().forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, doubleChance));
newDouble = !Utils.randInt(doubleChance.value);
} else
newDouble = double;
const lastBattle = this.currentBattle;
this.currentBattle = new Battle(newWaveIndex, newDouble);
this.currentBattle.incrementTurn(this);
if (!waveIndex) { if (!waveIndex) {
if (this.currentBattle) { if (lastBattle) {
this.getEnemyPokemon().destroy(); this.getEnemyField().forEach(enemyPokemon => enemyPokemon.destroy());
if (this.currentBattle.waveIndex % 10) if (lastBattle.waveIndex % 10)
this.pushPhase(new NextEncounterPhase(this)); this.pushPhase(new NextEncounterPhase(this));
else { else {
this.pushPhase(new SelectBiomePhase(this)); this.pushPhase(new SelectBiomePhase(this));
@ -509,12 +542,29 @@ export default class BattleScene extends Phaser.Scene {
else { else {
this.arena.playBgm(); this.arena.playBgm();
this.pushPhase(new EncounterPhase(this)); this.pushPhase(new EncounterPhase(this));
this.pushPhase(new SummonPhase(this)); this.pushPhase(new SummonPhase(this, 0));
}
} }
} }
this.currentBattle = new Battle(waveIndex || ((this.currentBattle?.waveIndex || (startingWave - 1)) + 1)); if ((lastBattle?.double || false) !== newDouble) {
const availablePartyMemberCount = this.getParty().filter(p => !p.isFainted()).length;
if (newDouble) {
this.pushPhase(new ToggleDoublePositionPhase(this, true));
if (availablePartyMemberCount > 1)
this.pushPhase(new SummonPhase(this, 1));
} else {
if (availablePartyMemberCount > 1)
this.pushPhase(new ReturnPhase(this, 1));
this.pushPhase(new ToggleDoublePositionPhase(this, false));
}
}
if (lastBattle) {
this.pushPhase(new CheckSwitchPhase(this, 0, newDouble));
if (newDouble)
this.pushPhase(new CheckSwitchPhase(this, 1, newDouble));
}
}
return this.currentBattle; return this.currentBattle;
} }
@ -699,7 +749,7 @@ export default class BattleScene extends Phaser.Scene {
} }
populatePhaseQueue(): void { populatePhaseQueue(): void {
this.phaseQueue.push(new CommandPhase(this)); this.phaseQueue.push(new TurnInitPhase(this));
} }
addModifier(modifier: Modifier, playSound?: boolean, virtual?: boolean): Promise<void> { addModifier(modifier: Modifier, playSound?: boolean, virtual?: boolean): Promise<void> {
@ -828,7 +878,9 @@ export default class BattleScene extends Phaser.Scene {
} }
if (isBoss) if (isBoss)
count = Math.max(count, Math.floor(chances / 2)); count = Math.max(count, Math.floor(chances / 2));
getEnemyModifierTypesForWave(waveIndex, count, this.getEnemyParty()).map(mt => mt.newModifier(this.getEnemyPokemon()).add(this.enemyModifiers, false)); const enemyField = this.getEnemyField();
getEnemyModifierTypesForWave(waveIndex, count, this.getEnemyField())
.map(mt => mt.newModifier(enemyField[Utils.randInt(enemyField.length)]).add(this.enemyModifiers, false));
this.updateModifiers(false).then(() => resolve()); this.updateModifiers(false).then(() => resolve());
}); });
@ -855,7 +907,7 @@ export default class BattleScene extends Phaser.Scene {
modifiers.splice(modifiers.indexOf(modifier), 1); modifiers.splice(modifiers.indexOf(modifier), 1);
} }
this.updatePartyForModifiers(player ? this.getParty() : this.getEnemyParty()).then(() => { this.updatePartyForModifiers(player ? this.getParty() : this.getEnemyField().filter(p => p.isActive())).then(() => {
(player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers); (player ? this.modifierBar : this.enemyModifierBar).updateModifiers(modifiers);
if (!player) if (!player)
this.updateWaveCountPosition(); this.updateWaveCountPosition();

View File

@ -1,18 +1,44 @@
import { EnemyPokemon, PlayerPokemon } from "./pokemon"; import BattleScene, { PokeballCounts } from "./battle-scene";
import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./pokemon";
import { Command } from "./ui/command-ui-handler";
import * as Utils from "./utils"; import * as Utils from "./utils";
export class Battle { export enum BattlerIndex {
PLAYER,
PLAYER_2,
ENEMY,
ENEMY_2
}
export interface TurnCommand {
command: Command;
cursor?: integer;
move?: QueuedMove;
targets?: BattlerIndex[];
args?: any[];
};
interface TurnCommands {
[key: integer]: TurnCommand
}
export default class Battle {
public waveIndex: integer; public waveIndex: integer;
public enemyLevel: integer; public enemyLevels: integer[];
public enemyPokemon: EnemyPokemon; public enemyField: EnemyPokemon[];
public double: boolean;
public turn: integer; public turn: integer;
public turnCommands: TurnCommands;
public turnPokeballCounts: PokeballCounts;
public playerParticipantIds: Set<integer> = new Set<integer>(); public playerParticipantIds: Set<integer> = new Set<integer>();
public escapeAttempts: integer = 0; public escapeAttempts: integer = 0;
constructor(waveIndex: integer) { constructor(waveIndex: integer, double: boolean) {
this.waveIndex = waveIndex; this.waveIndex = waveIndex;
this.enemyLevel = this.getLevelForWave(); this.enemyLevels = new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave());
this.turn = 1; this.enemyField = [];
this.double = double;
this.turn = 0;
} }
private getLevelForWave(): number { private getLevelForWave(): number {
@ -29,8 +55,14 @@ export class Battle {
return Math.max(Math.round(baseLevel + Math.abs(Utils.randGauss(deviation))), 1); return Math.max(Math.round(baseLevel + Math.abs(Utils.randGauss(deviation))), 1);
} }
incrementTurn() { getBattlerCount(): integer {
return this.double ? 2 : 1;
}
incrementTurn(scene: BattleScene): void {
this.turn++; this.turn++;
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
this.turnPokeballCounts = Object.assign({}, scene.pokeballCounts);
} }
addParticipant(playerPokemon: PlayerPokemon): void { addParticipant(playerPokemon: PlayerPokemon): void {

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";
@ -83,6 +83,18 @@ export class BlockRecoilDamageAttr extends AbAttr {
} }
} }
export class DoubleBattleChanceAbAttr extends AbAttr {
constructor() {
super(false);
}
apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const doubleChance = (args[0] as Utils.IntegerHolder);
doubleChance.value = Math.max(doubleChance.value / 2, 1);
return true;
}
}
export class PreDefendAbAttr extends AbAttr { export class PreDefendAbAttr extends AbAttr {
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
return false; return false;
@ -155,9 +167,10 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean { applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args); const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args);
if (ret && pokemon.getHpRatio() < 1) { if (ret) {
const scene = pokemon.scene; if (pokemon.getHpRatio() < 1)
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true; return true;
} }
@ -181,7 +194,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
if (ret) { if (ret) {
cancelled.value = true; cancelled.value = true;
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ this.stat ], this.levels)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
} }
return ret; return ret;
@ -232,14 +245,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 +280,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 +303,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);
@ -406,13 +419,21 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
} }
applyPostSummon(pokemon: Pokemon, args: any[]): boolean { applyPostSummon(pokemon: Pokemon, args: any[]): boolean {
const statChangePhase = new StatChangePhase(pokemon.scene, pokemon.isPlayer() === this.selfTarget, this.selfTarget, this.stats, this.levels); const statChangePhases: StatChangePhase[] = [];
if (!this.selfTarget && !pokemon.getOpponent()?.summonData) if (this.selfTarget)
statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
else {
for (let opponent of pokemon.getOpponents())
statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels));
}
for (let statChangePhase of statChangePhases) {
if (!this.selfTarget && !statChangePhase.getPokemon().summonData)
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
else else
pokemon.scene.unshiftPhase(statChangePhase); pokemon.scene.unshiftPhase(statChangePhase);
}
return true; return true;
} }
@ -576,7 +597,7 @@ export class PostTurnAbAttr extends AbAttr {
export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr { export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr {
applyPostTurn(pokemon: Pokemon, args: any[]): boolean { applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ BattleStat.SPD ], 1)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.SPD ], 1));
return true; return true;
} }
} }
@ -594,7 +615,8 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr {
applyPostTurn(pokemon: Pokemon, args: any[]): boolean { applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true; return true;
} }
@ -632,7 +654,8 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean { applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true)); scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
return true; return true;
} }
@ -653,7 +676,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(), MoveResult.OTHER)); scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor))); pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor)));
return true; return true;
} }
@ -685,7 +708,6 @@ export function applyAbAttrs(attrType: { new(...args: any[]): AbAttr }, pokemon:
const ability = pokemon.getAbility(); const ability = pokemon.getAbility();
const attrs = ability.getAttrs(attrType) as AbAttr[]; const attrs = ability.getAttrs(attrType) as AbAttr[];
console.log(attrs, ability);
for (let attr of attrs) { for (let attr of attrs) {
if (!canApplyAttr(pokemon, attr)) if (!canApplyAttr(pokemon, attr))
continue; continue;
@ -726,7 +748,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;
@ -736,7 +758,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);
@ -988,7 +1010,7 @@ function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean {
} }
function queueShowAbility(pokemon: Pokemon): void { function queueShowAbility(pokemon: Pokemon): void {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer())); pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.scene.clearPhaseQueueSplice(); pokemon.scene.clearPhaseQueueSplice();
} }
@ -1210,7 +1232,8 @@ export function initAbilities() {
new Ability(Abilities.HUSTLE, "Hustle (N)", "Boosts the ATTACK stat, but lowers accuracy.", 3), new Ability(Abilities.HUSTLE, "Hustle (N)", "Boosts the ATTACK stat, but lowers accuracy.", 3),
new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter", "Prevents other POKéMON from lowering ATTACK stat.", 3) new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter", "Prevents other POKéMON from lowering ATTACK stat.", 3)
.attr(ProtectStatAbAttr, BattleStat.ATK), .attr(ProtectStatAbAttr, BattleStat.ATK),
new Ability(Abilities.ILLUMINATE, "Illuminate (N)", "Raises the likelihood of meeting wild POKéMON.", 3), new Ability(Abilities.ILLUMINATE, "Illuminate", "Raises the likelihood of an encounter being a double battle.", 3)
.attr(DoubleBattleChanceAbAttr),
new Ability(Abilities.IMMUNITY, "Immunity", "Prevents the POKéMON from getting poisoned.", 3) new Ability(Abilities.IMMUNITY, "Immunity", "Prevents the POKéMON from getting poisoned.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON), .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON),
new Ability(Abilities.INNER_FOCUS, "Inner Focus", "The POKéMON is protected from flinching.", 3) new Ability(Abilities.INNER_FOCUS, "Inner Focus", "The POKéMON is protected from flinching.", 3)
@ -1232,8 +1255,8 @@ export function initAbilities() {
new Ability(Abilities.MAGMA_ARMOR, "Magma Armor", "Prevents the POKéMON from becoming frozen.", 3) new Ability(Abilities.MAGMA_ARMOR, "Magma Armor", "Prevents the POKéMON from becoming frozen.", 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE), .attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE),
new Ability(Abilities.MAGNET_PULL, "Magnet Pull", "Prevents STEEL-type POKéMON from escaping.", 3) new Ability(Abilities.MAGNET_PULL, "Magnet Pull", "Prevents STEEL-type POKéMON from escaping.", 3)
.attr(ArenaTrapAbAttr) /*.attr(ArenaTrapAbAttr)
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL)), .condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL))*/, // TODO: Rework
new Ability(Abilities.MARVEL_SCALE, "Marvel Scale (N)", "Ups DEFENSE if there is a status problem.", 3), new Ability(Abilities.MARVEL_SCALE, "Marvel Scale (N)", "Ups DEFENSE if there is a status problem.", 3),
new Ability(Abilities.MINUS, "Minus (N)", "Ups SP. ATK if another POKéMON has PLUS or MINUS.", 3), new Ability(Abilities.MINUS, "Minus (N)", "Ups SP. ATK if another POKéMON has PLUS or MINUS.", 3),
new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status problems heal when it switches out.", 3), new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status problems heal when it switches out.", 3),

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";
@ -143,8 +143,7 @@ class SpikesTag extends ArenaTrapTag {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = arena.scene.getPokemonById(this.sourceId);
const target = source.getOpponent(); arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
} }
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon): boolean {
@ -152,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(), MoveResult.OTHER)); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
return true; return true;
} }
@ -170,15 +169,14 @@ class ToxicSpikesTag extends ArenaTrapTag {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = arena.scene.getPokemonById(this.sourceId);
const target = source.getOpponent(); arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
} }
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon): boolean {
if (!pokemon.status && (!pokemon.isOfType(Type.FLYING) || pokemon.getTag(BattlerTagType.IGNORE_FLYING) || pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))) { if (!pokemon.status && (!pokemon.isOfType(Type.FLYING) || pokemon.getTag(BattlerTagType.IGNORE_FLYING) || pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))) {
const toxic = this.layers > 1; const toxic = this.layers > 1;
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(),
!toxic ? StatusEffect.POISON : StatusEffect.TOXIC, null, `the ${this.getMoveName()}`)); !toxic ? StatusEffect.POISON : StatusEffect.TOXIC, null, `the ${this.getMoveName()}`));
return true; return true;
} }
@ -196,8 +194,7 @@ class StealthRockTag extends ArenaTrapTag {
super.onAdd(arena); super.onAdd(arena);
const source = arena.scene.getPokemonById(this.sourceId); const source = arena.scene.getPokemonById(this.sourceId);
const target = source.getOpponent(); arena.scene.queueMessage(`Pointed stones float in the air\naround ${source.getOpponentDescriptor()}!`);
arena.scene.queueMessage(`Pointed stones float in the air\naround ${target.name}!`);
} }
activateTrap(pokemon: Pokemon): boolean { activateTrap(pokemon: Pokemon): boolean {
@ -228,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(), MoveResult.OTHER)); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio)); pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
} }
@ -242,8 +239,8 @@ export class TrickRoomTag extends ArenaTag {
} }
apply(args: any[]): boolean { apply(args: any[]): boolean {
const speedDelayed = args[0] as Utils.BooleanHolder; const speedReversed = args[0] as Utils.BooleanHolder;
speedDelayed.value = !speedDelayed.value; speedReversed.value = !speedReversed.value;
return true; return true;
} }

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 { BattlerIndex } 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) { constructor(move: Moves, user: Pokemon, target: BattlerIndex) {
super(user, getMoveTarget(user, move)); super(user, user.scene.getField()[target]);
this.move = move; this.move = move;
} }
@ -852,7 +853,7 @@ export class MoveChargeAnim extends MoveAnim {
private chargeAnim: ChargeAnim; private chargeAnim: ChargeAnim;
constructor(chargeAnim: ChargeAnim, move: Moves, user: Pokemon) { constructor(chargeAnim: ChargeAnim, move: Moves, user: Pokemon) {
super(move, user); super(move, user, 0);
this.chargeAnim = chargeAnim; this.chargeAnim = chargeAnim;
} }

View File

@ -103,7 +103,7 @@ export class RechargingTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.getMoveQueue().push({ move: Moves.NONE }) pokemon.getMoveQueue().push({ move: Moves.NONE, targets: [] })
} }
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
@ -177,7 +177,7 @@ export class ConfusedTag extends BattlerTag {
onAdd(pokemon: Pokemon): void { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' became\nconfused!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' became\nconfused!'));
} }
@ -198,14 +198,14 @@ export class ConfusedTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!'));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
if (Utils.randInt(2)) { if (Utils.randInt(2)) {
const atk = pokemon.getBattleStat(Stat.ATK); const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF); const def = pokemon.getBattleStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * ((Utils.randInt(15) + 85) / 100)); const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * ((Utils.randInt(15) + 85) / 100));
pokemon.scene.queueMessage('It hurt itself in its\nconfusion!'); pokemon.scene.queueMessage('It hurt itself in its\nconfusion!');
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer())); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage); pokemon.damage(damage);
(pokemon.scene.getCurrentPhase() as MovePhase).cancel(); (pokemon.scene.getCurrentPhase() as MovePhase).cancel();
} }
@ -245,7 +245,7 @@ export class InfatuatedTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`)); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.ATTRACT)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
if (Utils.randInt(2)) { if (Utils.randInt(2)) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!'));
@ -282,12 +282,13 @@ export class SeedTag extends BattlerTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) { if (ret) {
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, !pokemon.isPlayer(), CommonAnim.LEECH_SEED)); const source = pokemon.scene.getPokemonById(this.sourceId);
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
const damage = Math.max(Math.floor(pokemon.getMaxHp() / 8), 1); const damage = Math.max(Math.floor(pokemon.getMaxHp() / 8), 1);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer())); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage); pokemon.damage(damage);
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, !pokemon.isPlayer(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true)); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
} }
return ret; return ret;
@ -320,10 +321,10 @@ export class NightmareTag extends BattlerTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a NIGHTMARE!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a NIGHTMARE!'));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CURSE)); // TODO: Update animation type pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
const damage = Math.ceil(pokemon.getMaxHp() / 4); const damage = Math.ceil(pokemon.getMaxHp() / 4);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer())); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage); pokemon.damage(damage);
} }
@ -344,7 +345,7 @@ export class IngrainTag extends TrappedTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) if (ret)
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 16), pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), Math.floor(pokemon.getMaxHp() / 16),
getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true)); getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true));
return ret; return ret;
@ -374,7 +375,8 @@ export class AquaRingTag extends BattlerTag {
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (ret) if (ret)
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true)); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
return ret; return ret;
} }
@ -393,7 +395,7 @@ export class DrowsyTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (!super.lapse(pokemon, lapseType)) { if (!super.lapse(pokemon, lapseType)) {
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), StatusEffect.SLEEP)); pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(), StatusEffect.SLEEP));
return false; return false;
} }
@ -423,10 +425,10 @@ export abstract class DamagingTrapTag extends TrappedTag {
if (ret) { if (ret) {
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`)); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`));
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), this.commonAnim)); pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
const damage = Math.ceil(pokemon.getMaxHp() / 16); const damage = Math.ceil(pokemon.getMaxHp() / 16);
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer())); pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.damage(damage); pokemon.damage(damage);
} }
@ -541,7 +543,7 @@ export class TruantTag extends BattlerTag {
if (lastMove && lastMove.move !== Moves.NONE) { if (lastMove && lastMove.move !== Moves.NONE) {
(pokemon.scene.getCurrentPhase() as MovePhase).cancel(); (pokemon.scene.getCurrentPhase() as MovePhase).cancel();
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer())); pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!')); pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!'));
} }

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,12 +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) => { return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
const opponent = pokemon.getOpponent();
const opponentLastMove = opponent ? opponent.getLastXMoves(1).find(() => true) : null; // TODO: Update so this works even if opponent has fainted
return opponentLastMove && opponentLastMove.turn === pokemon.scene.currentBattle?.turn - 1 && opponentLastMove.result === MoveResult.SUPER_EFFECTIVE;
};
case BerryType.LIECHI: case BerryType.LIECHI:
case BerryType.GANLON: case BerryType.GANLON:
case BerryType.SALAC: case BerryType.SALAC:
@ -83,7 +78,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.SITRUS: case BerryType.SITRUS:
case BerryType.ENIGMA: case BerryType.ENIGMA:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true)); pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
}; };
case BerryType.LUM: case BerryType.LUM:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
@ -101,13 +97,13 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
case BerryType.APICOT: case BerryType.APICOT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const battleStat = (berryType - BerryType.LIECHI) as BattleStat; const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ battleStat ], 1)); pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], 1));
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
pokemon.addTag(BattlerTagType.CRIT_BOOST); pokemon.addTag(BattlerTagType.CRIT_BOOST);
}; };
case BerryType.STARF: case BerryType.STARF:
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ BattleStat.RAND ], 2)); return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], 2));
} }
} }

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,8 @@ 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 { BattlerIndex } from "../battle";
import { Stat } from "./pokemon-stat";
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -24,14 +26,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 +48,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;
@ -159,12 +161,67 @@ export default class Move {
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let score = 0;
for (let attr of this.attrs)
score += attr.getUserBenefitScore(user, target, move);
return score;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let score = 0;
for (let attr of this.attrs)
score += attr.getTargetBenefitScore(user, target, move);
return score;
}
} }
export class AttackMove extends Move { export class AttackMove extends Move {
constructor(id: Moves, name: string, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, tm: integer, effect: string, chance: integer, priority: integer, generation: integer) { constructor(id: Moves, name: string, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, tm: integer, effect: string, chance: integer, priority: integer, generation: integer) {
super(id, name, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, tm, effect, chance, priority, generation); super(id, name, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, tm, effect, chance, priority, generation);
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let ret = super.getTargetBenefitScore(user, target, move);
let attackScore = 0;
const effectiveness = target.getAttackMoveEffectiveness(this.type);
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
if (attackScore) {
if (this.category === MoveCategory.PHYSICAL) {
if (user.getBattleStat(Stat.ATK) > user.getBattleStat(Stat.SPATK)) {
const statRatio = user.getBattleStat(Stat.SPATK) / user.getBattleStat(Stat.ATK);
if (statRatio <= 0.75)
attackScore *= 2;
else if (statRatio <= 0.875)
attackScore *= 1.5;
}
} else {
if (user.getBattleStat(Stat.SPATK) > user.getBattleStat(Stat.ATK)) {
const statRatio = user.getBattleStat(Stat.ATK) / user.getBattleStat(Stat.SPATK);
if (statRatio <= 0.75)
attackScore *= 2;
else if (statRatio <= 0.875)
attackScore *= 1.5;
}
}
const power = new Utils.NumberHolder(this.power);
applyMoveAttrs(VariablePowerAttr, user, target, move, power);
attackScore += Math.floor(power.value / 5);
}
ret -= attackScore;
return ret;
}
} }
export class StatusMove extends Move { export class StatusMove extends Move {
@ -755,6 +812,14 @@ export abstract class MoveAttr {
getCondition(): MoveCondition { getCondition(): MoveCondition {
return null; return null;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 0;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 0;
}
} }
export class MoveEffectAttr extends MoveAttr { export class MoveEffectAttr extends MoveAttr {
@ -792,6 +857,10 @@ export class HighCritAttr extends MoveAttr {
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 3;
}
} }
export class CritOnlyAttr extends MoveAttr { export class CritOnlyAttr extends MoveAttr {
@ -800,6 +869,10 @@ export class CritOnlyAttr extends MoveAttr {
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return 5;
}
} }
export class FixedDamageAttr extends MoveAttr { export class FixedDamageAttr extends MoveAttr {
@ -889,12 +962,16 @@ 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(), MoveResult.OTHER)); user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), 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);
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.floor((move.power / 5) / -4);
}
} }
export class SacrificialAttr extends MoveEffectAttr { export class SacrificialAttr extends MoveEffectAttr {
@ -906,11 +983,15 @@ 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(), MoveResult.OTHER)); user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
user.damage(user.getMaxHp()); user.damage(user.getMaxHp());
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.ceil((1 - user.getHpRatio()) * 10) - 10;
}
} }
export enum MultiHitType { export enum MultiHitType {
@ -936,7 +1017,12 @@ export class HealAttr extends MoveEffectAttr {
} }
addHealPhase(user: Pokemon, healRatio: number) { addHealPhase(user: Pokemon, healRatio: number) {
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim)); user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return (1 - (this.selfTarget ? user : target).getHpRatio()) * 20;
} }
} }
@ -977,9 +1063,14 @@ export class HitHealAttr extends MoveHitEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true)); user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * ((move.power / 5) / 4));
}
} }
export class MultiHitAttr extends MoveAttr { export class MultiHitAttr extends MoveAttr {
@ -1016,6 +1107,10 @@ export class MultiHitAttr extends MoveAttr {
(args[0] as Utils.IntegerHolder).value = hitTimes; (args[0] as Utils.IntegerHolder).value = hitTimes;
return true; return true;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return 5;
}
} }
export class StatusEffectAttr extends MoveHitEffectAttr { export class StatusEffectAttr extends MoveHitEffectAttr {
@ -1034,12 +1129,16 @@ export class StatusEffectAttr extends MoveHitEffectAttr {
if (statusCheck) { if (statusCheck) {
const pokemon = this.selfTarget ? user : target; const pokemon = this.selfTarget ? user : target;
if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) { if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) {
user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.isPlayer(), this.effect, this.cureTurn)); user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.getBattlerIndex(), this.effect, this.cureTurn));
return true; return true;
} }
} }
return false; return false;
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return !(this.selfTarget ? user : target).status ? Math.floor(move.chance * -0.1) : 0;
}
} }
export class StealHeldItemAttr extends MoveHitEffectAttr { export class StealHeldItemAttr extends MoveHitEffectAttr {
@ -1048,8 +1147,7 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const heldItems = user.scene.findModifiers(m => m instanceof PokemonHeldItemModifier const heldItems = this.getTargetHeldItems(target);
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
if (heldItems.length) { if (heldItems.length) {
const stolenItem = heldItems[Utils.randInt(heldItems.length)]; const stolenItem = heldItems[Utils.randInt(heldItems.length)];
user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false); user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false);
@ -1060,6 +1158,21 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
return false; return false;
} }
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
}
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const heldItems = this.getTargetHeldItems(target);
return heldItems.length ? 5 : 0;
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const heldItems = this.getTargetHeldItems(target);
return heldItems.length ? -5 : 0;
}
} }
export class HealStatusEffectAttr extends MoveEffectAttr { export class HealStatusEffectAttr extends MoveEffectAttr {
@ -1086,6 +1199,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
return false; return false;
} }
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return user.status ? 10 : 0;
}
} }
export class BypassSleepAttr extends MoveAttr { export class BypassSleepAttr extends MoveAttr {
@ -1170,8 +1287,8 @@ 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.getBattlerIndex() ], result: MoveResult.OTHER });
user.getMoveQueue().push({ move: move.id, ignorePP: true }); user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
resolve(true); resolve(true);
}); });
} else } else
@ -1214,7 +1331,7 @@ export class StatChangeAttr extends MoveEffectAttr {
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) { if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
const levels = this.getLevels(user); const levels = this.getLevels(user);
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, this.selfTarget, this.stats, levels)); user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels));
return true; return true;
} }
@ -1224,6 +1341,12 @@ export class StatChangeAttr extends MoveEffectAttr {
getLevels(_user: Pokemon): integer { getLevels(_user: Pokemon): integer {
return this.levels; return this.levels;
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
// TODO: Add awareness of level limits
const levels = this.getLevels(user);
return (levels * 4) + (levels > 0 ? -2 : 2);
}
} }
export class GrowthStatChangeAttr extends StatChangeAttr { export class GrowthStatChangeAttr extends StatChangeAttr {
@ -1273,7 +1396,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)
@ -1458,16 +1581,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;
} }
} }
@ -1530,7 +1653,7 @@ export class FrenzyAttr extends MoveEffectAttr {
} }
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) { canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
return !!(this.selfTarget ? user.hp : target.hp); return !(this.selfTarget ? user : target).isFainted();
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -1540,7 +1663,7 @@ export class FrenzyAttr extends MoveEffectAttr {
if (!user.getMoveQueue().length) { if (!user.getMoveQueue().length) {
if (!user.getTag(BattlerTagType.FRENZY)) { if (!user.getTag(BattlerTagType.FRENZY)) {
const turnCount = Utils.randInt(2) + 1; const turnCount = Utils.randInt(2) + 1;
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, ignorePP: true })); new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id); user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id);
} else { } else {
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args); applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
@ -1553,7 +1676,7 @@ export class FrenzyAttr extends MoveEffectAttr {
} }
} }
export const frenzyMissFunc: MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => { export const frenzyMissFunc: UserMoveCondition = (user: Pokemon, move: Move) => {
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id)
user.getMoveQueue().shift(); user.getMoveQueue().shift();
user.lapseTag(BattlerTagType.FRENZY); user.lapseTag(BattlerTagType.FRENZY);
@ -1596,6 +1719,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
? (user: Pokemon, target: Pokemon, move: Move) => !(this.selfTarget ? user : target).getTag(this.tagType) ? (user: Pokemon, target: Pokemon, move: Move) => !(this.selfTarget ? user : target).getTag(this.tagType)
: null; : null;
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
switch (this.tagType) {
case BattlerTagType.FLINCHED:
return -5;
case BattlerTagType.CONFUSED:
return -5;
case BattlerTagType.INFATUATED:
return -5;
case BattlerTagType.SEEDED:
return -3;
case BattlerTagType.NIGHTMARE:
return -5;
case BattlerTagType.FRENZY:
return -2;
case BattlerTagType.INGRAIN:
return 3;
case BattlerTagType.AQUA_RING:
return 3;
case BattlerTagType.DROWSY:
return -5;
case BattlerTagType.TRAPPED:
case BattlerTagType.BIND:
case BattlerTagType.WRAP:
case BattlerTagType.FIRE_SPIN:
case BattlerTagType.WHIRLPOOL:
case BattlerTagType.CLAMP:
case BattlerTagType.SAND_TOMB:
case BattlerTagType.MAGMA_STORM:
return -3;
case BattlerTagType.PROTECTED:
return 10;
case BattlerTagType.FLYING:
return 5;
case BattlerTagType.CRIT_BOOST:
return 5;
case BattlerTagType.NO_CRIT:
return -5;
case BattlerTagType.IGNORE_ACCURACY:
return 3;
}
}
} }
export class LapseBattlerTagAttr extends MoveEffectAttr { export class LapseBattlerTagAttr extends MoveEffectAttr {
@ -1646,7 +1811,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));
@ -1680,6 +1845,10 @@ export class HitsTagAttr extends MoveAttr {
this.tagType = tagType; this.tagType = tagType;
this.doubleDamage = !!doubleDamage; this.doubleDamage = !!doubleDamage;
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0;
}
} }
export class AddArenaTagAttr extends MoveEffectAttr { export class AddArenaTagAttr extends MoveEffectAttr {
@ -1768,10 +1937,16 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
if (moves.length) { if (moves.length) {
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 }); const moveTargets = getMoveTargets(user, move.moveId);
user.scene.unshiftPhase(user.isPlayer() if (!moveTargets.targets.length)
? new PlayerMovePhase(user.scene, user as PlayerPokemon, moveset[moveIndex], true) return false;
: new EnemyMovePhase(user.scene, user as EnemyPokemon, moveset[moveIndex], true)); const targets = moveTargets.multiple || moveTargets.targets.length === 1
? moveTargets.targets
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
? [ target.getBattlerIndex() ]
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: this.enemyMoveset });
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true));
return true; return true;
} }
@ -1784,10 +1959,19 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
return new Promise(resolve => { return new Promise(resolve => {
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.scene.unshiftPhase(user.isPlayer() const moveTargets = getMoveTargets(user, moveId);
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId, 0, 0, true), true) if (!moveTargets.targets.length) {
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId, 0, 0, true), true)); resolve(false);
return;
}
const targets = moveTargets.multiple || moveTargets.targets.length === 1
? moveTargets.targets
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
? [ target.getBattlerIndex() ]
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true });
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true));
initMoveAnim(moveId).then(() => { initMoveAnim(moveId).then(() => {
loadMoveAnimAssets(user.scene, [ moveId ], true) loadMoveAnimAssets(user.scene, [ moveId ], true)
.then(() => resolve(true)); .then(() => resolve(true));
@ -1822,10 +2006,18 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
const copiedMove = targetMoves[0]; const copiedMove = targetMoves[0];
user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true }); const moveTargets = getMoveTargets(user, copiedMove.move);
user.scene.unshiftPhase(user.isPlayer() if (!moveTargets.targets.length)
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true) return false;
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true));
const targets = moveTargets.multiple || moveTargets.targets.length === 1
? moveTargets.targets
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
? [ target.getBattlerIndex() ]
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
user.getMoveQueue().push({ move: copiedMove.move, targets: targets, ignorePP: true });
user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(copiedMove.move, 0, 0, true), true));
return true; return true;
} }
@ -1932,20 +2124,60 @@ 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, move: Moves): Pokemon { export type MoveTargetSet = {
const moveTarget = allMoves[move].moveTarget; targets: BattlerIndex[];
multiple: boolean;
}
const other = user.getOpponent(); export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
const moveTarget = move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
const opponents = user.getOpponents();
let set: BattlerIndex[] = [];
let multiple = false;
switch (moveTarget) { switch (moveTarget) {
case MoveTarget.USER: case MoveTarget.USER:
set = [ user.getBattlerIndex() ];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattlerIndex());
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents.map(p => p.getBattlerIndex());
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [ opponents[Utils.randInt(opponents.length)].getBattlerIndex() ];
break;
case MoveTarget.ATTACKER:
set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattlerIndex() ];
break;
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = [ user.getAlly()?.getBattlerIndex() ];
break;
case MoveTarget.USER_OR_NEAR_ALLY: case MoveTarget.USER_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?.getBattlerIndex());
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?.getBattlerIndex());
multiple = true;
break;
} }
return { targets: set.filter(t => t !== undefined), multiple };
} }
export const allMoves = [ export const allMoves = [
@ -2002,7 +2234,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),
@ -2070,13 +2302,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),
@ -2253,7 +2487,7 @@ export function initMoves() {
new SelfStatusMove(Moves.SOFT_BOILED, "Soft-Boiled", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 0, 1) 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),
@ -2337,7 +2571,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

@ -100,17 +100,10 @@ export class Weather {
} }
isEffectSuppressed(scene: BattleScene): boolean { isEffectSuppressed(scene: BattleScene): boolean {
const playerPokemon = scene.getPlayerPokemon(); const field = scene.getField().filter(p => p);
const enemyPokemon = scene.getEnemyPokemon();
if (playerPokemon) { for (let pokemon of field) {
const suppressWeatherEffectAbAttr = playerPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr; const suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
return true;
}
if (enemyPokemon) {
const suppressWeatherEffectAbAttr = enemyPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable)) if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
return true; return true;
} }

View File

@ -44,7 +44,7 @@ export class EvolutionPhase extends BattlePhase {
this.evolutionContainer = (this.scene.ui.getHandler() as EvolutionSceneHandler).evolutionContainer; this.evolutionContainer = (this.scene.ui.getHandler() as EvolutionSceneHandler).evolutionContainer;
this.evolutionBaseBg = this.scene.add.image(0, 0, 'plains_bg'); this.evolutionBaseBg = this.scene.add.image(0, 0, 'default_bg');
this.evolutionBaseBg.setOrigin(0, 0); this.evolutionBaseBg.setOrigin(0, 0);
this.evolutionContainer.add(this.evolutionBaseBg); this.evolutionContainer.add(this.evolutionBaseBg);
@ -98,6 +98,7 @@ export class EvolutionPhase extends BattlePhase {
const levelMoves = pokemon.getLevelMoves(this.lastLevel + 1); const levelMoves = pokemon.getLevelMoves(this.lastLevel + 1);
for (let lm of levelMoves) for (let lm of levelMoves)
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm)); this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm));
this.scene.unshiftPhase(new EndEvolutionPhase(this.scene));
this.scene.time.delayedCall(1000, () => { this.scene.time.delayedCall(1000, () => {
const evolutionBgm = this.scene.sound.add('evolution'); const evolutionBgm = this.scene.sound.add('evolution');
@ -444,3 +445,11 @@ export class EvolutionPhase extends BattlePhase {
updateParticle(); updateParticle();
} }
} }
export class EndEvolutionPhase extends BattlePhase {
start() {
super.start();
this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end());
}
}

View File

@ -11,7 +11,6 @@ import * as Utils from '../utils';
import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from '../data/temp-battle-stat'; import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from '../data/temp-battle-stat';
import { BerryType, getBerryEffectDescription, getBerryName } from '../data/berry'; import { BerryType, getBerryEffectDescription, getBerryName } from '../data/berry';
import { Unlockables } from '../system/unlockables'; import { Unlockables } from '../system/unlockables';
import { maxExpLevel } from '../battle-scene';
type Modifier = Modifiers.Modifier; type Modifier = Modifiers.Modifier;
@ -128,7 +127,7 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType {
constructor(name: string, restorePercent: integer, iconImage?: string) { constructor(name: string, restorePercent: integer, iconImage?: string) {
super(name, restorePercent, true, (_type, args) => new Modifiers.PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, true, true), super(name, restorePercent, true, (_type, args) => new Modifiers.PokemonHpRestoreModifier(this, (args[0] as PlayerPokemon).id, this.restorePoints, true, true),
((pokemon: PlayerPokemon) => { ((pokemon: PlayerPokemon) => {
if (pokemon.hp) if (!pokemon.isFainted())
return PartyUiHandler.NoEffectMessage; return PartyUiHandler.NoEffectMessage;
return null; return null;
}), iconImage, 'revive'); }), iconImage, 'revive');
@ -669,15 +668,15 @@ const modifierPool = {
return statusEffectPartyMemberCount * 6; return statusEffectPartyMemberCount * 6;
}), }),
new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.REVIVE, (party: Pokemon[]) => {
const faintedPartyMemberCount = Math.min(party.filter(p => !p.hp).length, 3); const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
return faintedPartyMemberCount * 9; return faintedPartyMemberCount * 9;
}), }),
new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.MAX_REVIVE, (party: Pokemon[]) => {
const faintedPartyMemberCount = Math.min(party.filter(p => !p.hp).length, 3); const faintedPartyMemberCount = Math.min(party.filter(p => p.isFainted()).length, 3);
return faintedPartyMemberCount * 3; return faintedPartyMemberCount * 3;
}), }),
new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.SACRED_ASH, (party: Pokemon[]) => {
return party.filter(p => !p.hp).length >= Math.ceil(party.length / 2) ? 1 : 0; return party.filter(p => p.isFainted()).length >= Math.ceil(party.length / 2) ? 1 : 0;
}), }),
new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => { new WeightedModifierType(modifierTypes.HYPER_POTION, (party: Pokemon[]) => {
const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3); const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3);

View File

@ -480,7 +480,8 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
if (pokemon.getHpRatio() < 1) { if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true));
} }
return true; return true;
@ -509,7 +510,8 @@ export class HitHealModifier extends PokemonHeldItemModifier {
if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) { if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) {
const scene = pokemon.scene; const scene = pokemon.scene;
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true));
} }
return true; return true;
@ -1001,7 +1003,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
apply(args: any[]): boolean { apply(args: any[]): boolean {
const pokemon = args[0] as Pokemon; const pokemon = args[0] as Pokemon;
const targetPokemon = pokemon.getOpponent(); const targetPokemon = pokemon.getOpponent(args.length > 1 ? args[1] as integer : !pokemon.scene.currentBattle.double ? 0 : Utils.randInt(2));
if (!targetPokemon) if (!targetPokemon)
return false; return false;

View File

@ -1,7 +1,7 @@
import Phaser from 'phaser'; import Phaser from 'phaser';
import BattleScene from './battle-scene'; import BattleScene from './battle-scene';
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info';
import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr } from "./data/move"; import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, AttackMove, AddBattlerTagAttr } from "./data/move";
import { pokemonLevelMoves } from './data/pokemon-level-moves'; import { pokemonLevelMoves } from './data/pokemon-level-moves';
import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species'; import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species';
import * as Utils from './utils'; import * as Utils from './utils';
@ -21,10 +21,17 @@ import { BattlerTag, BattlerTagLapseType, BattlerTagType, TypeBoostTag, getBattl
import { Species } from './data/species'; import { Species } from './data/species';
import { WeatherType } from './data/weather'; import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat'; import { TempBattleStat } from './data/temp-battle-stat';
import { ArenaTagType, GravityTag, WeakenMoveTypeTag } from './data/arena-tag'; 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, BattlerTagImmunityAbAttr, 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 { BattlerIndex } from './battle';
export enum FieldPosition {
CENTER,
LEFT,
RIGHT
}
export default abstract class Pokemon extends Phaser.GameObjects.Container { export default abstract class Pokemon extends Phaser.GameObjects.Container {
public id: integer; public id: integer;
@ -50,6 +57,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public battleSummonData: PokemonBattleSummonData; public battleSummonData: PokemonBattleSummonData;
public turnData: PokemonTurnData; public turnData: PokemonTurnData;
public fieldPosition: FieldPosition;
public maskEnabled: boolean; public maskEnabled: boolean;
public maskSprite: Phaser.GameObjects.Sprite; public maskSprite: Phaser.GameObjects.Sprite;
@ -139,7 +148,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.calculateStats(); this.calculateStats();
scene.fieldUI.addAt(this.battleInfo, 0); this.fieldPosition = FieldPosition.CENTER;
scene.fieldUI.add(this.battleInfo);
this.battleInfo.initInfo(this); this.battleInfo.initInfo(this);
@ -177,8 +188,20 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
} }
isFainted(checkStatus?: boolean): boolean {
return !this.hp && (!checkStatus || this.status?.effect === StatusEffect.FAINT);
}
isActive(): boolean {
return !this.isFainted() && !!this.scene;
}
abstract isPlayer(): boolean; abstract isPlayer(): boolean;
abstract getFieldIndex(): integer;
abstract getBattlerIndex(): BattlerIndex;
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);
@ -261,11 +284,57 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: this.maskSprite; : this.maskSprite;
} }
playAnim(): void{ playAnim(): void {
this.getSprite().play(this.getBattleSpriteKey()); this.getSprite().play(this.getBattleSpriteKey());
this.getTintSprite().play(this.getBattleSpriteKey()); this.getTintSprite().play(this.getBattleSpriteKey());
} }
getFieldPositionOffset(): [ number, number ] {
switch (this.fieldPosition) {
case FieldPosition.CENTER:
return [ 0, 0 ];
case FieldPosition.LEFT:
return [ -32, -8 ];
case FieldPosition.RIGHT:
return [ 32, 0 ];
}
}
setFieldPosition(fieldPosition: FieldPosition, duration?: integer): Promise<void> {
return new Promise(resolve => {
if (fieldPosition === this.fieldPosition) {
resolve();
return;
}
const initialOffset = this.getFieldPositionOffset();
this.fieldPosition = fieldPosition;
this.battleInfo.setMini(fieldPosition !== FieldPosition.CENTER);
this.battleInfo.setOffset(fieldPosition === FieldPosition.RIGHT);
const newOffset = this.getFieldPositionOffset();
let relX = newOffset[0] - initialOffset[0];
let relY = newOffset[1] - initialOffset[1];
if (duration) {
this.scene.tweens.add({
targets: this,
x: (_target, _key, value: number) => value + relX,
y: (_target, _key, value: number) => value + relY,
duration: duration,
ease: 'Sine.easeOut',
onComplete: () => resolve()
});
} else {
this.x += relX;
this.y += relY;
}
});
}
getBattleStat(stat: Stat): integer { getBattleStat(stat: Stat): integer {
if (stat === Stat.HP) if (stat === Stat.HP)
return this.stats[Stat.HP]; return this.stats[Stat.HP];
@ -360,7 +429,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
canApplyAbility(): boolean { canApplyAbility(): boolean {
return !this.getAbility().conditions.find(condition => !condition(this)); return this.hp && !this.getAbility().conditions.find(condition => !condition(this));
} }
getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier { getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier {
@ -405,7 +474,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
setMove(moveIndex: integer, moveId: Moves): void { setMove(moveIndex: integer, moveId: Moves): void {
const move = moveId ? new PokemonMove(moveId) : null; const move = moveId ? new PokemonMove(moveId) : null;
this.moveset[moveIndex] = move; this.moveset[moveIndex] = move;
if (this.summonData.moveset) if (this.summonData?.moveset)
this.summonData.moveset[moveIndex] = move; this.summonData.moveset[moveIndex] = move;
} }
@ -496,15 +565,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.levelExp = this.exp - getLevelTotalExp(this.level, this.getSpeciesForm().growthRate); this.levelExp = this.exp - getLevelTotalExp(this.level, this.getSpeciesForm().growthRate);
} }
getOpponent(): Pokemon { getOpponent(targetIndex: integer): Pokemon {
const ret = this.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon(); const ret = this.getOpponents()[targetIndex];
if (ret.summonData) if (ret.summonData)
return ret; return ret;
return null; return null;
} }
apply(source: Pokemon, battlerMove: PokemonMove): MoveResult { getOpponents(): Pokemon[] {
let result: MoveResult; return ((this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField()) as Pokemon[]).filter(p => p.isActive());
}
getOpponentDescriptor(): string {
const opponents = this.getOpponents();
if (opponents.length === 1)
return opponents[0].name;
return this.isPlayer() ? 'the opposing team' : 'your team';
}
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 move = battlerMove.getMove();
const moveCategory = move.category; const moveCategory = move.category;
let damage = 0; let damage = 0;
@ -526,7 +610,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;
@ -567,46 +651,50 @@ 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) {
this.scene.unshiftPhase(new DamagePhase(this.scene, this.isPlayer(), result as DamageResult)); this.scene.unshiftPhase(new DamagePhase(this.scene, this.getBattlerIndex(), result as DamageResult));
if (isCritical) if (isCritical)
this.scene.queueMessage('A critical hit!'); this.scene.queueMessage('A critical hit!');
this.scene.setPhaseQueueSplice();
this.damage(damage); this.damage(damage);
source.turnData.damageDealt += damage; source.turnData.damageDealt += damage;
this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id }); this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id });
} }
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;
} }
if (damage)
this.scene.clearPhaseQueueSplice();
} }
break; break;
case MoveCategory.STATUS: case MoveCategory.STATUS:
result = MoveResult.STATUS; result = HitResult.STATUS;
break; break;
} }
@ -614,7 +702,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
damage(damage: integer, preventEndure?: boolean): void { damage(damage: integer, preventEndure?: boolean): void {
if (!this.hp) if (this.isFainted())
return; return;
if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) { if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) {
@ -625,10 +713,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
this.hp = Math.max(this.hp - damage, 0); this.hp = Math.max(this.hp - damage, 0);
if (!this.hp) { if (this.isFainted()) {
this.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer())); this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex()));
this.resetSummonData(); this.resetSummonData();
this.getOpponent()?.resetBattleSummonData();
} }
} }
@ -946,6 +1033,14 @@ export class PlayerPokemon extends Pokemon {
return true; return true;
} }
getFieldIndex(): integer {
return this.scene.getPlayerField().indexOf(this);
}
getBattlerIndex(): BattlerIndex {
return this.getFieldIndex();
}
generateCompatibleTms(): void { generateCompatibleTms(): void {
this.compatibleTms = []; this.compatibleTms = [];
@ -1041,7 +1136,7 @@ export class EnemyPokemon extends Pokemon {
: null; : null;
if (queuedMove) { if (queuedMove) {
if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP)) if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP))
return { move: queuedMove.moveId, ignorePP: this.getMoveQueue()[0].ignorePP }; return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP };
else { else {
this.getMoveQueue().shift(); this.getMoveQueue().shift();
return this.getNextMove(); return this.getNextMove();
@ -1051,52 +1146,26 @@ export class EnemyPokemon extends Pokemon {
const movePool = this.getMoveset().filter(m => m.isUsable()); const movePool = this.getMoveset().filter(m => m.isUsable());
if (movePool.length) { if (movePool.length) {
if (movePool.length === 1) if (movePool.length === 1)
return { move: movePool[0].moveId }; return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
switch (this.aiType) { switch (this.aiType) {
case AiType.RANDOM: case AiType.RANDOM:
return { move: movePool[Utils.randInt(movePool.length)].moveId }; const moveId = movePool[Utils.randInt(movePool.length)].moveId;
return { move: moveId, targets: this.getNextTargets(moveId) };
case AiType.SMART_RANDOM: case AiType.SMART_RANDOM:
case AiType.SMART: case AiType.SMART:
const target = this.scene.getPlayerPokemon();
const moveScores = movePool.map(() => 0); const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
for (let m in movePool) { for (let m in movePool) {
const pokemonMove = movePool[m]; const pokemonMove = movePool[m];
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
let moveScore = moveScores[m]; let moveScore = moveScores[m];
if (move.category === MoveCategory.STATUS)
moveScore++; for (let mt of moveTargets[move.id]) {
else { const target = this.scene.getField()[mt];
const effectiveness = this.getAttackMoveEffectiveness(move.type); moveScore += move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
moveScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
if (moveScore) {
if (move.category === MoveCategory.PHYSICAL) {
if (this.getBattleStat(Stat.ATK) > this.getBattleStat(Stat.SPATK)) {
const statRatio = this.getBattleStat(Stat.SPATK) / this.getBattleStat(Stat.ATK);
if (statRatio <= 0.75)
moveScore *= 2;
else if (statRatio <= 0.875)
moveScore *= 1.5;
}
} else {
if (this.getBattleStat(Stat.SPATK) > this.getBattleStat(Stat.ATK)) {
const statRatio = this.getBattleStat(Stat.ATK) / this.getBattleStat(Stat.SPATK);
if (statRatio <= 0.75)
moveScore *= 2;
else if (statRatio <= 0.875)
moveScore *= 1.5;
}
} }
moveScore += Math.floor(move.power / 5); moveScore /= moveTargets[move.id].length
}
}
const statChangeAttrs = move.getAttrs(StatChangeAttr) as StatChangeAttr[];
for (let sc of statChangeAttrs) {
moveScore += ((sc.levels >= 1) === sc.selfTarget ? -2 : 2) + sc.levels * (sc.selfTarget ? 4 : -4);
// TODO: Add awareness of current levels
}
// could make smarter by checking opponent def/spdef // could make smarter by checking opponent def/spdef
moveScores[m] = moveScore; moveScores[m] = moveScore;
@ -1116,17 +1185,48 @@ export class EnemyPokemon extends Pokemon {
r++; r++;
} }
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName())); console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName()));
return { move: sortedMovePool[r].moveId }; return { move: sortedMovePool[r].moveId, targets: moveTargets[sortedMovePool[r].moveId] };
} }
} }
return { move: Moves.STRUGGLE }; return { move: Moves.STRUGGLE, targets: this.getNextTargets(Moves.STRUGGLE) };
}
getNextTargets(moveId: Moves): BattlerIndex[] {
const moveTargets = getMoveTargets(this, moveId);
const targets = this.scene.getField().filter(p => p?.isActive() && moveTargets.targets.indexOf(p.getBattlerIndex()) > -1);
if (moveTargets.multiple)
return targets.map(p => p.getBattlerIndex());
const move = allMoves[moveId];
let benefitScores = targets
.map(p => [ p.getBattlerIndex(), move.getTargetBenefitScore(this, p, move) * (p.isPlayer() === this.isPlayer() ? 1 : -1) ]);
const sortedBenefitScores = benefitScores.slice(0);
sortedBenefitScores.sort((a, b) => {
const scoreA = a[1];
const scoreB = b[1];
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
});
// TODO: Add some randomness
return [ sortedBenefitScores[0][0] ];
} }
isPlayer() { isPlayer() {
return false; return false;
} }
getFieldIndex(): integer {
return this.scene.getEnemyField().indexOf(this);
}
getBattlerIndex(): BattlerIndex {
return BattlerIndex.ENEMY + this.getFieldIndex();
}
addToParty() { addToParty() {
const party = this.scene.getParty(); const party = this.scene.getParty();
let ret: PlayerPokemon = null; let ret: PlayerPokemon = null;
@ -1143,6 +1243,7 @@ export class EnemyPokemon extends Pokemon {
export interface TurnMove { export interface TurnMove {
move: Moves; move: Moves;
targets?: BattlerIndex[];
result: MoveResult; result: MoveResult;
virtual?: boolean; virtual?: boolean;
turn?: integer; turn?: integer;
@ -1150,6 +1251,7 @@ export interface TurnMove {
export interface QueuedMove { export interface QueuedMove {
move: Moves; move: Moves;
targets: BattlerIndex[];
ignorePP?: boolean; ignorePP?: boolean;
} }
@ -1188,17 +1290,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));
}); });
} }
@ -193,7 +193,7 @@ export function initAutoPlay() {
const party = thisArg.getParty(); const party = thisArg.getParty();
const modifierTypeOptions = modifierSelectUiHandler.options.map(o => o.modifierTypeOption); const modifierTypeOptions = modifierSelectUiHandler.options.map(o => o.modifierTypeOption);
const faintedPartyMemberIndex = party.findIndex(p => !p.hp); const faintedPartyMemberIndex = party.findIndex(p => p.isFainted());
const lowHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.5); const lowHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.5);
const criticalHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.25); const criticalHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.25);

View File

@ -21,7 +21,7 @@ interface SystemSaveData {
interface SessionSaveData { interface SessionSaveData {
party: PokemonData[]; party: PokemonData[];
enemyParty: PokemonData[]; enemyField: PokemonData[];
modifiers: PersistentModifierData[]; modifiers: PersistentModifierData[];
enemyModifiers: PersistentModifierData[]; enemyModifiers: PersistentModifierData[];
arena: ArenaData; arena: ArenaData;
@ -126,9 +126,9 @@ export class GameData {
saveSession(scene: BattleScene): boolean { saveSession(scene: BattleScene): boolean {
const sessionData = { const sessionData = {
party: scene.getParty().map(p => new PokemonData(p)), party: scene.getParty().map(p => new PokemonData(p)),
enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)), enemyField: scene.getEnemyField().map(p => new PokemonData(p)),
modifiers: scene.findModifiers(m => true).map(m => new PersistentModifierData(m, true)), modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
enemyModifiers: scene.findModifiers(m => true, false).map(m => new PersistentModifierData(m, false)), enemyModifiers: scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)),
arena: new ArenaData(scene.arena), arena: new ArenaData(scene.arena),
pokeballCounts: scene.pokeballCounts, pokeballCounts: scene.pokeballCounts,
waveIndex: scene.currentBattle.waveIndex, waveIndex: scene.currentBattle.waveIndex,
@ -153,7 +153,7 @@ export class GameData {
try { try {
const sessionData = JSON.parse(atob(localStorage.getItem('sessionData')), (k: string, v: any) => { const sessionData = JSON.parse(atob(localStorage.getItem('sessionData')), (k: string, v: any) => {
if (k === 'party' || k === 'enemyParty') { if (k === 'party' || k === 'enemyField') {
const ret: PokemonData[] = []; const ret: PokemonData[] = [];
for (let pd of v) for (let pd of v)
ret.push(new PokemonData(pd)); ret.push(new PokemonData(pd));
@ -188,16 +188,19 @@ export class GameData {
party.push(pokemon); party.push(pokemon);
} }
const enemyPokemon = sessionData.enemyParty[0].toPokemon(scene) as EnemyPokemon;
Object.keys(scene.pokeballCounts).forEach((key: string) => { Object.keys(scene.pokeballCounts).forEach((key: string) => {
scene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0; scene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0;
}); });
scene.newArena(sessionData.arena.biome, true); scene.newArena(sessionData.arena.biome, sessionData.enemyField.length > 1);
scene.newBattle(sessionData.waveIndex).enemyPokemon = enemyPokemon; const battle = scene.newBattle(sessionData.waveIndex, sessionData.enemyField.length > 1);
sessionData.enemyField.forEach((enemyData, e) => {
const enemyPokemon = enemyData.toPokemon(scene) as EnemyPokemon;
battle.enemyField[e] = enemyPokemon;
loadPokemonAssets.push(enemyPokemon.loadAssets()); loadPokemonAssets.push(enemyPokemon.loadAssets());
});
scene.arena.weather = sessionData.arena.weather; scene.arena.weather = sessionData.arena.weather;
// TODO // TODO

View File

@ -37,7 +37,7 @@ export default class ModifierData {
type.generatorId = this.typeGeneratorId; type.generatorId = this.typeGeneratorId;
if (type instanceof ModifierTypeGenerator) if (type instanceof ModifierTypeGenerator)
type = (type as ModifierTypeGenerator).generateType(this.player ? scene.getParty() : scene.getEnemyParty(), this.typePregenArgs); type = (type as ModifierTypeGenerator).generateType(this.player ? scene.getParty() : scene.getEnemyField(), this.typePregenArgs);
const ret = Reflect.construct(constructor, ([ type ] as any[]).concat(this.args).concat(this.stackCount)) as PersistentModifier const ret = Reflect.construct(constructor, ([ type ] as any[]).concat(this.args).concat(this.stackCount)) as PersistentModifier

View File

@ -4,6 +4,7 @@ import { TextStyle, addTextObject } from "./text";
const hiddenX = -91; const hiddenX = -91;
const shownX = 10; const shownX = 10;
const baseY = -116;
export default class AbilityBar extends Phaser.GameObjects.Container { export default class AbilityBar extends Phaser.GameObjects.Container {
private bg: Phaser.GameObjects.Image; private bg: Phaser.GameObjects.Image;
@ -15,7 +16,7 @@ export default class AbilityBar extends Phaser.GameObjects.Container {
public shown: boolean; public shown: boolean;
constructor(scene: BattleScene) { constructor(scene: BattleScene) {
super(scene, hiddenX, (-scene.game.canvas.height / 6) + 64); super(scene, hiddenX, baseY);
} }
setup(): void { setup(): void {
@ -43,9 +44,12 @@ export default class AbilityBar extends Phaser.GameObjects.Container {
if (this.shown) if (this.shown)
return; return;
(this.scene as BattleScene).fieldUI.bringToTop(this);
if (this.tween) if (this.tween)
this.tween.stop(); this.tween.stop();
this.y = baseY + ((this.scene as BattleScene).currentBattle.double ? 14 : 0);
this.tween = this.scene.tweens.add({ this.tween = this.scene.tweens.add({
targets: this, targets: this,
x: shownX, x: shownX,

View File

@ -60,12 +60,12 @@ export default class BallUiHandler extends UiHandler {
let success = false; let success = false;
const pokeballTypeCount = Object.keys(this.scene.pokeballCounts).length; const pokeballTypeCount = Object.keys(this.scene.currentBattle.turnPokeballCounts).length;
if (button === Button.ACTION || button === Button.CANCEL) { if (button === Button.ACTION || button === Button.CANCEL) {
success = true; success = true;
if (button === Button.ACTION && this.cursor < pokeballTypeCount) { if (button === Button.ACTION && this.cursor < pokeballTypeCount) {
if (this.scene.pokeballCounts[this.cursor]) { if (this.scene.currentBattle.turnPokeballCounts[this.cursor]) {
if ((this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.BALL, this.cursor)) { if ((this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.BALL, this.cursor)) {
this.scene.ui.setMode(Mode.COMMAND); this.scene.ui.setMode(Mode.COMMAND);
this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.setMode(Mode.MESSAGE);
@ -93,7 +93,7 @@ export default class BallUiHandler extends UiHandler {
} }
updateCounts() { updateCounts() {
this.countsText.setText(Object.values(this.scene.pokeballCounts).map(c => `x${c}`).join('\n')); this.countsText.setText(Object.values(this.scene.currentBattle.turnPokeballCounts).map(c => `x${c}`).join('\n'));
} }
setCursor(cursor: integer): boolean { setCursor(cursor: integer): boolean {

View File

@ -4,10 +4,12 @@ import * as Utils from '../utils';
import { addTextObject, TextStyle } from './text'; import { addTextObject, TextStyle } from './text';
import { getGenderSymbol, getGenderColor } from '../data/gender'; import { getGenderSymbol, getGenderColor } from '../data/gender';
import { StatusEffect } from '../data/status-effect'; import { StatusEffect } from '../data/status-effect';
import BattleScene, { maxExpLevel } from '../battle-scene'; import { maxExpLevel } from '../battle-scene';
export default class BattleInfo extends Phaser.GameObjects.Container { export default class BattleInfo extends Phaser.GameObjects.Container {
private player: boolean; private player: boolean;
private mini: boolean;
private offset: boolean;
private lastName: string; private lastName: string;
private lastStatus: StatusEffect; private lastStatus: StatusEffect;
private lastHp: integer; private lastHp: integer;
@ -17,9 +19,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private lastLevelExp: integer; private lastLevelExp: integer;
private lastLevel: integer; private lastLevel: integer;
private box: Phaser.GameObjects.Sprite;
private nameText: Phaser.GameObjects.Text; private nameText: Phaser.GameObjects.Text;
private genderText: Phaser.GameObjects.Text; private genderText: Phaser.GameObjects.Text;
private ownedIcon: Phaser.GameObjects.Image; private ownedIcon: Phaser.GameObjects.Sprite;
private statusIndicator: Phaser.GameObjects.Sprite; private statusIndicator: Phaser.GameObjects.Sprite;
private levelContainer: Phaser.GameObjects.Container; private levelContainer: Phaser.GameObjects.Container;
private hpBar: Phaser.GameObjects.Image; private hpBar: Phaser.GameObjects.Image;
@ -30,6 +33,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) { constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
super(scene, x, y); super(scene, x, y);
this.player = player; this.player = player;
this.mini = !player;
this.offset = false;
this.lastName = null; this.lastName = null;
this.lastStatus = StatusEffect.NONE; this.lastStatus = StatusEffect.NONE;
this.lastHp = -1; this.lastHp = -1;
@ -42,9 +47,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
// Initially invisible and shown via Pokemon.showInfo // Initially invisible and shown via Pokemon.showInfo
this.setVisible(false); this.setVisible(false);
const box = this.scene.add.image(0, 0, `pbinfo_${player ? 'player' : 'enemy'}`); this.box = this.scene.add.sprite(0, 0, this.getTextureName());
box.setOrigin(1, 0.5); this.box.setOrigin(1, 0.5);
this.add(box); this.add(this.box);
this.nameText = addTextObject(this.scene, player ? -115 : -124, player ? -15.2 : -11.2, '', TextStyle.BATTLE_INFO); this.nameText = addTextObject(this.scene, player ? -115 : -124, player ? -15.2 : -11.2, '', TextStyle.BATTLE_INFO);
this.nameText.setOrigin(0, 0); this.nameText.setOrigin(0, 0);
@ -56,7 +61,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.add(this.genderText); this.add(this.genderText);
if (!this.player) { if (!this.player) {
this.ownedIcon = this.scene.add.image(0, 0, 'icon_owned'); this.ownedIcon = this.scene.add.sprite(0, 0, 'icon_owned');
this.ownedIcon.setVisible(false); this.ownedIcon.setVisible(false);
this.ownedIcon.setOrigin(0, 0); this.ownedIcon.setOrigin(0, 0);
this.ownedIcon.setPositionRelative(this.nameText, 0, 11.5); this.ownedIcon.setPositionRelative(this.nameText, 0, 11.5);
@ -115,10 +120,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
} }
this.hpBar.setScale(pokemon.getHpRatio(), 1); this.hpBar.setScale(pokemon.getHpRatio(), 1);
this.lastHpFrame = this.hpBar.scaleX > 0.5 ? 'high' : this.hpBar.scaleX > 0.25 ? 'medium' : 'low';
this.hpBar.setFrame(this.lastHpFrame);
if (this.player) if (this.player)
this.setHpNumbers(pokemon.hp, pokemon.getMaxHp()); this.setHpNumbers(pokemon.hp, pokemon.getMaxHp());
this.lastHp = pokemon.hp; this.lastHp = pokemon.hp;
this.lastHpFrame = this.hpBar.scaleX > 0.5 ? 'high' : this.hpBar.scaleX > 0.25 ? 'medium' : 'low';
this.lastMaxHp = pokemon.getMaxHp(); this.lastMaxHp = pokemon.getMaxHp();
this.setLevel(pokemon.level); this.setLevel(pokemon.level);
@ -131,6 +137,39 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
} }
} }
getTextureName(): string {
return `pbinfo_${this.player ? 'player' : 'enemy'}${this.mini ? '_mini' : ''}`;
}
setMini(mini: boolean): void {
if (this.mini === mini)
return;
this.mini = mini;
this.box.setTexture(this.getTextureName());
if (this.player) {
this.y -= 12 * (mini ? 1 : -1);
}
const offsetElements = [ this.nameText, this.genderText, this.statusIndicator, this.levelContainer ];
offsetElements.forEach(el => el.y += 1.5 * (mini ? -1 : 1));
const toggledElements = [ this.hpNumbersContainer, this.expBar ];
toggledElements.forEach(el => el.setVisible(!mini));
}
setOffset(offset: boolean): void {
if (this.offset === offset)
return;
this.offset = offset;
this.x += 10 * (offset === this.player ? 1 : -1);
this.y += 27 * (offset ? 1 : -1);
}
updateInfo(pokemon: Pokemon, instant?: boolean): Promise<void> { updateInfo(pokemon: Pokemon, instant?: boolean): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
if (!this.scene) { if (!this.scene) {
@ -290,4 +329,6 @@ export class EnemyBattleInfo extends BattleInfo {
constructor(scene: Phaser.Scene) { constructor(scene: Phaser.Scene) {
super(scene, 140, -141, false); super(scene, 140, -141, false);
} }
setMini(mini: boolean): void { } // Always mini
} }

View File

@ -43,7 +43,7 @@ export default class CommandUiHandler extends UiHandler {
const messageHandler = this.getUi().getMessageHandler(); const messageHandler = this.getUi().getMessageHandler();
messageHandler.bg.setTexture('bg_command'); messageHandler.bg.setTexture('bg_command');
messageHandler.message.setWordWrapWidth(1110); messageHandler.message.setWordWrapWidth(1110);
messageHandler.showText(`What will\n${this.scene.getPlayerPokemon().name} do?`, 0); messageHandler.showText(`What will\n${(this.scene.getCurrentPhase() as CommandPhase).getPokemon().name} do?`, 0);
this.setCursor(this.cursor); this.setCursor(this.cursor);
} }
@ -65,7 +65,7 @@ export default class CommandUiHandler extends UiHandler {
success = true; success = true;
break; break;
case 2: case 2:
ui.setMode(Mode.PARTY, PartyUiMode.SWITCH); ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex());
success = true; success = true;
break; break;
case 3: case 3:

View File

@ -14,6 +14,12 @@ export default class EvolutionSceneHandler extends UiHandler {
this.scene.fieldUI.add(this.evolutionContainer); this.scene.fieldUI.add(this.evolutionContainer);
} }
show(_args: any[]): void {
super.show(_args);
this.scene.fieldUI.bringToTop(this.evolutionContainer);
}
processInput(button: Button) { processInput(button: Button) {
this.scene.ui.getMessageHandler().processInput(button); this.scene.ui.getMessageHandler().processInput(button);
} }

View File

@ -91,7 +91,7 @@ export default class FightUiHandler extends UiHandler {
ui.add(this.cursorObj); ui.add(this.cursorObj);
} }
const moveset = this.scene.getPlayerPokemon().getMoveset(); const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset();
const hasMove = cursor < moveset.length; const hasMove = cursor < moveset.length;
@ -114,7 +114,7 @@ export default class FightUiHandler extends UiHandler {
} }
displayMoves() { displayMoves() {
const moveset = this.scene.getPlayerPokemon().getMoveset(); const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset();
for (let m = 0; m < 4; m++) { for (let m = 0; m < 4; m++) {
const moveText = addTextObject(this.scene, m % 2 === 0 ? 0 : 100, m < 2 ? 0 : 16, '-', TextStyle.WINDOW); const moveText = addTextObject(this.scene, m % 2 === 0 ? 0 : 100, m < 2 ? 0 : 16, '-', TextStyle.WINDOW);
if (m < moveset.length) if (m < moveset.length)

View File

@ -42,7 +42,9 @@ export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string;
export default class PartyUiHandler extends MessageUiHandler { export default class PartyUiHandler extends MessageUiHandler {
private partyUiMode: PartyUiMode; private partyUiMode: PartyUiMode;
private fieldIndex: integer;
private partyBg: Phaser.GameObjects.Image;
private partyContainer: Phaser.GameObjects.Container; private partyContainer: Phaser.GameObjects.Container;
private partySlotsContainer: Phaser.GameObjects.Container; private partySlotsContainer: Phaser.GameObjects.Container;
private partySlots: PartySlot[]; private partySlots: PartySlot[];
@ -67,7 +69,7 @@ export default class PartyUiHandler extends MessageUiHandler {
private static FilterAll = (_pokemon: PlayerPokemon) => null; private static FilterAll = (_pokemon: PlayerPokemon) => null;
public static FilterNonFainted = (pokemon: PlayerPokemon) => { public static FilterNonFainted = (pokemon: PlayerPokemon) => {
if (!pokemon.hp) if (pokemon.isFainted())
return `${pokemon.name} has no energy\nleft to battle!`; return `${pokemon.name} has no energy\nleft to battle!`;
return null; return null;
}; };
@ -96,10 +98,10 @@ export default class PartyUiHandler extends MessageUiHandler {
this.partyContainer = partyContainer; this.partyContainer = partyContainer;
const partyBg = this.scene.add.image(0, 0, 'party_bg'); this.partyBg = this.scene.add.image(0, 0, 'party_bg');
partyContainer.add(partyBg); partyContainer.add(this.partyBg);
partyBg.setOrigin(0, 1); this.partyBg.setOrigin(0, 1);
const partySlotsContainer = this.scene.add.container(0, 0); const partySlotsContainer = this.scene.add.container(0, 0);
partyContainer.add(partySlotsContainer); partyContainer.add(partySlotsContainer);
@ -143,17 +145,20 @@ export default class PartyUiHandler extends MessageUiHandler {
this.partyUiMode = args[0] as PartyUiMode; this.partyUiMode = args[0] as PartyUiMode;
this.fieldIndex = args.length > 1 ? args[1] as integer : -1;
this.partyContainer.setVisible(true); this.partyContainer.setVisible(true);
this.partyBg.setTexture(`party_bg${this.scene.currentBattle.double ? '_double' : ''}`);
this.populatePartySlots(); this.populatePartySlots();
this.setCursor(this.cursor < 6 ? this.cursor : 0); this.setCursor(this.cursor < 6 ? this.cursor : 0);
if (args.length > 1 && args[1] instanceof Function) if (args.length > 2 && args[2] instanceof Function)
this.selectCallback = args[1]; this.selectCallback = args[2];
this.selectFilter = args.length > 2 && args[2] instanceof Function this.selectFilter = args.length > 3 && args[3] instanceof Function
? args[2] as PokemonSelectFilter ? args[3] as PokemonSelectFilter
: PartyUiHandler.FilterAll; : PartyUiHandler.FilterAll;
this.moveSelectFilter = args.length > 3 && args[3] instanceof Function this.moveSelectFilter = args.length > 4 && args[4] instanceof Function
? args[3] as PokemonMoveSelectFilter ? args[4] as PokemonMoveSelectFilter
: PartyUiHandler.FilterAllMoves; : PartyUiHandler.FilterAllMoves;
} }
@ -226,7 +231,7 @@ export default class PartyUiHandler extends MessageUiHandler {
} else if (option === PartyOption.RELEASE) { } else if (option === PartyOption.RELEASE) {
this.clearOptions(); this.clearOptions();
ui.playSelect(); ui.playSelect();
if (this.cursor) { if (this.cursor >= this.scene.currentBattle.getBattlerCount()) {
this.showText(`Do you really want to release ${pokemon.name}?`, null, () => { this.showText(`Do you really want to release ${pokemon.name}?`, null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => {
ui.setMode(Mode.PARTY); ui.setMode(Mode.PARTY);
@ -292,12 +297,13 @@ export default class PartyUiHandler extends MessageUiHandler {
success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0); success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0);
break; break;
case Button.LEFT: case Button.LEFT:
if (this.cursor && this.cursor < 6) if (this.cursor >= this.scene.currentBattle.getBattlerCount() && this.cursor < 6)
success = this.setCursor(0); success = this.setCursor(0);
break; break;
case Button.RIGHT: case Button.RIGHT:
if (!this.cursor) const battlerCount = this.scene.currentBattle.getBattlerCount();
success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || 1 : 1); if (this.cursor < battlerCount)
success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount);
break; break;
} }
} }
@ -411,10 +417,11 @@ export default class PartyUiHandler extends MessageUiHandler {
case PartyUiMode.SWITCH: case PartyUiMode.SWITCH:
case PartyUiMode.FAINT_SWITCH: case PartyUiMode.FAINT_SWITCH:
case PartyUiMode.POST_BATTLE_SWITCH: case PartyUiMode.POST_BATTLE_SWITCH:
if (this.cursor) { if (this.cursor >= this.scene.currentBattle.getBattlerCount()) {
this.options.push(PartyOption.SEND_OUT); this.options.push(PartyOption.SEND_OUT);
if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH
&& this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerPokemon().id)) && this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier
&& (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerField()[this.fieldIndex].id))
this.options.push(PartyOption.PASS_BATON); this.options.push(PartyOption.PASS_BATON);
} }
break; break;
@ -578,7 +585,9 @@ class PartySlot extends Phaser.GameObjects.Container {
private slotHpOverlay: Phaser.GameObjects.Sprite; private slotHpOverlay: Phaser.GameObjects.Sprite;
constructor(scene: BattleScene, slotIndex: integer, pokemon: PlayerPokemon) { constructor(scene: BattleScene, slotIndex: integer, pokemon: PlayerPokemon) {
super(scene, slotIndex ? 230.5 : 64, slotIndex ? -184 + 28 * slotIndex : -124); super(scene, slotIndex >= scene.currentBattle.getBattlerCount() ? 230.5 : 64,
slotIndex >= scene.currentBattle.getBattlerCount() ? -184 + (scene.currentBattle.double ? -38 : 0)
+ (28 + (scene.currentBattle.double ? 6 : 0)) * slotIndex : -124 + (scene.currentBattle.double ? -8 : 0) + slotIndex * 64);
this.slotIndex = slotIndex; this.slotIndex = slotIndex;
this.pokemon = pokemon; this.pokemon = pokemon;
@ -587,14 +596,16 @@ class PartySlot extends Phaser.GameObjects.Container {
} }
setup() { setup() {
const slotKey = `party_slot${this.slotIndex ? '' : '_main'}`; const battlerCount = (this.scene as BattleScene).currentBattle.getBattlerCount();
const slotKey = `party_slot${this.slotIndex >= battlerCount ? '' : '_main'}`;
const slotBg = this.scene.add.sprite(0, 0, slotKey, `${slotKey}${this.pokemon.hp ? '' : '_fnt'}`); const slotBg = this.scene.add.sprite(0, 0, slotKey, `${slotKey}${this.pokemon.hp ? '' : '_fnt'}`);
this.slotBg = slotBg; this.slotBg = slotBg;
this.add(slotBg); this.add(slotBg);
const slotPb = this.scene.add.sprite(this.slotIndex ? -85.5 : -51, this.slotIndex ? 0 : -20.5, 'party_pb'); const slotPb = this.scene.add.sprite(this.slotIndex >= battlerCount ? -85.5 : -51, this.slotIndex >= battlerCount ? 0 : -20.5, 'party_pb');
this.slotPb = slotPb; this.slotPb = slotPb;
this.add(slotPb); this.add(slotPb);
@ -609,7 +620,7 @@ class PartySlot extends Phaser.GameObjects.Container {
this.add(slotInfoContainer); this.add(slotInfoContainer);
const slotName = addTextObject(this.scene, 0, 0, this.pokemon.name, TextStyle.PARTY); const slotName = addTextObject(this.scene, 0, 0, this.pokemon.name, TextStyle.PARTY);
slotName.setPositionRelative(slotBg, this.slotIndex ? 21 : 24, this.slotIndex ? 3 : 10); slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 3 : 10);
slotName.setOrigin(0, 0); slotName.setOrigin(0, 0);
const slotLevelLabel = this.scene.add.image(0, 0, 'party_slot_overlay_lv'); const slotLevelLabel = this.scene.add.image(0, 0, 'party_slot_overlay_lv');
@ -621,7 +632,7 @@ class PartySlot extends Phaser.GameObjects.Container {
slotLevelText.setOrigin(0, 0.25); slotLevelText.setOrigin(0, 0.25);
const slotHpBar = this.scene.add.image(0, 0, 'party_slot_hp_bar'); const slotHpBar = this.scene.add.image(0, 0, 'party_slot_hp_bar');
slotHpBar.setPositionRelative(slotBg, this.slotIndex ? 72 : 8, this.slotIndex ? 7 : 31); slotHpBar.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 72 : 8, this.slotIndex >= battlerCount ? 7 : 31);
slotHpBar.setOrigin(0, 0); slotHpBar.setOrigin(0, 0);
const hpRatio = this.pokemon.getHpRatio(); const hpRatio = this.pokemon.getHpRatio();
@ -669,8 +680,9 @@ class PartySlot extends Phaser.GameObjects.Container {
} }
private updateSlotTexture(): void { private updateSlotTexture(): void {
this.slotBg.setTexture(`party_slot${this.slotIndex ? '' : '_main'}`, const battlerCount = (this.scene as BattleScene).currentBattle.getBattlerCount();
`party_slot${this.slotIndex ? '' : '_main'}${this.transfer ? '_swap' : this.pokemon.hp ? '' : '_fnt'}${this.selected ? '_sel' : ''}`); this.slotBg.setTexture(`party_slot${this.slotIndex >= battlerCount ? '' : '_main'}`,
`party_slot${this.slotIndex >= battlerCount ? '' : '_main'}${this.transfer ? '_swap' : this.pokemon.hp ? '' : '_fnt'}${this.selected ? '_sel' : ''}`);
} }
} }

View File

@ -0,0 +1,121 @@
import { BattlerIndex } 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: BattlerIndex[];
private targetFlashTween: Phaser.Tweens.Tween;
constructor(scene: BattleScene) {
super(scene, Mode.TARGET_SELECT);
this.cursor = -1;
}
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;
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 < BattlerIndex.ENEMY && this.targets.find(t => t >= BattlerIndex.ENEMY))
success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY));
break;
case Button.DOWN:
if (this.cursor >= BattlerIndex.ENEMY && this.targets.find(t => t < BattlerIndex.ENEMY))
success = this.setCursor(this.targets.find(t => t < BattlerIndex.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;
} }