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 battlespull/2/head
parent
e2d6890072
commit
b9f7ba173d
Binary file not shown.
After Width: | Height: | Size: 471 B |
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 |
|
@ -161,7 +161,7 @@ export class Arena {
|
|||
this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null;
|
||||
|
||||
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));
|
||||
} else
|
||||
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType));
|
||||
|
|
1073
src/battle-phases.ts
1073
src/battle-phases.ts
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +1,14 @@
|
|||
import Phaser from 'phaser';
|
||||
import { Biome } from './data/biome';
|
||||
import UI from './ui/ui';
|
||||
import { EncounterPhase, SummonPhase, CommandPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, CheckLoadPhase } from './battle-phases';
|
||||
import Pokemon, { PlayerPokemon, EnemyPokemon } from './pokemon';
|
||||
import { EncounterPhase, SummonPhase, NextEncounterPhase, NewBiomeEncounterPhase, SelectBiomePhase, MessagePhase, CheckLoadPhase, TurnInitPhase, ReturnPhase, ToggleDoublePositionPhase, CheckSwitchPhase } from './battle-phases';
|
||||
import Pokemon, { PlayerPokemon, EnemyPokemon, FieldPosition } from './pokemon';
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies, initSpecies } from './data/pokemon-species';
|
||||
import * as Utils from './utils';
|
||||
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate } from './modifier/modifier';
|
||||
import { PokeballType } from './data/pokeball';
|
||||
import { Species } from './data/species';
|
||||
import { initAutoPlay } from './system/auto-play';
|
||||
import { Battle } from './battle';
|
||||
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from './data/battle-anims';
|
||||
import { BattlePhase } from './battle-phase';
|
||||
import { initGameSpeed } from './system/game-speed';
|
||||
|
@ -20,7 +19,8 @@ import { TextStyle, addTextObject } from './ui/text';
|
|||
import { Moves, initMoves } from './data/move';
|
||||
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type';
|
||||
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 quickStart = false;
|
||||
|
@ -156,7 +156,8 @@ export default class BattleScene extends Phaser.Scene {
|
|||
this.loadAtlas('prompt', 'ui');
|
||||
this.loadImage('cursor', '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.loadAtlas('numbers', 'ui');
|
||||
this.loadAtlas('overlay_hp', 'ui');
|
||||
|
@ -168,6 +169,7 @@ export default class BattleScene extends Phaser.Scene {
|
|||
this.loadImage('boolean_window', 'ui');
|
||||
|
||||
this.loadImage('party_bg', 'ui');
|
||||
this.loadImage('party_bg_double', 'ui');
|
||||
this.loadAtlas('party_slot_main', 'ui');
|
||||
this.loadAtlas('party_slot', '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_highlight', 'ui');
|
||||
|
||||
this.loadImage('default_bg', 'arenas');
|
||||
|
||||
// Load arena images
|
||||
Utils.getEnumValues(Biome).map(bt => {
|
||||
const btKey = Biome[bt].toLowerCase();
|
||||
|
@ -446,21 +450,35 @@ export default class BattleScene extends Phaser.Scene {
|
|||
return this.party;
|
||||
}
|
||||
|
||||
getEnemyParty(): EnemyPokemon[] {
|
||||
return this.getEnemyPokemon() ? [ this.getEnemyPokemon() ] : [];
|
||||
getPlayerPokemon(): PlayerPokemon {
|
||||
return this.getPlayerField().find(p => p.isActive());
|
||||
}
|
||||
|
||||
getPlayerPokemon(): PlayerPokemon {
|
||||
return this.getParty()[0];
|
||||
getPlayerField(): PlayerPokemon[] {
|
||||
const party = this.getParty();
|
||||
return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1));
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
@ -475,7 +493,7 @@ export default class BattleScene extends Phaser.Scene {
|
|||
for (let p of this.getParty())
|
||||
p.destroy();
|
||||
this.party = [];
|
||||
for (let p of this.getEnemyParty())
|
||||
for (let p of this.getEnemyField())
|
||||
p.destroy();
|
||||
|
||||
this.currentBattle = null;
|
||||
|
@ -493,11 +511,26 @@ export default class BattleScene extends Phaser.Scene {
|
|||
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 (this.currentBattle) {
|
||||
this.getEnemyPokemon().destroy();
|
||||
if (this.currentBattle.waveIndex % 10)
|
||||
if (lastBattle) {
|
||||
this.getEnemyField().forEach(enemyPokemon => enemyPokemon.destroy());
|
||||
if (lastBattle.waveIndex % 10)
|
||||
this.pushPhase(new NextEncounterPhase(this));
|
||||
else {
|
||||
this.pushPhase(new SelectBiomePhase(this));
|
||||
|
@ -509,12 +542,29 @@ export default class BattleScene extends Phaser.Scene {
|
|||
else {
|
||||
this.arena.playBgm();
|
||||
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;
|
||||
}
|
||||
|
@ -699,7 +749,7 @@ export default class BattleScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
populatePhaseQueue(): void {
|
||||
this.phaseQueue.push(new CommandPhase(this));
|
||||
this.phaseQueue.push(new TurnInitPhase(this));
|
||||
}
|
||||
|
||||
addModifier(modifier: Modifier, playSound?: boolean, virtual?: boolean): Promise<void> {
|
||||
|
@ -828,7 +878,9 @@ export default class BattleScene extends Phaser.Scene {
|
|||
}
|
||||
if (isBoss)
|
||||
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());
|
||||
});
|
||||
|
@ -855,7 +907,7 @@ export default class BattleScene extends Phaser.Scene {
|
|||
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);
|
||||
if (!player)
|
||||
this.updateWaveCountPosition();
|
||||
|
|
|
@ -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";
|
||||
|
||||
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 enemyLevel: integer;
|
||||
public enemyPokemon: EnemyPokemon;
|
||||
public enemyLevels: integer[];
|
||||
public enemyField: EnemyPokemon[];
|
||||
public double: boolean;
|
||||
public turn: integer;
|
||||
public turnCommands: TurnCommands;
|
||||
public turnPokeballCounts: PokeballCounts;
|
||||
public playerParticipantIds: Set<integer> = new Set<integer>();
|
||||
public escapeAttempts: integer = 0;
|
||||
|
||||
constructor(waveIndex: integer) {
|
||||
constructor(waveIndex: integer, double: boolean) {
|
||||
this.waveIndex = waveIndex;
|
||||
this.enemyLevel = this.getLevelForWave();
|
||||
this.turn = 1;
|
||||
this.enemyLevels = new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave());
|
||||
this.enemyField = [];
|
||||
this.double = double;
|
||||
this.turn = 0;
|
||||
}
|
||||
|
||||
private getLevelForWave(): number {
|
||||
|
@ -29,8 +55,14 @@ export class Battle {
|
|||
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.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
|
||||
this.turnPokeballCounts = Object.assign({}, scene.pokeballCounts);
|
||||
}
|
||||
|
||||
addParticipant(playerPokemon: PlayerPokemon): void {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Pokemon, { MoveResult, PokemonMove } from "../pokemon";
|
||||
import Pokemon, { HitResult, MoveResult, PokemonMove } from "../pokemon";
|
||||
import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
import { BattleStat, getBattleStatName } from "./battle-stat";
|
||||
|
@ -83,6 +83,18 @@ export class BlockRecoilDamageAttr extends AbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
export class DoubleBattleChanceAbAttr extends AbAttr {
|
||||
constructor() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const doubleChance = (args[0] as Utils.IntegerHolder);
|
||||
doubleChance.value = Math.max(doubleChance.value / 2, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class PreDefendAbAttr extends AbAttr {
|
||||
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
return false;
|
||||
|
@ -155,9 +167,10 @@ export class TypeImmunityHealAbAttr extends TypeImmunityAbAttr {
|
|||
applyPreDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const ret = super.applyPreDefend(pokemon, attacker, move, cancelled, args);
|
||||
|
||||
if (ret && pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
if (ret) {
|
||||
if (pokemon.getHpRatio() < 1)
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
Math.max(Math.floor(pokemon.getMaxHp() / 4), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -181,7 +194,7 @@ class TypeImmunityStatChangeAbAttr extends TypeImmunityAbAttr {
|
|||
|
||||
if (ret) {
|
||||
cancelled.value = true;
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ this.stat ], this.levels));
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.levels));
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -232,14 +245,14 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
|
|||
}
|
||||
|
||||
export class PostDefendAbAttr extends AbAttr {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
if (moveResult < MoveResult.NO_EFFECT) {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
if (hitResult < HitResult.NO_EFFECT) {
|
||||
const type = move.getMove().type;
|
||||
const pokemonTypes = pokemon.getTypes();
|
||||
if (pokemonTypes.length !== 1 || pokemonTypes[0] !== type) {
|
||||
|
@ -267,7 +280,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
|
|||
this.effects = effects;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.getMove().hasFlag(MoveFlags.MAKES_CONTACT) && Utils.randInt(100) < this.chance) {
|
||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[Utils.randInt(this.effects.length)];
|
||||
return attacker.trySetStatus(effect);
|
||||
|
@ -290,7 +303,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
|||
this.turnCount = turnCount;
|
||||
}
|
||||
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, args: any[]): boolean {
|
||||
applyPostDefend(pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, args: any[]): boolean {
|
||||
if (move.getMove().hasFlag(MoveFlags.MAKES_CONTACT) && Utils.randInt(100) < this.chance)
|
||||
return attacker.addTag(this.tagType, this.turnCount, move.moveId, pokemon.id);
|
||||
|
||||
|
@ -406,13 +419,21 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
|
|||
}
|
||||
|
||||
applyPostSummon(pokemon: Pokemon, args: any[]): boolean {
|
||||
const statChangePhase = new StatChangePhase(pokemon.scene, pokemon.isPlayer() === this.selfTarget, this.selfTarget, this.stats, this.levels);
|
||||
const statChangePhases: StatChangePhase[] = [];
|
||||
|
||||
if (!this.selfTarget && !pokemon.getOpponent()?.summonData)
|
||||
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
|
||||
else
|
||||
pokemon.scene.unshiftPhase(statChangePhase);
|
||||
if (this.selfTarget)
|
||||
statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
|
||||
else {
|
||||
for (let opponent of pokemon.getOpponents())
|
||||
statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels));
|
||||
}
|
||||
|
||||
for (let statChangePhase of statChangePhases) {
|
||||
if (!this.selfTarget && !statChangePhase.getPokemon().summonData)
|
||||
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
|
||||
else
|
||||
pokemon.scene.unshiftPhase(statChangePhase);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -576,7 +597,7 @@ export class PostTurnAbAttr extends AbAttr {
|
|||
|
||||
export class PostTurnSpeedBoostAbAttr extends PostTurnAbAttr {
|
||||
applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ BattleStat.SPD ], 1));
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.SPD ], 1));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -594,7 +615,8 @@ export class PostTurnHealAbAttr extends PostTurnAbAttr {
|
|||
applyPostTurn(pokemon: Pokemon, args: any[]): boolean {
|
||||
if (pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
|
||||
Math.max(Math.floor(pokemon.getMaxHp() / 16), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -632,7 +654,8 @@ export class PostWeatherLapseHealAbAttr extends PostWeatherLapseAbAttr {
|
|||
applyPostWeatherLapse(pokemon: Pokemon, weather: Weather, args: any[]): boolean {
|
||||
if (pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.isPlayer(), Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(),
|
||||
Math.max(Math.floor(pokemon.getMaxHp() / (16 / this.healFactor)), 1), getPokemonMessage(pokemon, `'s ${pokemon.getAbility().name}\nrestored its HP a little!`), true));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -653,7 +676,7 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
|
|||
if (pokemon.getHpRatio() < 1) {
|
||||
const scene = pokemon.scene;
|
||||
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${pokemon.getAbility()}!`));
|
||||
scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), MoveResult.OTHER));
|
||||
scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
|
||||
pokemon.damage(Math.ceil(pokemon.getMaxHp() * (16 / this.damageFactor)));
|
||||
return true;
|
||||
}
|
||||
|
@ -685,7 +708,6 @@ export function applyAbAttrs(attrType: { new(...args: any[]): AbAttr }, pokemon:
|
|||
|
||||
const ability = pokemon.getAbility();
|
||||
const attrs = ability.getAttrs(attrType) as AbAttr[];
|
||||
console.log(attrs, ability);
|
||||
for (let attr of attrs) {
|
||||
if (!canApplyAttr(pokemon, attr))
|
||||
continue;
|
||||
|
@ -726,7 +748,7 @@ export function applyPreDefendAbAttrs(attrType: { new(...args: any[]): PreDefend
|
|||
}
|
||||
|
||||
export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefendAbAttr },
|
||||
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, moveResult: MoveResult, ...args: any[]): void {
|
||||
pokemon: Pokemon, attacker: Pokemon, move: PokemonMove, hitResult: HitResult, ...args: any[]): void {
|
||||
if (!pokemon.canApplyAbility())
|
||||
return;
|
||||
|
||||
|
@ -736,7 +758,7 @@ export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefe
|
|||
if (!canApplyAttr(pokemon, attr))
|
||||
continue;
|
||||
pokemon.scene.setPhaseQueueSplice();
|
||||
if (attr.applyPostDefend(pokemon, attacker, move, moveResult, args)) {
|
||||
if (attr.applyPostDefend(pokemon, attacker, move, hitResult, args)) {
|
||||
if (attr.showAbility)
|
||||
queueShowAbility(pokemon);
|
||||
const message = attr.getTriggerMessage(pokemon, attacker, move);
|
||||
|
@ -988,7 +1010,7 @@ function canApplyAttr(pokemon: Pokemon, attr: AbAttr): boolean {
|
|||
}
|
||||
|
||||
function queueShowAbility(pokemon: Pokemon): void {
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.scene.clearPhaseQueueSplice();
|
||||
}
|
||||
|
||||
|
@ -1210,7 +1232,8 @@ export function initAbilities() {
|
|||
new Ability(Abilities.HUSTLE, "Hustle (N)", "Boosts the ATTACK stat, but lowers accuracy.", 3),
|
||||
new Ability(Abilities.HYPER_CUTTER, "Hyper Cutter", "Prevents other POKéMON from lowering ATTACK stat.", 3)
|
||||
.attr(ProtectStatAbAttr, BattleStat.ATK),
|
||||
new Ability(Abilities.ILLUMINATE, "Illuminate (N)", "Raises the likelihood of meeting wild POKéMON.", 3),
|
||||
new Ability(Abilities.ILLUMINATE, "Illuminate", "Raises the likelihood of an encounter being a double battle.", 3)
|
||||
.attr(DoubleBattleChanceAbAttr),
|
||||
new Ability(Abilities.IMMUNITY, "Immunity", "Prevents the POKéMON from getting poisoned.", 3)
|
||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON),
|
||||
new Ability(Abilities.INNER_FOCUS, "Inner Focus", "The POKéMON is protected from flinching.", 3)
|
||||
|
@ -1232,8 +1255,8 @@ export function initAbilities() {
|
|||
new Ability(Abilities.MAGMA_ARMOR, "Magma Armor", "Prevents the POKéMON from becoming frozen.", 3)
|
||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE),
|
||||
new Ability(Abilities.MAGNET_PULL, "Magnet Pull", "Prevents STEEL-type POKéMON from escaping.", 3)
|
||||
.attr(ArenaTrapAbAttr)
|
||||
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL)),
|
||||
/*.attr(ArenaTrapAbAttr)
|
||||
.condition((pokemon: Pokemon) => pokemon.getOpponent()?.isOfType(Type.STEEL))*/, // TODO: Rework
|
||||
new Ability(Abilities.MARVEL_SCALE, "Marvel Scale (N)", "Ups DEFENSE if there is a status problem.", 3),
|
||||
new Ability(Abilities.MINUS, "Minus (N)", "Ups SP. ATK if another POKéMON has PLUS or MINUS.", 3),
|
||||
new Ability(Abilities.NATURAL_CURE, "Natural Cure (N)", "All status problems heal when it switches out.", 3),
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Type } from "./type";
|
|||
import * as Utils from "../utils";
|
||||
import { Moves, allMoves } from "./move";
|
||||
import { getPokemonMessage } from "../messages";
|
||||
import Pokemon, { DamageResult, MoveResult } from "../pokemon";
|
||||
import Pokemon, { DamageResult, HitResult, MoveResult } from "../pokemon";
|
||||
import { DamagePhase, ObtainStatusEffectPhase } from "../battle-phases";
|
||||
import { StatusEffect } from "./status-effect";
|
||||
import { BattlerTagType } from "./battler-tag";
|
||||
|
@ -143,8 +143,7 @@ class SpikesTag extends ArenaTrapTag {
|
|||
super.onAdd(arena);
|
||||
|
||||
const source = arena.scene.getPokemonById(this.sourceId);
|
||||
const target = source.getOpponent();
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
|
@ -152,7 +151,7 @@ class SpikesTag extends ArenaTrapTag {
|
|||
const damageHpRatio = 1 / (10 - 2 * this.layers);
|
||||
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is hurt\nby the spikes!'));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), MoveResult.OTHER));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
|
||||
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
|
||||
return true;
|
||||
}
|
||||
|
@ -170,15 +169,14 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|||
super.onAdd(arena);
|
||||
|
||||
const source = arena.scene.getPokemonById(this.sourceId);
|
||||
const target = source.getOpponent();
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${target.name}'s feet!`);
|
||||
arena.scene.queueMessage(`${this.getMoveName()} were scattered\nall around ${source.getOpponentDescriptor()}'s feet!`);
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
if (!pokemon.status && (!pokemon.isOfType(Type.FLYING) || pokemon.getTag(BattlerTagType.IGNORE_FLYING) || pokemon.scene.arena.getTag(ArenaTagType.GRAVITY))) {
|
||||
const toxic = this.layers > 1;
|
||||
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(),
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
!toxic ? StatusEffect.POISON : StatusEffect.TOXIC, null, `the ${this.getMoveName()}`));
|
||||
return true;
|
||||
}
|
||||
|
@ -196,8 +194,7 @@ class StealthRockTag extends ArenaTrapTag {
|
|||
super.onAdd(arena);
|
||||
|
||||
const source = arena.scene.getPokemonById(this.sourceId);
|
||||
const target = source.getOpponent();
|
||||
arena.scene.queueMessage(`Pointed stones float in the air\naround ${target.name}!`);
|
||||
arena.scene.queueMessage(`Pointed stones float in the air\naround ${source.getOpponentDescriptor()}!`);
|
||||
}
|
||||
|
||||
activateTrap(pokemon: Pokemon): boolean {
|
||||
|
@ -228,7 +225,7 @@ class StealthRockTag extends ArenaTrapTag {
|
|||
|
||||
if (damageHpRatio) {
|
||||
pokemon.scene.queueMessage(`Pointed stones dug into\n${pokemon.name}!`);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer(), MoveResult.OTHER));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex(), HitResult.OTHER));
|
||||
pokemon.damage(Math.ceil(pokemon.getMaxHp() * damageHpRatio));
|
||||
}
|
||||
|
||||
|
@ -242,8 +239,8 @@ export class TrickRoomTag extends ArenaTag {
|
|||
}
|
||||
|
||||
apply(args: any[]): boolean {
|
||||
const speedDelayed = args[0] as Utils.BooleanHolder;
|
||||
speedDelayed.value = !speedDelayed.value;
|
||||
const speedReversed = args[0] as Utils.BooleanHolder;
|
||||
speedReversed.value = !speedReversed.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
//import { battleAnimRawData } from "./battle-anim-raw-data";
|
||||
import BattleScene from "../battle-scene";
|
||||
import { ChargeAttr, Moves, allMoves, getMoveTarget } from "./move";
|
||||
import { ChargeAttr, Moves, allMoves } from "./move";
|
||||
import Pokemon from "../pokemon";
|
||||
import * as Utils from "../utils";
|
||||
import { BattlerIndex } from "../battle";
|
||||
//import fs from 'vite-plugin-fs/browser';
|
||||
|
||||
export enum AnimFrameTarget {
|
||||
|
@ -831,8 +832,8 @@ export class CommonBattleAnim extends BattleAnim {
|
|||
export class MoveAnim extends BattleAnim {
|
||||
public move: Moves;
|
||||
|
||||
constructor(move: Moves, user: Pokemon) {
|
||||
super(user, getMoveTarget(user, move));
|
||||
constructor(move: Moves, user: Pokemon, target: BattlerIndex) {
|
||||
super(user, user.scene.getField()[target]);
|
||||
|
||||
this.move = move;
|
||||
}
|
||||
|
@ -852,7 +853,7 @@ export class MoveChargeAnim extends MoveAnim {
|
|||
private chargeAnim: ChargeAnim;
|
||||
|
||||
constructor(chargeAnim: ChargeAnim, move: Moves, user: Pokemon) {
|
||||
super(move, user);
|
||||
super(move, user, 0);
|
||||
|
||||
this.chargeAnim = chargeAnim;
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ export class RechargingTag extends BattlerTag {
|
|||
onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
pokemon.getMoveQueue().push({ move: Moves.NONE })
|
||||
pokemon.getMoveQueue().push({ move: Moves.NONE, targets: [] })
|
||||
}
|
||||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
|
@ -177,7 +177,7 @@ export class ConfusedTag extends BattlerTag {
|
|||
onAdd(pokemon: Pokemon): void {
|
||||
super.onAdd(pokemon);
|
||||
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' became\nconfused!'));
|
||||
}
|
||||
|
||||
|
@ -198,14 +198,14 @@ export class ConfusedTag extends BattlerTag {
|
|||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nconfused!'));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CONFUSION));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CONFUSION));
|
||||
|
||||
if (Utils.randInt(2)) {
|
||||
const atk = pokemon.getBattleStat(Stat.ATK);
|
||||
const def = pokemon.getBattleStat(Stat.DEF);
|
||||
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * ((Utils.randInt(15) + 85) / 100));
|
||||
pokemon.scene.queueMessage('It hurt itself in its\nconfusion!');
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ export class InfatuatedTag extends BattlerTag {
|
|||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is in love\nwith ${pokemon.scene.getPokemonById(this.sourceId).name}!`));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.ATTRACT));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.ATTRACT));
|
||||
|
||||
if (Utils.randInt(2)) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nimmobilized by love!'));
|
||||
|
@ -282,12 +282,13 @@ export class SeedTag extends BattlerTag {
|
|||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||
|
||||
if (ret) {
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, !pokemon.isPlayer(), CommonAnim.LEECH_SEED));
|
||||
const source = pokemon.scene.getPokemonById(this.sourceId);
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, source.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.LEECH_SEED));
|
||||
|
||||
const damage = Math.max(Math.floor(pokemon.getMaxHp() / 8), 1);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, !pokemon.isPlayer(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, source.getBattlerIndex(), damage, getPokemonMessage(pokemon, '\'s health is\nsapped by LEECH SEED!'), false, true));
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -320,10 +321,10 @@ export class NightmareTag extends BattlerTag {
|
|||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is locked\nin a NIGHTMARE!'));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), CommonAnim.CURSE)); // TODO: Update animation type
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, CommonAnim.CURSE)); // TODO: Update animation type
|
||||
|
||||
const damage = Math.ceil(pokemon.getMaxHp() / 4);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
}
|
||||
|
||||
|
@ -344,7 +345,7 @@ export class IngrainTag extends TrappedTag {
|
|||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||
|
||||
if (ret)
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 16),
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), Math.floor(pokemon.getMaxHp() / 16),
|
||||
getPokemonMessage(pokemon, ` absorbed\nnutrients with its roots!`), true));
|
||||
|
||||
return ret;
|
||||
|
@ -374,7 +375,8 @@ export class AquaRingTag extends BattlerTag {
|
|||
const ret = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
|
||||
|
||||
if (ret)
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
Math.floor(pokemon.getMaxHp() / 16), `${this.getMoveName()} restored\n${pokemon.name}\'s HP!`, true));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -393,7 +395,7 @@ export class DrowsyTag extends BattlerTag {
|
|||
|
||||
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||
if (!super.lapse(pokemon, lapseType)) {
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.isPlayer(), StatusEffect.SLEEP));
|
||||
pokemon.scene.unshiftPhase(new ObtainStatusEffectPhase(pokemon.scene, pokemon.getBattlerIndex(), StatusEffect.SLEEP));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -423,10 +425,10 @@ export abstract class DamagingTrapTag extends TrappedTag {
|
|||
|
||||
if (ret) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby ${this.getMoveName()}!`));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.isPlayer(), this.commonAnim));
|
||||
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), undefined, this.commonAnim));
|
||||
|
||||
const damage = Math.ceil(pokemon.getMaxHp() / 16);
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new DamagePhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.damage(damage);
|
||||
}
|
||||
|
||||
|
@ -541,7 +543,7 @@ export class TruantTag extends BattlerTag {
|
|||
|
||||
if (lastMove && lastMove.move !== Moves.NONE) {
|
||||
(pokemon.scene.getCurrentPhase() as MovePhase).cancel();
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.isPlayer()));
|
||||
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' is\nloafing around!'));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PokemonHealPhase, StatChangePhase } from "../battle-phases";
|
||||
import { getPokemonMessage } from "../messages";
|
||||
import Pokemon, { MoveResult } from "../pokemon";
|
||||
import Pokemon, { HitResult, MoveResult } from "../pokemon";
|
||||
import { getBattleStatName } from "./battle-stat";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
import { BattlerTagType } from "./battler-tag";
|
||||
|
@ -54,12 +54,7 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
|
|||
case BerryType.LUM:
|
||||
return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED);
|
||||
case BerryType.ENIGMA:
|
||||
return (pokemon: Pokemon) => {
|
||||
const opponent = pokemon.getOpponent();
|
||||
const opponentLastMove = opponent ? opponent.getLastXMoves(1).find(() => true) : null; // TODO: Update so this works even if opponent has fainted
|
||||
|
||||
return opponentLastMove && opponentLastMove.turn === pokemon.scene.currentBattle?.turn - 1 && opponentLastMove.result === MoveResult.SUPER_EFFECTIVE;
|
||||
};
|
||||
return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length;
|
||||
case BerryType.LIECHI:
|
||||
case BerryType.GANLON:
|
||||
case BerryType.SALAC:
|
||||
|
@ -83,7 +78,8 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
case BerryType.SITRUS:
|
||||
case BerryType.ENIGMA:
|
||||
return (pokemon: Pokemon) => {
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.isPlayer(), Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
|
||||
pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(),
|
||||
Math.floor(pokemon.getMaxHp() / 4), getPokemonMessage(pokemon, `'s ${getBerryName(berryType)}\nrestored its HP!`), true));
|
||||
};
|
||||
case BerryType.LUM:
|
||||
return (pokemon: Pokemon) => {
|
||||
|
@ -101,13 +97,13 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
|
|||
case BerryType.APICOT:
|
||||
return (pokemon: Pokemon) => {
|
||||
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ battleStat ], 1));
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], 1));
|
||||
};
|
||||
case BerryType.LANSAT:
|
||||
return (pokemon: Pokemon) => {
|
||||
pokemon.addTag(BattlerTagType.CRIT_BOOST);
|
||||
};
|
||||
case BerryType.STARF:
|
||||
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.isPlayer(), true, [ BattleStat.RAND ], 2));
|
||||
return (pokemon: Pokemon) => pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ BattleStat.RAND ], 2));
|
||||
}
|
||||
}
|
324
src/data/move.ts
324
src/data/move.ts
|
@ -1,9 +1,9 @@
|
|||
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
|
||||
import { DamagePhase, EnemyMovePhase, ObtainStatusEffectPhase, PlayerMovePhase, PokemonHealPhase, StatChangePhase } from "../battle-phases";
|
||||
import { DamagePhase, MovePhase, ObtainStatusEffectPhase, PokemonHealPhase, StatChangePhase } from "../battle-phases";
|
||||
import { BattleStat } from "./battle-stat";
|
||||
import { BattlerTagType } from "./battler-tag";
|
||||
import { getPokemonMessage } from "../messages";
|
||||
import Pokemon, { AttackMoveResult, EnemyPokemon, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../pokemon";
|
||||
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../pokemon";
|
||||
import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
|
||||
import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
|
@ -11,6 +11,8 @@ import { WeatherType } from "./weather";
|
|||
import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
|
||||
import { BlockRecoilDamageAttr, applyAbAttrs } from "./ability";
|
||||
import { PokemonHeldItemModifier } from "../modifier/modifier";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Stat } from "./pokemon-stat";
|
||||
|
||||
export enum MoveCategory {
|
||||
PHYSICAL,
|
||||
|
@ -24,14 +26,13 @@ export enum MoveTarget {
|
|||
ALL_OTHERS,
|
||||
NEAR_OTHER,
|
||||
ALL_NEAR_OTHERS,
|
||||
ENEMY,
|
||||
NEAR_ENEMY,
|
||||
ALL_NEAR_ENEMIES,
|
||||
RANDOM_NEAR_ENEMY,
|
||||
ALL_ENEMIES,
|
||||
ATTACKER,
|
||||
ALLY,
|
||||
NEAR_ALLY,
|
||||
ALLY,
|
||||
USER_OR_NEAR_ALLY,
|
||||
USER_AND_ALLIES,
|
||||
ALL,
|
||||
|
@ -47,6 +48,7 @@ export enum MoveFlags {
|
|||
}
|
||||
|
||||
type MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||
type UserMoveCondition = (user: Pokemon, move: Move) => boolean;
|
||||
|
||||
export default class Move {
|
||||
public id: Moves;
|
||||
|
@ -159,12 +161,67 @@ export default class Move {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
let score = 0;
|
||||
|
||||
for (let attr of this.attrs)
|
||||
score += attr.getUserBenefitScore(user, target, move);
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
let score = 0;
|
||||
|
||||
for (let attr of this.attrs)
|
||||
score += attr.getTargetBenefitScore(user, target, move);
|
||||
|
||||
return score;
|
||||
}
|
||||
}
|
||||
|
||||
export class AttackMove extends Move {
|
||||
constructor(id: Moves, name: string, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, tm: integer, effect: string, chance: integer, priority: integer, generation: integer) {
|
||||
super(id, name, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, tm, effect, chance, priority, generation);
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
let ret = super.getTargetBenefitScore(user, target, move);
|
||||
|
||||
let attackScore = 0;
|
||||
|
||||
const effectiveness = target.getAttackMoveEffectiveness(this.type);
|
||||
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
|
||||
if (attackScore) {
|
||||
if (this.category === MoveCategory.PHYSICAL) {
|
||||
if (user.getBattleStat(Stat.ATK) > user.getBattleStat(Stat.SPATK)) {
|
||||
const statRatio = user.getBattleStat(Stat.SPATK) / user.getBattleStat(Stat.ATK);
|
||||
if (statRatio <= 0.75)
|
||||
attackScore *= 2;
|
||||
else if (statRatio <= 0.875)
|
||||
attackScore *= 1.5;
|
||||
}
|
||||
} else {
|
||||
if (user.getBattleStat(Stat.SPATK) > user.getBattleStat(Stat.ATK)) {
|
||||
const statRatio = user.getBattleStat(Stat.ATK) / user.getBattleStat(Stat.SPATK);
|
||||
if (statRatio <= 0.75)
|
||||
attackScore *= 2;
|
||||
else if (statRatio <= 0.875)
|
||||
attackScore *= 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
const power = new Utils.NumberHolder(this.power);
|
||||
applyMoveAttrs(VariablePowerAttr, user, target, move, power);
|
||||
|
||||
attackScore += Math.floor(power.value / 5);
|
||||
}
|
||||
|
||||
ret -= attackScore;
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusMove extends Move {
|
||||
|
@ -755,6 +812,14 @@ export abstract class MoveAttr {
|
|||
getCondition(): MoveCondition {
|
||||
return null;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveEffectAttr extends MoveAttr {
|
||||
|
@ -792,6 +857,10 @@ export class HighCritAttr extends MoveAttr {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
export class CritOnlyAttr extends MoveAttr {
|
||||
|
@ -800,6 +869,10 @@ export class CritOnlyAttr extends MoveAttr {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
export class FixedDamageAttr extends MoveAttr {
|
||||
|
@ -889,12 +962,16 @@ export class RecoilAttr extends MoveEffectAttr {
|
|||
return false;
|
||||
|
||||
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) / 4), 1);
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), MoveResult.OTHER));
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
|
||||
user.scene.queueMessage(getPokemonMessage(user, ' is hit\nwith recoil!'));
|
||||
user.damage(recoilDamage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return Math.floor((move.power / 5) / -4);
|
||||
}
|
||||
}
|
||||
|
||||
export class SacrificialAttr extends MoveEffectAttr {
|
||||
|
@ -906,11 +983,15 @@ export class SacrificialAttr extends MoveEffectAttr {
|
|||
if (!super.apply(user, target, move, args))
|
||||
return false;
|
||||
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.isPlayer(), MoveResult.OTHER));
|
||||
user.scene.unshiftPhase(new DamagePhase(user.scene, user.getBattlerIndex(), HitResult.OTHER));
|
||||
user.damage(user.getMaxHp());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return Math.ceil((1 - user.getHpRatio()) * 10) - 10;
|
||||
}
|
||||
}
|
||||
|
||||
export enum MultiHitType {
|
||||
|
@ -936,7 +1017,12 @@ export class HealAttr extends MoveEffectAttr {
|
|||
}
|
||||
|
||||
addHealPhase(user: Pokemon, healRatio: number) {
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
|
||||
Math.max(Math.floor(user.getMaxHp() * healRatio), 1), getPokemonMessage(user, ' regained\nhealth!'), true, !this.showAnim));
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return (1 - (this.selfTarget ? user : target).getHpRatio()) * 20;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -977,9 +1063,14 @@ export class HitHealAttr extends MoveHitEffectAttr {
|
|||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.isPlayer(), Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
|
||||
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
|
||||
Math.max(Math.floor(user.turnData.damageDealt * this.healRatio), 1), getPokemonMessage(target, ` had its\nenergy drained!`), false, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * ((move.power / 5) / 4));
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiHitAttr extends MoveAttr {
|
||||
|
@ -1016,6 +1107,10 @@ export class MultiHitAttr extends MoveAttr {
|
|||
(args[0] as Utils.IntegerHolder).value = hitTimes;
|
||||
return true;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
export class StatusEffectAttr extends MoveHitEffectAttr {
|
||||
|
@ -1034,12 +1129,16 @@ export class StatusEffectAttr extends MoveHitEffectAttr {
|
|||
if (statusCheck) {
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
if (!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0)) {
|
||||
user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.isPlayer(), this.effect, this.cureTurn));
|
||||
user.scene.unshiftPhase(new ObtainStatusEffectPhase(user.scene, pokemon.getBattlerIndex(), this.effect, this.cureTurn));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return !(this.selfTarget ? user : target).status ? Math.floor(move.chance * -0.1) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class StealHeldItemAttr extends MoveHitEffectAttr {
|
||||
|
@ -1048,8 +1147,7 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
|
|||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const heldItems = user.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
if (heldItems.length) {
|
||||
const stolenItem = heldItems[Utils.randInt(heldItems.length)];
|
||||
user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false);
|
||||
|
@ -1060,6 +1158,21 @@ export class StealHeldItemAttr extends MoveHitEffectAttr {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
||||
return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
return heldItems.length ? 5 : 0;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
const heldItems = this.getTargetHeldItems(target);
|
||||
return heldItems.length ? -5 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class HealStatusEffectAttr extends MoveEffectAttr {
|
||||
|
@ -1086,6 +1199,10 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return user.status ? 10 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class BypassSleepAttr extends MoveAttr {
|
||||
|
@ -1170,8 +1287,8 @@ export class ChargeAttr extends OverrideMoveEffectAttr {
|
|||
user.addTag(this.tagType, 1, move.id, user.id);
|
||||
if (this.chargeEffect)
|
||||
applyMoveAttrs(MoveEffectAttr, user, target, move);
|
||||
user.pushMoveHistory({ move: move.id, result: MoveResult.OTHER });
|
||||
user.getMoveQueue().push({ move: move.id, ignorePP: true });
|
||||
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
|
||||
user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
|
||||
resolve(true);
|
||||
});
|
||||
} else
|
||||
|
@ -1214,7 +1331,7 @@ export class StatChangeAttr extends MoveEffectAttr {
|
|||
|
||||
if (move.chance < 0 || move.chance === 100 || Utils.randInt(100) < move.chance) {
|
||||
const levels = this.getLevels(user);
|
||||
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.isPlayer() === this.selfTarget, this.selfTarget, this.stats, levels));
|
||||
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1224,6 +1341,12 @@ export class StatChangeAttr extends MoveEffectAttr {
|
|||
getLevels(_user: Pokemon): integer {
|
||||
return this.levels;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
// TODO: Add awareness of level limits
|
||||
const levels = this.getLevels(user);
|
||||
return (levels * 4) + (levels > 0 ? -2 : 2);
|
||||
}
|
||||
}
|
||||
|
||||
export class GrowthStatChangeAttr extends StatChangeAttr {
|
||||
|
@ -1273,7 +1396,7 @@ export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultipl
|
|||
let count = 0;
|
||||
let turnMove: TurnMove;
|
||||
|
||||
while (((turnMove = moveHistory.shift())?.move === move.id || (comboMoves.length && comboMoves.indexOf(turnMove?.move) > -1)) && (!resetOnFail || turnMove.result < MoveResult.NO_EFFECT)) {
|
||||
while (((turnMove = moveHistory.shift())?.move === move.id || (comboMoves.length && comboMoves.indexOf(turnMove?.move) > -1)) && (!resetOnFail || turnMove.result === MoveResult.SUCCESS)) {
|
||||
if (count < (limit - 1))
|
||||
count++;
|
||||
else if (resetOnLimit)
|
||||
|
@ -1458,16 +1581,16 @@ export class BlizzardAccuracyAttr extends VariableAccuracyAttr {
|
|||
}
|
||||
|
||||
export class MissEffectAttr extends MoveAttr {
|
||||
private missEffectFunc: MoveCondition;
|
||||
private missEffectFunc: UserMoveCondition;
|
||||
|
||||
constructor(missEffectFunc: MoveCondition) {
|
||||
constructor(missEffectFunc: UserMoveCondition) {
|
||||
super();
|
||||
|
||||
this.missEffectFunc = missEffectFunc;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
this.missEffectFunc(user, target, move);
|
||||
this.missEffectFunc(user, move);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1530,7 +1653,7 @@ export class FrenzyAttr extends MoveEffectAttr {
|
|||
}
|
||||
|
||||
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
||||
return !!(this.selfTarget ? user.hp : target.hp);
|
||||
return !(this.selfTarget ? user : target).isFainted();
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
|
@ -1540,7 +1663,7 @@ export class FrenzyAttr extends MoveEffectAttr {
|
|||
if (!user.getMoveQueue().length) {
|
||||
if (!user.getTag(BattlerTagType.FRENZY)) {
|
||||
const turnCount = Utils.randInt(2) + 1;
|
||||
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, ignorePP: true }));
|
||||
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
|
||||
user.addTag(BattlerTagType.FRENZY, 1, move.id, user.id);
|
||||
} else {
|
||||
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
|
||||
|
@ -1553,7 +1676,7 @@ export class FrenzyAttr extends MoveEffectAttr {
|
|||
}
|
||||
}
|
||||
|
||||
export const frenzyMissFunc: MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
export const frenzyMissFunc: UserMoveCondition = (user: Pokemon, move: Move) => {
|
||||
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id)
|
||||
user.getMoveQueue().shift();
|
||||
user.lapseTag(BattlerTagType.FRENZY);
|
||||
|
@ -1596,6 +1719,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||
? (user: Pokemon, target: Pokemon, move: Move) => !(this.selfTarget ? user : target).getTag(this.tagType)
|
||||
: null;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
switch (this.tagType) {
|
||||
case BattlerTagType.FLINCHED:
|
||||
return -5;
|
||||
case BattlerTagType.CONFUSED:
|
||||
return -5;
|
||||
case BattlerTagType.INFATUATED:
|
||||
return -5;
|
||||
case BattlerTagType.SEEDED:
|
||||
return -3;
|
||||
case BattlerTagType.NIGHTMARE:
|
||||
return -5;
|
||||
case BattlerTagType.FRENZY:
|
||||
return -2;
|
||||
case BattlerTagType.INGRAIN:
|
||||
return 3;
|
||||
case BattlerTagType.AQUA_RING:
|
||||
return 3;
|
||||
case BattlerTagType.DROWSY:
|
||||
return -5;
|
||||
case BattlerTagType.TRAPPED:
|
||||
case BattlerTagType.BIND:
|
||||
case BattlerTagType.WRAP:
|
||||
case BattlerTagType.FIRE_SPIN:
|
||||
case BattlerTagType.WHIRLPOOL:
|
||||
case BattlerTagType.CLAMP:
|
||||
case BattlerTagType.SAND_TOMB:
|
||||
case BattlerTagType.MAGMA_STORM:
|
||||
return -3;
|
||||
case BattlerTagType.PROTECTED:
|
||||
return 10;
|
||||
case BattlerTagType.FLYING:
|
||||
return 5;
|
||||
case BattlerTagType.CRIT_BOOST:
|
||||
return 5;
|
||||
case BattlerTagType.NO_CRIT:
|
||||
return -5;
|
||||
case BattlerTagType.IGNORE_ACCURACY:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LapseBattlerTagAttr extends MoveEffectAttr {
|
||||
|
@ -1646,7 +1811,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
|||
let timesUsed = 0;
|
||||
const moveHistory = user.getLastXMoves(-1);
|
||||
let turnMove: TurnMove;
|
||||
while (moveHistory.length && (turnMove = moveHistory.shift()).move === move.id && turnMove.result === MoveResult.STATUS)
|
||||
while (moveHistory.length && (turnMove = moveHistory.shift()).move === move.id && turnMove.result === MoveResult.SUCCESS)
|
||||
timesUsed++;
|
||||
if (timesUsed)
|
||||
return !Utils.randInt(Math.pow(2, timesUsed));
|
||||
|
@ -1680,6 +1845,10 @@ export class HitsTagAttr extends MoveAttr {
|
|||
this.tagType = tagType;
|
||||
this.doubleDamage = !!doubleDamage;
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class AddArenaTagAttr extends MoveEffectAttr {
|
||||
|
@ -1768,10 +1937,16 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
|
|||
if (moves.length) {
|
||||
const move = moves[Utils.randInt(moves.length)];
|
||||
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId);
|
||||
user.getMoveQueue().push({ move: move.moveId, ignorePP: this.enemyMoveset });
|
||||
user.scene.unshiftPhase(user.isPlayer()
|
||||
? new PlayerMovePhase(user.scene, user as PlayerPokemon, moveset[moveIndex], true)
|
||||
: new EnemyMovePhase(user.scene, user as EnemyPokemon, moveset[moveIndex], true));
|
||||
const moveTargets = getMoveTargets(user, move.moveId);
|
||||
if (!moveTargets.targets.length)
|
||||
return false;
|
||||
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
||||
? moveTargets.targets
|
||||
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
||||
? [ target.getBattlerIndex() ]
|
||||
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
|
||||
user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: this.enemyMoveset });
|
||||
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1784,10 +1959,19 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr {
|
|||
return new Promise(resolve => {
|
||||
const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL));
|
||||
const moveId = moveIds[Utils.randInt(moveIds.length)];
|
||||
user.getMoveQueue().push({ move: moveId, ignorePP: true });
|
||||
user.scene.unshiftPhase(user.isPlayer()
|
||||
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(moveId, 0, 0, true), true)
|
||||
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(moveId, 0, 0, true), true));
|
||||
|
||||
const moveTargets = getMoveTargets(user, moveId);
|
||||
if (!moveTargets.targets.length) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
||||
? moveTargets.targets
|
||||
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
||||
? [ target.getBattlerIndex() ]
|
||||
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
|
||||
user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true });
|
||||
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true));
|
||||
initMoveAnim(moveId).then(() => {
|
||||
loadMoveAnimAssets(user.scene, [ moveId ], true)
|
||||
.then(() => resolve(true));
|
||||
|
@ -1822,10 +2006,18 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
|
|||
|
||||
const copiedMove = targetMoves[0];
|
||||
|
||||
user.getMoveQueue().push({ move: copiedMove.move, ignorePP: true });
|
||||
user.scene.unshiftPhase(user.isPlayer()
|
||||
? new PlayerMovePhase(user.scene, user as PlayerPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true)
|
||||
: new EnemyMovePhase(user.scene, user as EnemyPokemon, new PokemonMove(copiedMove.move, 0, 0, true), true));
|
||||
const moveTargets = getMoveTargets(user, copiedMove.move);
|
||||
if (!moveTargets.targets.length)
|
||||
return false;
|
||||
|
||||
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
||||
? moveTargets.targets
|
||||
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
||||
? [ target.getBattlerIndex() ]
|
||||
: [ moveTargets.targets[Utils.randInt(moveTargets.targets.length)] ];
|
||||
user.getMoveQueue().push({ move: copiedMove.move, targets: targets, ignorePP: true });
|
||||
|
||||
user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(copiedMove.move, 0, 0, true), true));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1932,20 +2124,60 @@ export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon
|
|||
return applyMoveAttrsInternal(attrFilter, user, target, move, args);
|
||||
}
|
||||
|
||||
export function getMoveTarget(user: Pokemon, move: Moves): Pokemon {
|
||||
const moveTarget = allMoves[move].moveTarget;
|
||||
export type MoveTargetSet = {
|
||||
targets: BattlerIndex[];
|
||||
multiple: boolean;
|
||||
}
|
||||
|
||||
const other = user.getOpponent();
|
||||
export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
|
||||
const moveTarget = move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
|
||||
const opponents = user.getOpponents();
|
||||
|
||||
let set: BattlerIndex[] = [];
|
||||
let multiple = false;
|
||||
|
||||
switch (moveTarget) {
|
||||
case MoveTarget.USER:
|
||||
set = [ user.getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.NEAR_OTHER:
|
||||
case MoveTarget.OTHER:
|
||||
case MoveTarget.ALL_NEAR_OTHERS:
|
||||
case MoveTarget.ALL_OTHERS:
|
||||
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattlerIndex());
|
||||
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS
|
||||
break;
|
||||
case MoveTarget.NEAR_ENEMY:
|
||||
case MoveTarget.ALL_NEAR_ENEMIES:
|
||||
case MoveTarget.ALL_ENEMIES:
|
||||
case MoveTarget.ENEMY_SIDE:
|
||||
set = opponents.map(p => p.getBattlerIndex());
|
||||
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
|
||||
break;
|
||||
case MoveTarget.RANDOM_NEAR_ENEMY:
|
||||
set = [ opponents[Utils.randInt(opponents.length)].getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.ATTACKER:
|
||||
set = [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.NEAR_ALLY:
|
||||
case MoveTarget.ALLY:
|
||||
set = [ user.getAlly()?.getBattlerIndex() ];
|
||||
break;
|
||||
case MoveTarget.USER_OR_NEAR_ALLY:
|
||||
case MoveTarget.USER_AND_ALLIES:
|
||||
case MoveTarget.USER_SIDE:
|
||||
return user;
|
||||
default:
|
||||
return other;
|
||||
set = [ user, user.getAlly() ].map(p => p?.getBattlerIndex());
|
||||
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
|
||||
break;
|
||||
case MoveTarget.ALL:
|
||||
case MoveTarget.BOTH_SIDES:
|
||||
set = [ user, user.getAlly() ].concat(user.getOpponents()).map(p => p?.getBattlerIndex());
|
||||
multiple = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return { targets: set.filter(t => t !== undefined), multiple };
|
||||
}
|
||||
|
||||
export const allMoves = [
|
||||
|
@ -2002,7 +2234,7 @@ export function initMoves() {
|
|||
.attr(MultiHitAttr, MultiHitType._2),
|
||||
new AttackMove(Moves.MEGA_KICK, "Mega Kick", Type.NORMAL, MoveCategory.PHYSICAL, 120, 75, 5, -1, "", -1, 0, 1),
|
||||
new AttackMove(Moves.JUMP_KICK, "Jump Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, "If it misses, the user loses half their HP.", -1, 0, 1)
|
||||
.attr(MissEffectAttr, (user: Pokemon, target: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.attr(MissEffectAttr, (user: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.condition(failOnGravityCondition),
|
||||
new AttackMove(Moves.ROLLING_KICK, "Rolling Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, -1, "May cause flinching.", 30, 0, 1)
|
||||
.attr(FlinchAttr),
|
||||
|
@ -2070,13 +2302,15 @@ export function initMoves() {
|
|||
.target(MoveTarget.USER_SIDE),
|
||||
new AttackMove(Moves.WATER_GUN, "Water Gun", Type.WATER, MoveCategory.SPECIAL, 40, 100, 25, -1, "", -1, 0, 1),
|
||||
new AttackMove(Moves.HYDRO_PUMP, "Hydro Pump", Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, 142, "", -1, 0, 1),
|
||||
new AttackMove(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 0, 1), // TODO
|
||||
new AttackMove(Moves.SURF, "Surf", Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 123, "Hits all adjacent Pokémon.", -1, 0, 1)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.ICE_BEAM, "Ice Beam", Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 135, "May freeze opponent.", 10, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.BLIZZARD, "Blizzard", Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 143, "May freeze opponent.", 10, 0, 1)
|
||||
.attr(BlizzardAccuracyAttr)
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE), // TODO: 30% chance to hit protect/detect in hail
|
||||
.attr(StatusEffectAttr, StatusEffect.FREEZE) // TODO: 30% chance to hit protect/detect in hail
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.PSYBEAM, "Psybeam", Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 16, "May confuse opponent.", 10, 0, 1)
|
||||
.attr(ConfuseAttr)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
|
@ -2253,7 +2487,7 @@ export function initMoves() {
|
|||
new SelfStatusMove(Moves.SOFT_BOILED, "Soft-Boiled", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 0, 1)
|
||||
.attr(HealAttr, 0.5),
|
||||
new AttackMove(Moves.HIGH_JUMP_KICK, "High Jump Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 130, 90, 10, -1, "If it misses, the user loses half their HP.", -1, 0, 1)
|
||||
.attr(MissEffectAttr, (user: Pokemon, target: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.attr(MissEffectAttr, (user: Pokemon, move: Move) => { user.damage(Math.floor(user.getMaxHp() / 2)); return true; })
|
||||
.condition(failOnGravityCondition),
|
||||
new StatusMove(Moves.GLARE, "Glare", Type.NORMAL, 100, 30, -1, "Paralyzes opponent.", -1, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||
|
@ -2337,7 +2571,7 @@ export function initMoves() {
|
|||
.ignoresVirtual(),
|
||||
new AttackMove(Moves.TRIPLE_KICK, "Triple Kick", Type.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, "Hits thrice in one turn at increasing power.", -1, 0, 2)
|
||||
.attr(MultiHitAttr, MultiHitType._3_INCR)
|
||||
.attr(MissEffectAttr, (user: Pokemon, target: Pokemon, move: Move) => {
|
||||
.attr(MissEffectAttr, (user: Pokemon, move: Move) => {
|
||||
user.turnData.hitsLeft = 0;
|
||||
return true;
|
||||
}),
|
||||
|
|
|
@ -100,17 +100,10 @@ export class Weather {
|
|||
}
|
||||
|
||||
isEffectSuppressed(scene: BattleScene): boolean {
|
||||
const playerPokemon = scene.getPlayerPokemon();
|
||||
const enemyPokemon = scene.getEnemyPokemon();
|
||||
const field = scene.getField().filter(p => p);
|
||||
|
||||
if (playerPokemon) {
|
||||
const suppressWeatherEffectAbAttr = playerPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
|
||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (enemyPokemon) {
|
||||
const suppressWeatherEffectAbAttr = enemyPokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
|
||||
for (let pokemon of field) {
|
||||
const suppressWeatherEffectAbAttr = pokemon.getAbility().getAttrs(SuppressWeatherEffectAbAttr).find(() => true) as SuppressWeatherEffectAbAttr;
|
||||
if (suppressWeatherEffectAbAttr && (!this.isImmutable() || suppressWeatherEffectAbAttr.affectsImmutable))
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export class EvolutionPhase extends BattlePhase {
|
|||
|
||||
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.evolutionContainer.add(this.evolutionBaseBg);
|
||||
|
||||
|
@ -98,6 +98,7 @@ export class EvolutionPhase extends BattlePhase {
|
|||
const levelMoves = pokemon.getLevelMoves(this.lastLevel + 1);
|
||||
for (let lm of levelMoves)
|
||||
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm));
|
||||
this.scene.unshiftPhase(new EndEvolutionPhase(this.scene));
|
||||
|
||||
this.scene.time.delayedCall(1000, () => {
|
||||
const evolutionBgm = this.scene.sound.add('evolution');
|
||||
|
@ -444,3 +445,11 @@ export class EvolutionPhase extends BattlePhase {
|
|||
updateParticle();
|
||||
}
|
||||
}
|
||||
|
||||
export class EndEvolutionPhase extends BattlePhase {
|
||||
start() {
|
||||
super.start();
|
||||
|
||||
this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => this.end());
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ import * as Utils from '../utils';
|
|||
import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from '../data/temp-battle-stat';
|
||||
import { BerryType, getBerryEffectDescription, getBerryName } from '../data/berry';
|
||||
import { Unlockables } from '../system/unlockables';
|
||||
import { maxExpLevel } from '../battle-scene';
|
||||
|
||||
type Modifier = Modifiers.Modifier;
|
||||
|
||||
|
@ -128,7 +127,7 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType {
|
|||
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),
|
||||
((pokemon: PlayerPokemon) => {
|
||||
if (pokemon.hp)
|
||||
if (!pokemon.isFainted())
|
||||
return PartyUiHandler.NoEffectMessage;
|
||||
return null;
|
||||
}), iconImage, 'revive');
|
||||
|
@ -669,15 +668,15 @@ const modifierPool = {
|
|||
return statusEffectPartyMemberCount * 6;
|
||||
}),
|
||||
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;
|
||||
}),
|
||||
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;
|
||||
}),
|
||||
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[]) => {
|
||||
const thresholdPartyMemberCount = Math.min(party.filter(p => p.getInverseHp() >= 100 || p.getHpRatio() <= 0.625).length, 3);
|
||||
|
|
|
@ -480,7 +480,8 @@ export class TurnHealModifier extends PokemonHeldItemModifier {
|
|||
|
||||
if (pokemon.getHpRatio() < 1) {
|
||||
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;
|
||||
|
@ -509,7 +510,8 @@ export class HitHealModifier extends PokemonHeldItemModifier {
|
|||
|
||||
if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) {
|
||||
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;
|
||||
|
@ -1001,7 +1003,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
|
|||
|
||||
apply(args: any[]): boolean {
|
||||
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)
|
||||
return false;
|
||||
|
||||
|
|
242
src/pokemon.ts
242
src/pokemon.ts
|
@ -1,7 +1,7 @@
|
|||
import Phaser from 'phaser';
|
||||
import BattleScene from './battle-scene';
|
||||
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 { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species';
|
||||
import * as Utils from './utils';
|
||||
|
@ -21,10 +21,17 @@ import { BattlerTag, BattlerTagLapseType, BattlerTagType, TypeBoostTag, getBattl
|
|||
import { Species } from './data/species';
|
||||
import { WeatherType } from './data/weather';
|
||||
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 { 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 { BattlerIndex } from './battle';
|
||||
|
||||
export enum FieldPosition {
|
||||
CENTER,
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
public id: integer;
|
||||
|
@ -50,6 +57,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
public battleSummonData: PokemonBattleSummonData;
|
||||
public turnData: PokemonTurnData;
|
||||
|
||||
public fieldPosition: FieldPosition;
|
||||
|
||||
public maskEnabled: boolean;
|
||||
public maskSprite: Phaser.GameObjects.Sprite;
|
||||
|
||||
|
@ -139,7 +148,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
|
||||
this.calculateStats();
|
||||
|
||||
scene.fieldUI.addAt(this.battleInfo, 0);
|
||||
this.fieldPosition = FieldPosition.CENTER;
|
||||
|
||||
scene.fieldUI.add(this.battleInfo);
|
||||
|
||||
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 getFieldIndex(): integer;
|
||||
|
||||
abstract getBattlerIndex(): BattlerIndex;
|
||||
|
||||
loadAssets(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
const moveIds = this.getMoveset().map(m => m.getMove().id);
|
||||
|
@ -261,11 +284,57 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
: this.maskSprite;
|
||||
}
|
||||
|
||||
playAnim(): void{
|
||||
playAnim(): void {
|
||||
this.getSprite().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 {
|
||||
if (stat === Stat.HP)
|
||||
return this.stats[Stat.HP];
|
||||
|
@ -360,7 +429,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
canApplyAbility(): boolean {
|
||||
return !this.getAbility().conditions.find(condition => !condition(this));
|
||||
return this.hp && !this.getAbility().conditions.find(condition => !condition(this));
|
||||
}
|
||||
|
||||
getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier {
|
||||
|
@ -405,7 +474,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
setMove(moveIndex: integer, moveId: Moves): void {
|
||||
const move = moveId ? new PokemonMove(moveId) : null;
|
||||
this.moveset[moveIndex] = move;
|
||||
if (this.summonData.moveset)
|
||||
if (this.summonData?.moveset)
|
||||
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);
|
||||
}
|
||||
|
||||
getOpponent(): Pokemon {
|
||||
const ret = this.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon();
|
||||
getOpponent(targetIndex: integer): Pokemon {
|
||||
const ret = this.getOpponents()[targetIndex];
|
||||
if (ret.summonData)
|
||||
return ret;
|
||||
return null;
|
||||
}
|
||||
|
||||
apply(source: Pokemon, battlerMove: PokemonMove): MoveResult {
|
||||
let result: MoveResult;
|
||||
getOpponents(): Pokemon[] {
|
||||
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 moveCategory = move.category;
|
||||
let damage = 0;
|
||||
|
@ -526,7 +610,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
|
||||
|
||||
if (cancelled.value)
|
||||
result = MoveResult.NO_EFFECT;
|
||||
result = HitResult.NO_EFFECT;
|
||||
else {
|
||||
if (source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === move.type))
|
||||
power.value *= 1.5;
|
||||
|
@ -567,46 +651,50 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
if (damage && fixedDamage.value) {
|
||||
damage = fixedDamage.value;
|
||||
isCritical = false;
|
||||
result = MoveResult.EFFECTIVE;
|
||||
result = HitResult.EFFECTIVE;
|
||||
}
|
||||
|
||||
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
|
||||
|
||||
if (!result) {
|
||||
if (typeMultiplier.value >= 2)
|
||||
result = MoveResult.SUPER_EFFECTIVE;
|
||||
result = HitResult.SUPER_EFFECTIVE;
|
||||
else if (typeMultiplier.value >= 1)
|
||||
result = MoveResult.EFFECTIVE;
|
||||
result = HitResult.EFFECTIVE;
|
||||
else if (typeMultiplier.value > 0)
|
||||
result = MoveResult.NOT_VERY_EFFECTIVE;
|
||||
result = HitResult.NOT_VERY_EFFECTIVE;
|
||||
else
|
||||
result = MoveResult.NO_EFFECT;
|
||||
result = HitResult.NO_EFFECT;
|
||||
}
|
||||
|
||||
if (damage) {
|
||||
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)
|
||||
this.scene.queueMessage('A critical hit!');
|
||||
this.scene.setPhaseQueueSplice();
|
||||
this.damage(damage);
|
||||
source.turnData.damageDealt += damage;
|
||||
this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id });
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case MoveResult.SUPER_EFFECTIVE:
|
||||
case HitResult.SUPER_EFFECTIVE:
|
||||
this.scene.queueMessage('It\'s super effective!');
|
||||
break;
|
||||
case MoveResult.NOT_VERY_EFFECTIVE:
|
||||
case HitResult.NOT_VERY_EFFECTIVE:
|
||||
this.scene.queueMessage('It\'s not very effective!');
|
||||
break;
|
||||
case MoveResult.NO_EFFECT:
|
||||
case HitResult.NO_EFFECT:
|
||||
this.scene.queueMessage(`It doesn\'t affect ${this.name}!`);
|
||||
break;
|
||||
}
|
||||
|
||||
if (damage)
|
||||
this.scene.clearPhaseQueueSplice();
|
||||
}
|
||||
break;
|
||||
case MoveCategory.STATUS:
|
||||
result = MoveResult.STATUS;
|
||||
result = HitResult.STATUS;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -614,7 +702,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
damage(damage: integer, preventEndure?: boolean): void {
|
||||
if (!this.hp)
|
||||
if (this.isFainted())
|
||||
return;
|
||||
|
||||
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);
|
||||
if (!this.hp) {
|
||||
this.scene.pushPhase(new FaintPhase(this.scene, this.isPlayer()));
|
||||
if (this.isFainted()) {
|
||||
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex()));
|
||||
this.resetSummonData();
|
||||
this.getOpponent()?.resetBattleSummonData();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -946,6 +1033,14 @@ export class PlayerPokemon extends Pokemon {
|
|||
return true;
|
||||
}
|
||||
|
||||
getFieldIndex(): integer {
|
||||
return this.scene.getPlayerField().indexOf(this);
|
||||
}
|
||||
|
||||
getBattlerIndex(): BattlerIndex {
|
||||
return this.getFieldIndex();
|
||||
}
|
||||
|
||||
generateCompatibleTms(): void {
|
||||
this.compatibleTms = [];
|
||||
|
||||
|
@ -1041,7 +1136,7 @@ export class EnemyPokemon extends Pokemon {
|
|||
: null;
|
||||
if (queuedMove) {
|
||||
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 {
|
||||
this.getMoveQueue().shift();
|
||||
return this.getNextMove();
|
||||
|
@ -1051,52 +1146,26 @@ export class EnemyPokemon extends Pokemon {
|
|||
const movePool = this.getMoveset().filter(m => m.isUsable());
|
||||
if (movePool.length) {
|
||||
if (movePool.length === 1)
|
||||
return { move: movePool[0].moveId };
|
||||
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
|
||||
switch (this.aiType) {
|
||||
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:
|
||||
const target = this.scene.getPlayerPokemon();
|
||||
const moveScores = movePool.map(() => 0);
|
||||
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
|
||||
for (let m in movePool) {
|
||||
const pokemonMove = movePool[m];
|
||||
const move = pokemonMove.getMove();
|
||||
let moveScore = moveScores[m];
|
||||
if (move.category === MoveCategory.STATUS)
|
||||
moveScore++;
|
||||
else {
|
||||
const effectiveness = this.getAttackMoveEffectiveness(move.type);
|
||||
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);
|
||||
}
|
||||
for (let mt of moveTargets[move.id]) {
|
||||
const target = this.scene.getField()[mt];
|
||||
moveScore += move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
moveScore /= moveTargets[move.id].length
|
||||
|
||||
// could make smarter by checking opponent def/spdef
|
||||
moveScores[m] = moveScore;
|
||||
|
@ -1116,17 +1185,48 @@ export class EnemyPokemon extends Pokemon {
|
|||
r++;
|
||||
}
|
||||
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() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getFieldIndex(): integer {
|
||||
return this.scene.getEnemyField().indexOf(this);
|
||||
}
|
||||
|
||||
getBattlerIndex(): BattlerIndex {
|
||||
return BattlerIndex.ENEMY + this.getFieldIndex();
|
||||
}
|
||||
|
||||
addToParty() {
|
||||
const party = this.scene.getParty();
|
||||
let ret: PlayerPokemon = null;
|
||||
|
@ -1143,6 +1243,7 @@ export class EnemyPokemon extends Pokemon {
|
|||
|
||||
export interface TurnMove {
|
||||
move: Moves;
|
||||
targets?: BattlerIndex[];
|
||||
result: MoveResult;
|
||||
virtual?: boolean;
|
||||
turn?: integer;
|
||||
|
@ -1150,6 +1251,7 @@ export interface TurnMove {
|
|||
|
||||
export interface QueuedMove {
|
||||
move: Moves;
|
||||
targets: BattlerIndex[];
|
||||
ignorePP?: boolean;
|
||||
}
|
||||
|
||||
|
@ -1188,17 +1290,25 @@ export enum AiType {
|
|||
}
|
||||
|
||||
export enum MoveResult {
|
||||
PENDING,
|
||||
SUCCESS,
|
||||
FAIL,
|
||||
MISS,
|
||||
OTHER
|
||||
}
|
||||
|
||||
export enum HitResult {
|
||||
EFFECTIVE = 1,
|
||||
SUPER_EFFECTIVE,
|
||||
NOT_VERY_EFFECTIVE,
|
||||
NO_EFFECT,
|
||||
STATUS,
|
||||
FAILED,
|
||||
MISSED,
|
||||
FAIL,
|
||||
MISS,
|
||||
OTHER
|
||||
}
|
||||
|
||||
export type DamageResult = MoveResult.EFFECTIVE | MoveResult.SUPER_EFFECTIVE | MoveResult.NOT_VERY_EFFECTIVE | MoveResult.OTHER;
|
||||
export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.OTHER;
|
||||
|
||||
export class PokemonMove {
|
||||
public moveId: Moves;
|
||||
|
|
|
@ -24,7 +24,7 @@ export function initAutoPlay() {
|
|||
const commandUiHandler = this.ui.handlers[Mode.COMMAND] as CommandUiHandler;
|
||||
const fightUiHandler = this.ui.handlers[Mode.FIGHT] as FightUiHandler;
|
||||
const partyUiHandler = this.ui.handlers[Mode.PARTY] as PartyUiHandler;
|
||||
const switchCheckUiHandler = this.ui.handlers[Mode.CONFIRM] as ConfirmUiHandler;
|
||||
const confirmUiHandler = this.ui.handlers[Mode.CONFIRM] as ConfirmUiHandler;
|
||||
const modifierSelectUiHandler = this.ui.handlers[Mode.MODIFIER_SELECT] as ModifierSelectUiHandler;
|
||||
|
||||
const getBestPartyMemberIndex = () => {
|
||||
|
@ -153,15 +153,15 @@ export function initAutoPlay() {
|
|||
}
|
||||
};
|
||||
|
||||
const originalSwitchCheckUiHandlerShow = switchCheckUiHandler.show;
|
||||
switchCheckUiHandler.show = function (args: any[]) {
|
||||
const originalSwitchCheckUiHandlerShow = confirmUiHandler.show;
|
||||
confirmUiHandler.show = function (args: any[]) {
|
||||
originalSwitchCheckUiHandlerShow.apply(this, [ args ]);
|
||||
if (thisArg.auto) {
|
||||
const bestPartyMemberIndex = getBestPartyMemberIndex();
|
||||
thisArg.time.delayedCall(20, () => {
|
||||
if (bestPartyMemberIndex)
|
||||
nextPartyMemberIndex = bestPartyMemberIndex;
|
||||
switchCheckUiHandler.setCursor(bestPartyMemberIndex ? 1 : 0);
|
||||
confirmUiHandler.setCursor(bestPartyMemberIndex ? 1 : 0);
|
||||
thisArg.time.delayedCall(20, () => this.processInput(Button.ACTION));
|
||||
});
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ export function initAutoPlay() {
|
|||
|
||||
const party = thisArg.getParty();
|
||||
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 criticalHpPartyMemberIndex = party.findIndex(p => p.getHpRatio() <= 0.25);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ interface SystemSaveData {
|
|||
|
||||
interface SessionSaveData {
|
||||
party: PokemonData[];
|
||||
enemyParty: PokemonData[];
|
||||
enemyField: PokemonData[];
|
||||
modifiers: PersistentModifierData[];
|
||||
enemyModifiers: PersistentModifierData[];
|
||||
arena: ArenaData;
|
||||
|
@ -126,9 +126,9 @@ export class GameData {
|
|||
saveSession(scene: BattleScene): boolean {
|
||||
const sessionData = {
|
||||
party: scene.getParty().map(p => new PokemonData(p)),
|
||||
enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)),
|
||||
modifiers: scene.findModifiers(m => true).map(m => new PersistentModifierData(m, true)),
|
||||
enemyModifiers: scene.findModifiers(m => true, false).map(m => new PersistentModifierData(m, false)),
|
||||
enemyField: scene.getEnemyField().map(p => new PokemonData(p)),
|
||||
modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
|
||||
enemyModifiers: scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)),
|
||||
arena: new ArenaData(scene.arena),
|
||||
pokeballCounts: scene.pokeballCounts,
|
||||
waveIndex: scene.currentBattle.waveIndex,
|
||||
|
@ -153,7 +153,7 @@ export class GameData {
|
|||
|
||||
try {
|
||||
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[] = [];
|
||||
for (let pd of v)
|
||||
ret.push(new PokemonData(pd));
|
||||
|
@ -188,16 +188,19 @@ export class GameData {
|
|||
party.push(pokemon);
|
||||
}
|
||||
|
||||
const enemyPokemon = sessionData.enemyParty[0].toPokemon(scene) as EnemyPokemon;
|
||||
|
||||
Object.keys(scene.pokeballCounts).forEach((key: string) => {
|
||||
scene.pokeballCounts[key] = sessionData.pokeballCounts[key] || 0;
|
||||
});
|
||||
|
||||
scene.newArena(sessionData.arena.biome, true);
|
||||
scene.newBattle(sessionData.waveIndex).enemyPokemon = enemyPokemon;
|
||||
scene.newArena(sessionData.arena.biome, sessionData.enemyField.length > 1);
|
||||
const battle = scene.newBattle(sessionData.waveIndex, sessionData.enemyField.length > 1);
|
||||
|
||||
loadPokemonAssets.push(enemyPokemon.loadAssets());
|
||||
sessionData.enemyField.forEach((enemyData, e) => {
|
||||
const enemyPokemon = enemyData.toPokemon(scene) as EnemyPokemon;
|
||||
battle.enemyField[e] = enemyPokemon;
|
||||
|
||||
loadPokemonAssets.push(enemyPokemon.loadAssets());
|
||||
});
|
||||
|
||||
scene.arena.weather = sessionData.arena.weather;
|
||||
// TODO
|
||||
|
|
|
@ -37,7 +37,7 @@ export default class ModifierData {
|
|||
type.generatorId = this.typeGeneratorId;
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import { TextStyle, addTextObject } from "./text";
|
|||
|
||||
const hiddenX = -91;
|
||||
const shownX = 10;
|
||||
const baseY = -116;
|
||||
|
||||
export default class AbilityBar extends Phaser.GameObjects.Container {
|
||||
private bg: Phaser.GameObjects.Image;
|
||||
|
@ -15,7 +16,7 @@ export default class AbilityBar extends Phaser.GameObjects.Container {
|
|||
public shown: boolean;
|
||||
|
||||
constructor(scene: BattleScene) {
|
||||
super(scene, hiddenX, (-scene.game.canvas.height / 6) + 64);
|
||||
super(scene, hiddenX, baseY);
|
||||
}
|
||||
|
||||
setup(): void {
|
||||
|
@ -43,9 +44,12 @@ export default class AbilityBar extends Phaser.GameObjects.Container {
|
|||
if (this.shown)
|
||||
return;
|
||||
|
||||
(this.scene as BattleScene).fieldUI.bringToTop(this);
|
||||
|
||||
if (this.tween)
|
||||
this.tween.stop();
|
||||
|
||||
this.y = baseY + ((this.scene as BattleScene).currentBattle.double ? 14 : 0);
|
||||
this.tween = this.scene.tweens.add({
|
||||
targets: this,
|
||||
x: shownX,
|
||||
|
|
|
@ -60,12 +60,12 @@ export default class BallUiHandler extends UiHandler {
|
|||
|
||||
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) {
|
||||
success = true;
|
||||
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)) {
|
||||
this.scene.ui.setMode(Mode.COMMAND);
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
|
@ -93,7 +93,7 @@ export default class BallUiHandler extends UiHandler {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -4,10 +4,12 @@ import * as Utils from '../utils';
|
|||
import { addTextObject, TextStyle } from './text';
|
||||
import { getGenderSymbol, getGenderColor } from '../data/gender';
|
||||
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 {
|
||||
private player: boolean;
|
||||
private mini: boolean;
|
||||
private offset: boolean;
|
||||
private lastName: string;
|
||||
private lastStatus: StatusEffect;
|
||||
private lastHp: integer;
|
||||
|
@ -17,9 +19,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
private lastLevelExp: integer;
|
||||
private lastLevel: integer;
|
||||
|
||||
private box: Phaser.GameObjects.Sprite;
|
||||
private nameText: Phaser.GameObjects.Text;
|
||||
private genderText: Phaser.GameObjects.Text;
|
||||
private ownedIcon: Phaser.GameObjects.Image;
|
||||
private ownedIcon: Phaser.GameObjects.Sprite;
|
||||
private statusIndicator: Phaser.GameObjects.Sprite;
|
||||
private levelContainer: Phaser.GameObjects.Container;
|
||||
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) {
|
||||
super(scene, x, y);
|
||||
this.player = player;
|
||||
this.mini = !player;
|
||||
this.offset = false;
|
||||
this.lastName = null;
|
||||
this.lastStatus = StatusEffect.NONE;
|
||||
this.lastHp = -1;
|
||||
|
@ -42,9 +47,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
// Initially invisible and shown via Pokemon.showInfo
|
||||
this.setVisible(false);
|
||||
|
||||
const box = this.scene.add.image(0, 0, `pbinfo_${player ? 'player' : 'enemy'}`);
|
||||
box.setOrigin(1, 0.5);
|
||||
this.add(box);
|
||||
this.box = this.scene.add.sprite(0, 0, this.getTextureName());
|
||||
this.box.setOrigin(1, 0.5);
|
||||
this.add(this.box);
|
||||
|
||||
this.nameText = addTextObject(this.scene, player ? -115 : -124, player ? -15.2 : -11.2, '', TextStyle.BATTLE_INFO);
|
||||
this.nameText.setOrigin(0, 0);
|
||||
|
@ -56,7 +61,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
this.add(this.genderText);
|
||||
|
||||
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.setOrigin(0, 0);
|
||||
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.lastHpFrame = this.hpBar.scaleX > 0.5 ? 'high' : this.hpBar.scaleX > 0.25 ? 'medium' : 'low';
|
||||
this.hpBar.setFrame(this.lastHpFrame);
|
||||
if (this.player)
|
||||
this.setHpNumbers(pokemon.hp, pokemon.getMaxHp());
|
||||
this.lastHp = pokemon.hp;
|
||||
this.lastHpFrame = this.hpBar.scaleX > 0.5 ? 'high' : this.hpBar.scaleX > 0.25 ? 'medium' : 'low';
|
||||
this.lastMaxHp = pokemon.getMaxHp();
|
||||
|
||||
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> {
|
||||
return new Promise(resolve => {
|
||||
if (!this.scene) {
|
||||
|
@ -290,4 +329,6 @@ export class EnemyBattleInfo extends BattleInfo {
|
|||
constructor(scene: Phaser.Scene) {
|
||||
super(scene, 140, -141, false);
|
||||
}
|
||||
|
||||
setMini(mini: boolean): void { } // Always mini
|
||||
}
|
|
@ -43,7 +43,7 @@ export default class CommandUiHandler extends UiHandler {
|
|||
const messageHandler = this.getUi().getMessageHandler();
|
||||
messageHandler.bg.setTexture('bg_command');
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ export default class CommandUiHandler extends UiHandler {
|
|||
success = true;
|
||||
break;
|
||||
case 2:
|
||||
ui.setMode(Mode.PARTY, PartyUiMode.SWITCH);
|
||||
ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex());
|
||||
success = true;
|
||||
break;
|
||||
case 3:
|
||||
|
|
|
@ -14,6 +14,12 @@ export default class EvolutionSceneHandler extends UiHandler {
|
|||
this.scene.fieldUI.add(this.evolutionContainer);
|
||||
}
|
||||
|
||||
show(_args: any[]): void {
|
||||
super.show(_args);
|
||||
|
||||
this.scene.fieldUI.bringToTop(this.evolutionContainer);
|
||||
}
|
||||
|
||||
processInput(button: Button) {
|
||||
this.scene.ui.getMessageHandler().processInput(button);
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ export default class FightUiHandler extends UiHandler {
|
|||
ui.add(this.cursorObj);
|
||||
}
|
||||
|
||||
const moveset = this.scene.getPlayerPokemon().getMoveset();
|
||||
const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset();
|
||||
|
||||
const hasMove = cursor < moveset.length;
|
||||
|
||||
|
@ -114,7 +114,7 @@ export default class FightUiHandler extends UiHandler {
|
|||
}
|
||||
|
||||
displayMoves() {
|
||||
const moveset = this.scene.getPlayerPokemon().getMoveset();
|
||||
const moveset = (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getMoveset();
|
||||
for (let m = 0; m < 4; m++) {
|
||||
const moveText = addTextObject(this.scene, m % 2 === 0 ? 0 : 100, m < 2 ? 0 : 16, '-', TextStyle.WINDOW);
|
||||
if (m < moveset.length)
|
||||
|
|
|
@ -42,7 +42,9 @@ export type PokemonMoveSelectFilter = (pokemonMove: PokemonMove) => string;
|
|||
|
||||
export default class PartyUiHandler extends MessageUiHandler {
|
||||
private partyUiMode: PartyUiMode;
|
||||
private fieldIndex: integer;
|
||||
|
||||
private partyBg: Phaser.GameObjects.Image;
|
||||
private partyContainer: Phaser.GameObjects.Container;
|
||||
private partySlotsContainer: Phaser.GameObjects.Container;
|
||||
private partySlots: PartySlot[];
|
||||
|
@ -67,7 +69,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
private static FilterAll = (_pokemon: PlayerPokemon) => null;
|
||||
|
||||
public static FilterNonFainted = (pokemon: PlayerPokemon) => {
|
||||
if (!pokemon.hp)
|
||||
if (pokemon.isFainted())
|
||||
return `${pokemon.name} has no energy\nleft to battle!`;
|
||||
return null;
|
||||
};
|
||||
|
@ -96,10 +98,10 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
|
||||
this.partyContainer = partyContainer;
|
||||
|
||||
const partyBg = this.scene.add.image(0, 0, 'party_bg');
|
||||
partyContainer.add(partyBg);
|
||||
this.partyBg = this.scene.add.image(0, 0, 'party_bg');
|
||||
partyContainer.add(this.partyBg);
|
||||
|
||||
partyBg.setOrigin(0, 1);
|
||||
this.partyBg.setOrigin(0, 1);
|
||||
|
||||
const partySlotsContainer = this.scene.add.container(0, 0);
|
||||
partyContainer.add(partySlotsContainer);
|
||||
|
@ -143,17 +145,20 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
|
||||
this.partyUiMode = args[0] as PartyUiMode;
|
||||
|
||||
this.fieldIndex = args.length > 1 ? args[1] as integer : -1;
|
||||
|
||||
this.partyContainer.setVisible(true);
|
||||
this.partyBg.setTexture(`party_bg${this.scene.currentBattle.double ? '_double' : ''}`);
|
||||
this.populatePartySlots();
|
||||
this.setCursor(this.cursor < 6 ? this.cursor : 0);
|
||||
|
||||
if (args.length > 1 && args[1] instanceof Function)
|
||||
this.selectCallback = args[1];
|
||||
this.selectFilter = args.length > 2 && args[2] instanceof Function
|
||||
? args[2] as PokemonSelectFilter
|
||||
if (args.length > 2 && args[2] instanceof Function)
|
||||
this.selectCallback = args[2];
|
||||
this.selectFilter = args.length > 3 && args[3] instanceof Function
|
||||
? args[3] as PokemonSelectFilter
|
||||
: PartyUiHandler.FilterAll;
|
||||
this.moveSelectFilter = args.length > 3 && args[3] instanceof Function
|
||||
? args[3] as PokemonMoveSelectFilter
|
||||
this.moveSelectFilter = args.length > 4 && args[4] instanceof Function
|
||||
? args[4] as PokemonMoveSelectFilter
|
||||
: PartyUiHandler.FilterAllMoves;
|
||||
}
|
||||
|
||||
|
@ -226,7 +231,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
} else if (option === PartyOption.RELEASE) {
|
||||
this.clearOptions();
|
||||
ui.playSelect();
|
||||
if (this.cursor) {
|
||||
if (this.cursor >= this.scene.currentBattle.getBattlerCount()) {
|
||||
this.showText(`Do you really want to release ${pokemon.name}?`, null, () => {
|
||||
ui.setModeWithoutClear(Mode.CONFIRM, () => {
|
||||
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);
|
||||
break;
|
||||
case Button.LEFT:
|
||||
if (this.cursor && this.cursor < 6)
|
||||
if (this.cursor >= this.scene.currentBattle.getBattlerCount() && this.cursor < 6)
|
||||
success = this.setCursor(0);
|
||||
break;
|
||||
case Button.RIGHT:
|
||||
if (!this.cursor)
|
||||
success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || 1 : 1);
|
||||
const battlerCount = this.scene.currentBattle.getBattlerCount();
|
||||
if (this.cursor < battlerCount)
|
||||
success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -411,10 +417,11 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
case PartyUiMode.SWITCH:
|
||||
case PartyUiMode.FAINT_SWITCH:
|
||||
case PartyUiMode.POST_BATTLE_SWITCH:
|
||||
if (this.cursor) {
|
||||
if (this.cursor >= this.scene.currentBattle.getBattlerCount()) {
|
||||
this.options.push(PartyOption.SEND_OUT);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
@ -578,7 +585,9 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||
private slotHpOverlay: Phaser.GameObjects.Sprite;
|
||||
|
||||
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.pokemon = pokemon;
|
||||
|
@ -587,14 +596,16 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
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'}`);
|
||||
this.slotBg = 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.add(slotPb);
|
||||
|
@ -609,7 +620,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||
this.add(slotInfoContainer);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
const hpRatio = this.pokemon.getHpRatio();
|
||||
|
@ -669,8 +680,9 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
private updateSlotTexture(): void {
|
||||
this.slotBg.setTexture(`party_slot${this.slotIndex ? '' : '_main'}`,
|
||||
`party_slot${this.slotIndex ? '' : '_main'}${this.transfer ? '_swap' : this.pokemon.hp ? '' : '_fnt'}${this.selected ? '_sel' : ''}`);
|
||||
const battlerCount = (this.scene as BattleScene).currentBattle.getBattlerCount();
|
||||
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' : ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -12,12 +12,14 @@ import SummaryUiHandler from './summary-ui-handler';
|
|||
import StarterSelectUiHandler from './starter-select-ui-handler';
|
||||
import EvolutionSceneHandler from './evolution-scene-handler';
|
||||
import BiomeSelectUiHandler from './biome-select-ui-handler';
|
||||
import TargetSelectUiHandler from './target-select-ui-handler';
|
||||
|
||||
export enum Mode {
|
||||
MESSAGE,
|
||||
COMMAND,
|
||||
FIGHT,
|
||||
BALL,
|
||||
TARGET_SELECT,
|
||||
MODIFIER_SELECT,
|
||||
PARTY,
|
||||
SUMMARY,
|
||||
|
@ -54,6 +56,7 @@ export default class UI extends Phaser.GameObjects.Container {
|
|||
new CommandUiHandler(scene),
|
||||
new FightUiHandler(scene),
|
||||
new BallUiHandler(scene),
|
||||
new TargetSelectUiHandler(scene),
|
||||
new ModifierSelectUiHandler(scene),
|
||||
new PartyUiHandler(scene),
|
||||
new SummaryUiHandler(scene),
|
||||
|
|
|
@ -25,6 +25,8 @@ export function padInt(value: integer, length: integer, padWith?: string): strin
|
|||
export function randInt(range: integer, min?: integer): integer {
|
||||
if (!min)
|
||||
min = 0;
|
||||
if (range === 1)
|
||||
return min;
|
||||
return Math.floor(Math.random() * range) + min;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue