2023-03-28 19:54:52 +01:00
|
|
|
import Phaser from 'phaser';
|
|
|
|
import BattleScene from './battle-scene';
|
2023-04-20 20:46:05 +01:00
|
|
|
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info';
|
2023-05-18 16:11:06 +01:00
|
|
|
import Move, { StatChangeAttr, HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, AttackMove, AddBattlerTagAttr } from "./data/move";
|
2023-04-20 20:46:05 +01:00
|
|
|
import { pokemonLevelMoves } from './data/pokemon-level-moves';
|
|
|
|
import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species';
|
2023-03-28 19:54:52 +01:00
|
|
|
import * as Utils from './utils';
|
2023-04-24 19:30:21 +01:00
|
|
|
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier } from './data/type';
|
2023-04-20 20:46:05 +01:00
|
|
|
import { getLevelTotalExp } from './data/exp';
|
|
|
|
import { Stat } from './data/pokemon-stat';
|
2023-04-24 02:31:06 +01:00
|
|
|
import { AttackTypeBoosterModifier, PokemonBaseStatModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier } from './modifier/modifier';
|
2023-04-20 20:46:05 +01:00
|
|
|
import { PokeballType } from './data/pokeball';
|
|
|
|
import { Gender } from './data/gender';
|
|
|
|
import { initMoveAnim, loadMoveAnimAssets } from './data/battle-anims';
|
|
|
|
import { Status, StatusEffect } from './data/status-effect';
|
|
|
|
import { tmSpecies } from './data/tms';
|
|
|
|
import { pokemonEvolutions, pokemonPrevolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './data/pokemon-evolutions';
|
2023-04-23 21:36:03 +01:00
|
|
|
import { DamagePhase, FaintPhase } from './battle-phases';
|
2023-04-20 20:46:05 +01:00
|
|
|
import { BattleStat } from './data/battle-stat';
|
2023-04-27 19:30:03 +01:00
|
|
|
import { BattlerTag, BattlerTagLapseType, BattlerTagType, TypeBoostTag, getBattlerTag } from './data/battler-tag';
|
2023-04-20 20:46:05 +01:00
|
|
|
import { Species } from './data/species';
|
|
|
|
import { WeatherType } from './data/weather';
|
|
|
|
import { TempBattleStat } from './data/temp-battle-stat';
|
2023-05-18 16:11:06 +01:00
|
|
|
import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag';
|
2023-04-23 23:40:21 +01:00
|
|
|
import { Biome } from './data/biome';
|
2023-05-19 17:35:06 +01:00
|
|
|
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, PreApplyBattlerTagAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
|
2023-04-28 20:03:42 +01:00
|
|
|
import PokemonData from './system/pokemon-data';
|
2023-05-18 16:11:06 +01:00
|
|
|
import { BattlerIndex } from './battle';
|
|
|
|
|
|
|
|
export enum FieldPosition {
|
|
|
|
CENTER,
|
|
|
|
LEFT,
|
|
|
|
RIGHT
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
|
|
|
|
export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|
|
|
public id: integer;
|
|
|
|
public name: string;
|
|
|
|
public species: PokemonSpecies;
|
2023-04-18 03:44:41 +01:00
|
|
|
public formIndex: integer;
|
2023-04-23 06:03:09 +01:00
|
|
|
public abilityIndex: integer;
|
2023-03-28 19:54:52 +01:00
|
|
|
public shiny: boolean;
|
|
|
|
public pokeball: PokeballType;
|
|
|
|
protected battleInfo: BattleInfo;
|
|
|
|
public level: integer;
|
|
|
|
public exp: integer;
|
|
|
|
public levelExp: integer;
|
2023-04-02 01:06:44 +01:00
|
|
|
public gender: Gender;
|
2023-03-28 19:54:52 +01:00
|
|
|
public hp: integer;
|
|
|
|
public stats: integer[];
|
|
|
|
public ivs: integer[];
|
|
|
|
public moveset: PokemonMove[];
|
2023-04-12 00:08:03 +01:00
|
|
|
public status: Status;
|
2023-03-28 19:54:52 +01:00
|
|
|
public winCount: integer;
|
|
|
|
|
2023-04-04 04:38:31 +01:00
|
|
|
public summonData: PokemonSummonData;
|
|
|
|
public battleSummonData: PokemonBattleSummonData;
|
|
|
|
public turnData: PokemonTurnData;
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
public fieldPosition: FieldPosition;
|
|
|
|
|
2023-04-11 04:15:06 +01:00
|
|
|
public maskEnabled: boolean;
|
|
|
|
public maskSprite: Phaser.GameObjects.Sprite;
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
private shinySparkle: Phaser.GameObjects.Sprite;
|
|
|
|
|
2023-04-28 20:03:42 +01:00
|
|
|
constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, dataSource?: Pokemon | PokemonData) {
|
2023-03-28 19:54:52 +01:00
|
|
|
super(scene, x, y);
|
2023-04-26 21:07:29 +01:00
|
|
|
|
|
|
|
if (!species.isObtainable() && this.isPlayer())
|
|
|
|
throw `Cannot create a player Pokemon for species '${species.name}'`;
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
this.name = Utils.toPokemonUpperCase(species.name);
|
|
|
|
this.species = species;
|
|
|
|
this.battleInfo = this.isPlayer()
|
|
|
|
? new PlayerBattleInfo(scene)
|
|
|
|
: new EnemyBattleInfo(scene);
|
|
|
|
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
|
2023-04-18 03:44:41 +01:00
|
|
|
this.level = level;
|
2023-04-23 06:03:09 +01:00
|
|
|
this.abilityIndex = abilityIndex || (species.ability2 ? Utils.randInt(2) : 0);
|
2023-04-18 03:44:41 +01:00
|
|
|
this.formIndex = formIndex || 0;
|
|
|
|
if (gender !== undefined)
|
|
|
|
this.gender = gender;
|
|
|
|
if (shiny !== undefined)
|
|
|
|
this.shiny = shiny;
|
2023-03-28 19:54:52 +01:00
|
|
|
this.exp = dataSource?.exp || getLevelTotalExp(this.level, species.growthRate);
|
|
|
|
this.levelExp = dataSource?.levelExp || 0;
|
|
|
|
if (dataSource) {
|
|
|
|
this.id = dataSource.id;
|
|
|
|
this.hp = dataSource.hp;
|
|
|
|
this.stats = dataSource.stats;
|
|
|
|
this.ivs = dataSource.ivs;
|
|
|
|
this.moveset = dataSource.moveset;
|
2023-04-04 04:38:31 +01:00
|
|
|
this.status = dataSource.status;
|
2023-03-28 19:54:52 +01:00
|
|
|
this.winCount = dataSource.winCount;
|
|
|
|
} else {
|
|
|
|
this.generateAndPopulateMoveset();
|
|
|
|
|
|
|
|
this.id = Utils.randInt(4294967295);
|
|
|
|
this.ivs = [
|
|
|
|
Utils.binToDec(Utils.decToBin(this.id).substring(0, 5)),
|
|
|
|
Utils.binToDec(Utils.decToBin(this.id).substring(5, 10)),
|
|
|
|
Utils.binToDec(Utils.decToBin(this.id).substring(10, 15)),
|
|
|
|
Utils.binToDec(Utils.decToBin(this.id).substring(15, 20)),
|
|
|
|
Utils.binToDec(Utils.decToBin(this.id).substring(20, 25)),
|
|
|
|
Utils.binToDec(Utils.decToBin(this.id).substring(25, 30))
|
|
|
|
];
|
2023-04-18 03:44:41 +01:00
|
|
|
//} else
|
|
|
|
//this.id = parseInt(Utils.decToBin(this.ivs[Stat.HP]) + Utils.decToBin(this.ivs[Stat.ATK]) + Utils.decToBin(this.ivs[Stat.DEF]) + Utils.decToBin(this.ivs[Stat.SPATK]) + Utils.decToBin(this.ivs[Stat.SPDEF]) + Utils.decToBin(this.ivs[Stat.SPD]) + this.id.toString(2).slice(30));
|
2023-03-28 19:54:52 +01:00
|
|
|
|
2023-04-18 03:44:41 +01:00
|
|
|
if (this.gender === undefined) {
|
|
|
|
if (this.getSpeciesForm().malePercent === null)
|
|
|
|
this.gender = Gender.GENDERLESS;
|
|
|
|
else {
|
|
|
|
const genderChance = (this.id % 256) * 0.390625;
|
|
|
|
if (genderChance < this.getSpeciesForm().malePercent)
|
|
|
|
this.gender = Gender.MALE;
|
|
|
|
else
|
|
|
|
this.gender = Gender.FEMALE;
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const rand1 = Utils.binToDec(Utils.decToBin(this.id).substring(0, 16));
|
|
|
|
const rand2 = Utils.binToDec(Utils.decToBin(this.id).substring(16, 32));
|
|
|
|
|
2023-04-18 06:32:26 +01:00
|
|
|
const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId;
|
2023-03-28 19:54:52 +01:00
|
|
|
const F = rand1 ^ rand2;
|
|
|
|
|
2023-04-18 03:44:41 +01:00
|
|
|
if (this.shiny === undefined) {
|
|
|
|
let shinyThreshold = new Utils.IntegerHolder(32);
|
2023-04-23 06:03:09 +01:00
|
|
|
this.scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
2023-04-18 03:44:41 +01:00
|
|
|
console.log(shinyThreshold.value);
|
|
|
|
|
|
|
|
this.shiny = (E ^ F) < shinyThreshold.value;
|
|
|
|
if ((E ^ F) < 32)
|
|
|
|
console.log('REAL SHINY!!');
|
|
|
|
if (this.shiny)
|
|
|
|
console.log((E ^ F), shinyThreshold.value);
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
|
|
|
|
this.winCount = 0;
|
|
|
|
}
|
|
|
|
|
2023-04-26 21:07:29 +01:00
|
|
|
if (!species.isObtainable())
|
|
|
|
this.shiny = false;
|
|
|
|
|
2023-04-14 23:21:33 +01:00
|
|
|
//this.setPipeline(this.scene).spritePipeline);
|
2023-04-04 01:47:41 +01:00
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
this.calculateStats();
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
this.fieldPosition = FieldPosition.CENTER;
|
|
|
|
|
|
|
|
scene.fieldUI.add(this.battleInfo);
|
2023-03-28 19:54:52 +01:00
|
|
|
|
|
|
|
this.battleInfo.initInfo(this);
|
|
|
|
|
|
|
|
const getSprite = () => {
|
|
|
|
const ret = this.scene.add.sprite(0, 0, `pkmn__${this.isPlayer() ? 'back__' : ''}sub`);
|
|
|
|
ret.setOrigin(0.5, 1);
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
|
|
|
const sprite = getSprite();
|
|
|
|
const tintSprite = getSprite();
|
|
|
|
|
|
|
|
tintSprite.setVisible(false);
|
|
|
|
|
|
|
|
this.add(sprite);
|
|
|
|
this.add(tintSprite);
|
|
|
|
|
2023-04-21 05:04:48 +01:00
|
|
|
this.getSpeciesForm().generateIconAnim(scene, this.gender === Gender.FEMALE, formIndex);
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
if (this.shiny) {
|
|
|
|
const shinySparkle = this.scene.add.sprite(0, 0, 'shiny');
|
|
|
|
shinySparkle.setVisible(false);
|
|
|
|
shinySparkle.setOrigin(0.5, 1);
|
|
|
|
const frameNames = this.scene.anims.generateFrameNames('shiny', { suffix: '.png', end: 34 });
|
|
|
|
this.scene.anims.create({
|
|
|
|
key: 'sparkle',
|
|
|
|
frames: frameNames,
|
|
|
|
frameRate: 32,
|
|
|
|
showOnStart: true,
|
|
|
|
hideOnComplete: true,
|
|
|
|
});
|
|
|
|
this.add(shinySparkle);
|
|
|
|
|
|
|
|
this.shinySparkle = shinySparkle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
isFainted(checkStatus?: boolean): boolean {
|
|
|
|
return !this.hp && (!checkStatus || this.status?.effect === StatusEffect.FAINT);
|
|
|
|
}
|
|
|
|
|
|
|
|
isActive(): boolean {
|
|
|
|
return !this.isFainted() && !!this.scene;
|
|
|
|
}
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
abstract isPlayer(): boolean;
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
abstract getFieldIndex(): integer;
|
|
|
|
|
|
|
|
abstract getBattlerIndex(): BattlerIndex;
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
loadAssets(): Promise<void> {
|
|
|
|
return new Promise(resolve => {
|
2023-05-06 05:42:01 +01:00
|
|
|
const moveIds = this.getMoveset().map(m => m.getMove().id);
|
2023-04-12 00:08:03 +01:00
|
|
|
Promise.allSettled(moveIds.map(m => initMoveAnim(m)))
|
2023-04-04 01:47:41 +01:00
|
|
|
.then(() => {
|
2023-04-14 23:21:33 +01:00
|
|
|
loadMoveAnimAssets(this.scene, moveIds);
|
2023-04-18 03:44:41 +01:00
|
|
|
this.getSpeciesForm().loadAssets(this.scene, this.gender === Gender.FEMALE, this.formIndex, this.shiny);
|
2023-04-07 18:23:55 +01:00
|
|
|
if (this.isPlayer())
|
2023-04-14 23:21:33 +01:00
|
|
|
this.scene.loadAtlas(this.getBattleSpriteKey(), 'pokemon', this.getBattleSpriteAtlasPath());
|
2023-04-04 01:47:41 +01:00
|
|
|
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
2023-04-07 18:23:55 +01:00
|
|
|
if (this.isPlayer()) {
|
2023-04-13 00:09:15 +01:00
|
|
|
const originalWarn = console.warn;
|
|
|
|
// Ignore warnings for missing frames, because there will be a lot
|
|
|
|
console.warn = () => {};
|
|
|
|
const battleFrameNames = this.scene.anims.generateFrameNames(this.getBattleSpriteKey(), { zeroPad: 4, suffix: ".png", start: 1, end: 256 });
|
|
|
|
console.warn = originalWarn;
|
|
|
|
if (this.isPlayer()) {
|
|
|
|
this.scene.anims.create({
|
|
|
|
key: this.getBattleSpriteKey(),
|
|
|
|
frames: battleFrameNames,
|
|
|
|
frameRate: 12,
|
|
|
|
repeat: -1
|
|
|
|
});
|
|
|
|
}
|
2023-04-07 18:23:55 +01:00
|
|
|
}
|
2023-04-05 02:10:11 +01:00
|
|
|
this.playAnim();
|
2023-04-04 01:47:41 +01:00
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
if (!this.scene.load.isLoading())
|
|
|
|
this.scene.load.start();
|
2023-03-29 05:31:25 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-04-07 18:23:55 +01:00
|
|
|
getSpriteAtlasPath(): string {
|
2023-03-28 19:54:52 +01:00
|
|
|
return this.getSpriteId().replace(/\_{2}/g, '/');
|
|
|
|
}
|
|
|
|
|
2023-04-07 18:23:55 +01:00
|
|
|
getBattleSpriteAtlasPath(): string {
|
|
|
|
return this.getBattleSpriteId().replace(/\_{2}/g, '/');
|
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
getSpriteId(): string {
|
2023-04-18 03:44:41 +01:00
|
|
|
return this.getSpeciesForm().getSpriteId(this.gender === Gender.FEMALE, this.formIndex, this.shiny);
|
2023-04-07 18:23:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
getBattleSpriteId(): string {
|
|
|
|
return `${this.isPlayer() ? 'back__' : ''}${this.getSpriteId()}`;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
getSpriteKey(): string {
|
2023-04-18 03:44:41 +01:00
|
|
|
return this.getSpeciesForm().getSpriteKey(this.gender === Gender.FEMALE, this.formIndex, this.shiny);
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-04-07 18:23:55 +01:00
|
|
|
getBattleSpriteKey(): string {
|
|
|
|
return `pkmn__${this.getBattleSpriteId()}`;
|
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
getIconId(): string {
|
2023-04-18 03:44:41 +01:00
|
|
|
return this.getSpeciesForm().getIconId(this.gender === Gender.FEMALE, this.formIndex);
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
getIconKey(): string {
|
2023-03-28 19:54:52 +01:00
|
|
|
return `pkmn_icon__${this.getIconId()}`;
|
|
|
|
}
|
|
|
|
|
2023-04-18 03:44:41 +01:00
|
|
|
getSpeciesForm(): PokemonSpeciesForm {
|
|
|
|
if (!this.species.forms?.length)
|
|
|
|
return this.species;
|
|
|
|
return this.species.forms[this.formIndex];
|
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
getSprite(): Phaser.GameObjects.Sprite {
|
2023-03-28 19:54:52 +01:00
|
|
|
return this.getAt(0) as Phaser.GameObjects.Sprite;
|
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
getTintSprite(): Phaser.GameObjects.Sprite {
|
2023-04-11 04:15:06 +01:00
|
|
|
return !this.maskEnabled
|
|
|
|
? this.getAt(1) as Phaser.GameObjects.Sprite
|
|
|
|
: this.maskSprite;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
playAnim(): void {
|
2023-04-07 18:23:55 +01:00
|
|
|
this.getSprite().play(this.getBattleSpriteKey());
|
|
|
|
this.getTintSprite().play(this.getBattleSpriteKey());
|
2023-04-05 02:10:11 +01:00
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-04-17 05:46:50 +01:00
|
|
|
getBattleStat(stat: Stat): integer {
|
2023-04-11 04:15:06 +01:00
|
|
|
if (stat === Stat.HP)
|
|
|
|
return this.stats[Stat.HP];
|
2023-04-19 03:09:37 +01:00
|
|
|
const battleStat = (stat - 1) as BattleStat;
|
|
|
|
const statLevel = new Utils.IntegerHolder(this.summonData.battleStats[battleStat]);
|
|
|
|
if (this.isPlayer())
|
2023-04-21 00:44:56 +01:00
|
|
|
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), battleStat as integer as TempBattleStat, statLevel);
|
2023-05-02 20:56:41 +01:00
|
|
|
const statValue = new Utils.NumberHolder(this.stats[stat]);
|
|
|
|
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, battleStat, statValue);
|
|
|
|
let ret = statValue.value * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value));
|
2023-04-17 05:46:50 +01:00
|
|
|
if (stat === Stat.SPDEF && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM)
|
|
|
|
ret *= 1.5;
|
2023-04-21 05:10:45 +01:00
|
|
|
if (stat === Stat.SPD && this.status && this.status.effect === StatusEffect.PARALYSIS)
|
2023-04-12 00:08:03 +01:00
|
|
|
ret >>= 2;
|
|
|
|
return ret;
|
2023-04-11 04:15:06 +01:00
|
|
|
}
|
|
|
|
|
2023-04-10 12:59:00 +01:00
|
|
|
calculateStats(): void {
|
2023-03-28 19:54:52 +01:00
|
|
|
if (!this.stats)
|
|
|
|
this.stats = [ 0, 0, 0, 0, 0, 0 ];
|
2023-04-18 03:44:41 +01:00
|
|
|
const baseStats = this.getSpeciesForm().baseStats.slice(0);
|
2023-04-21 00:44:56 +01:00
|
|
|
this.scene.applyModifiers(PokemonBaseStatModifier, this.isPlayer(), this, baseStats);
|
2023-03-28 19:54:52 +01:00
|
|
|
const stats = Utils.getEnumValues(Stat);
|
|
|
|
for (let s of stats) {
|
|
|
|
const isHp = s === Stat.HP;
|
|
|
|
let baseStat = baseStats[s];
|
|
|
|
let value = Math.floor(((2 * baseStat + this.ivs[s] + (0 / 4)) * this.level) * 0.01);
|
|
|
|
if (isHp) {
|
|
|
|
value = Math.min(value + this.level + 10, 99999);
|
|
|
|
if (this.hp > value || this.hp === undefined)
|
|
|
|
this.hp = value;
|
2023-04-25 03:32:12 +01:00
|
|
|
else if (this.hp) {
|
2023-03-28 19:54:52 +01:00
|
|
|
const lastMaxHp = this.getMaxHp();
|
|
|
|
if (lastMaxHp && value > lastMaxHp)
|
|
|
|
this.hp += value - lastMaxHp;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
value = Math.min(value + 5, 99999);
|
|
|
|
this.stats[s] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-10 12:59:00 +01:00
|
|
|
getMaxHp(): integer {
|
2023-03-28 19:54:52 +01:00
|
|
|
return this.stats[Stat.HP];
|
|
|
|
}
|
|
|
|
|
2023-04-11 16:04:39 +01:00
|
|
|
getInverseHp(): integer {
|
|
|
|
return this.getMaxHp() - this.hp;
|
|
|
|
}
|
|
|
|
|
2023-04-10 12:59:00 +01:00
|
|
|
getHpRatio(): number {
|
2023-03-29 17:23:52 +01:00
|
|
|
return Math.floor((this.hp / this.getMaxHp()) * 100) / 100;
|
|
|
|
}
|
|
|
|
|
2023-05-06 05:42:01 +01:00
|
|
|
getMoveset(): PokemonMove[] {
|
|
|
|
if (this.summonData?.moveset)
|
|
|
|
return this.summonData.moveset;
|
|
|
|
return this.moveset;
|
|
|
|
}
|
|
|
|
|
2023-04-16 05:29:55 +01:00
|
|
|
getTypes(): Type[] {
|
2023-04-22 00:30:04 +01:00
|
|
|
const types = [];
|
|
|
|
|
|
|
|
if (this.summonData.types)
|
|
|
|
this.summonData.types.forEach(t => types.push(t));
|
|
|
|
else {
|
|
|
|
const speciesForm = this.getSpeciesForm();
|
|
|
|
|
|
|
|
types.push(speciesForm.type1);
|
|
|
|
if (speciesForm.type2 !== null)
|
|
|
|
types.push(speciesForm.type1);
|
|
|
|
}
|
2023-04-16 05:29:55 +01:00
|
|
|
|
2023-05-08 23:48:35 +01:00
|
|
|
if (this.getTag(BattlerTagType.IGNORE_FLYING) || this.scene.arena.getTag(ArenaTagType.GRAVITY)) {
|
2023-04-22 00:30:04 +01:00
|
|
|
const flyingIndex = types.indexOf(Type.FLYING);
|
2023-04-16 05:29:55 +01:00
|
|
|
if (flyingIndex > -1)
|
2023-04-22 00:30:04 +01:00
|
|
|
types.splice(flyingIndex, 1);
|
2023-04-16 05:29:55 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
if (!types.length)
|
|
|
|
types.push(Type.NORMAL);
|
2023-04-16 05:29:55 +01:00
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
return types;
|
2023-04-16 05:29:55 +01:00
|
|
|
}
|
|
|
|
|
2023-04-23 03:14:53 +01:00
|
|
|
isOfType(type: Type) {
|
|
|
|
return this.getTypes().indexOf(type) > -1;
|
|
|
|
}
|
|
|
|
|
2023-04-27 04:33:13 +01:00
|
|
|
getAbility(): Ability {
|
|
|
|
return abilities[this.species.getAbility(this.abilityIndex)];
|
|
|
|
}
|
|
|
|
|
2023-04-27 19:30:03 +01:00
|
|
|
canApplyAbility(): boolean {
|
2023-05-18 16:11:06 +01:00
|
|
|
return this.hp && !this.getAbility().conditions.find(condition => !condition(this));
|
2023-04-27 19:30:03 +01:00
|
|
|
}
|
|
|
|
|
2023-04-24 19:30:21 +01:00
|
|
|
getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier {
|
|
|
|
const types = this.getTypes();
|
|
|
|
return getTypeDamageMultiplier(moveType, types[0]) * (types.length ? getTypeDamageMultiplier(moveType, types[1]) : 1) as TypeDamageMultiplier;
|
|
|
|
}
|
|
|
|
|
2023-04-10 12:59:00 +01:00
|
|
|
getEvolution(): SpeciesEvolution {
|
|
|
|
if (!pokemonEvolutions.hasOwnProperty(this.species.speciesId))
|
|
|
|
return null;
|
|
|
|
|
|
|
|
const evolutions = pokemonEvolutions[this.species.speciesId];
|
|
|
|
for (let e of evolutions) {
|
2023-04-16 03:51:33 +01:00
|
|
|
if (!e.item && this.level >= e.level) {
|
|
|
|
if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this))
|
2023-04-10 12:59:00 +01:00
|
|
|
return e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-04-10 18:54:06 +01:00
|
|
|
getLevelMoves(startingLevel?: integer): Moves[] {
|
|
|
|
const ret: Moves[] = [];
|
|
|
|
const levelMoves = pokemonLevelMoves[this.species.speciesId];
|
|
|
|
if (levelMoves) {
|
|
|
|
if (!startingLevel)
|
|
|
|
startingLevel = this.level;
|
|
|
|
for (let lm of levelMoves) {
|
|
|
|
const level = lm[0];
|
|
|
|
if (level < startingLevel)
|
|
|
|
continue;
|
|
|
|
else if (level > this.level)
|
|
|
|
break;
|
|
|
|
ret.push(lm[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2023-04-10 12:59:00 +01:00
|
|
|
}
|
2023-05-06 05:42:01 +01:00
|
|
|
|
|
|
|
setMove(moveIndex: integer, moveId: Moves): void {
|
|
|
|
const move = moveId ? new PokemonMove(moveId) : null;
|
2023-05-06 22:31:45 +01:00
|
|
|
this.moveset[moveIndex] = move;
|
2023-05-18 16:11:06 +01:00
|
|
|
if (this.summonData?.moveset)
|
2023-05-06 05:42:01 +01:00
|
|
|
this.summonData.moveset[moveIndex] = move;
|
|
|
|
}
|
2023-04-10 12:59:00 +01:00
|
|
|
|
|
|
|
generateAndPopulateMoveset(): void {
|
2023-03-28 19:54:52 +01:00
|
|
|
this.moveset = [];
|
|
|
|
const movePool = [];
|
|
|
|
const allLevelMoves = pokemonLevelMoves[this.species.speciesId];
|
|
|
|
if (!allLevelMoves) {
|
|
|
|
console.log(this.species.speciesId, 'ERROR')
|
|
|
|
return;
|
|
|
|
}
|
2023-04-29 06:40:24 +01:00
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
for (let m = 0; m < allLevelMoves.length; m++) {
|
|
|
|
const levelMove = allLevelMoves[m];
|
|
|
|
if (this.level < levelMove[0])
|
|
|
|
break;
|
|
|
|
if (movePool.indexOf(levelMove[1]) === -1)
|
|
|
|
movePool.push(levelMove[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
const attackMovePool = movePool.filter(m => {
|
2023-04-21 02:32:48 +01:00
|
|
|
const move = allMoves[m];
|
2023-03-28 19:54:52 +01:00
|
|
|
return move.category !== MoveCategory.STATUS;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (attackMovePool.length) {
|
|
|
|
const moveIndex = Utils.randInt(attackMovePool.length);
|
|
|
|
this.moveset.push(new PokemonMove(attackMovePool[moveIndex], 0, 0));
|
2023-04-21 02:32:48 +01:00
|
|
|
console.log(allMoves[attackMovePool[moveIndex]]);
|
2023-03-28 19:54:52 +01:00
|
|
|
movePool.splice(movePool.findIndex(m => m === attackMovePool[moveIndex]), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (movePool.length && this.moveset.length < 4) {
|
|
|
|
const moveIndex = Utils.randInt(movePool.length);
|
|
|
|
this.moveset.push(new PokemonMove(movePool[moveIndex], 0, 0));
|
2023-04-21 02:32:48 +01:00
|
|
|
console.log(allMoves[movePool[moveIndex]]);
|
2023-03-28 19:54:52 +01:00
|
|
|
movePool.splice(moveIndex, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-01 04:58:16 +01:00
|
|
|
trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean {
|
2023-05-06 05:42:01 +01:00
|
|
|
const move = this.getMoveset().length > moveIndex
|
|
|
|
? this.getMoveset()[moveIndex]
|
2023-03-28 19:54:52 +01:00
|
|
|
: null;
|
2023-05-01 04:58:16 +01:00
|
|
|
return move?.isUsable(ignorePp);
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
showInfo() {
|
|
|
|
if (!this.battleInfo.visible) {
|
|
|
|
this.battleInfo.setX(this.battleInfo.x + (this.isPlayer() ? 150 : -150));
|
|
|
|
this.battleInfo.setVisible(true);
|
|
|
|
this.scene.tweens.add({
|
|
|
|
targets: this.battleInfo,
|
|
|
|
x: this.isPlayer() ? '-=150' : '+=150',
|
|
|
|
duration: 1000,
|
|
|
|
ease: 'Sine.easeOut'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-18 18:36:11 +01:00
|
|
|
hideInfo(): Promise<void> {
|
|
|
|
return new Promise(resolve => {
|
|
|
|
if (this.battleInfo.visible) {
|
|
|
|
this.scene.tweens.add({
|
|
|
|
targets: this.battleInfo,
|
|
|
|
x: this.isPlayer() ? '+=150' : '-=150',
|
|
|
|
duration: 500,
|
|
|
|
ease: 'Sine.easeIn',
|
|
|
|
onComplete: () => {
|
|
|
|
this.battleInfo.setVisible(false);
|
|
|
|
this.battleInfo.setX(this.battleInfo.x - (this.isPlayer() ? 150 : -150));
|
|
|
|
resolve();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
resolve();
|
|
|
|
});
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-04-10 12:59:00 +01:00
|
|
|
updateInfo(instant?: boolean): Promise<void> {
|
|
|
|
return this.battleInfo.updateInfo(this, instant);
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
addExp(exp: integer) {
|
|
|
|
this.exp += exp;
|
2023-04-18 03:44:41 +01:00
|
|
|
while (this.exp >= getLevelTotalExp(this.level + 1, this.getSpeciesForm().growthRate))
|
2023-03-28 19:54:52 +01:00
|
|
|
this.level++;
|
2023-04-18 03:44:41 +01:00
|
|
|
this.levelExp = this.exp - getLevelTotalExp(this.level, this.getSpeciesForm().growthRate);
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
getOpponent(targetIndex: integer): Pokemon {
|
|
|
|
const ret = this.getOpponents()[targetIndex];
|
2023-05-04 17:57:55 +01:00
|
|
|
if (ret.summonData)
|
|
|
|
return ret;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
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;
|
2023-04-15 06:32:16 +01:00
|
|
|
const move = battlerMove.getMove();
|
|
|
|
const moveCategory = move.category;
|
|
|
|
let damage = 0;
|
|
|
|
switch (moveCategory) {
|
|
|
|
case MoveCategory.PHYSICAL:
|
|
|
|
case MoveCategory.SPECIAL:
|
|
|
|
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
|
2023-05-02 03:07:00 +01:00
|
|
|
const typeless = !!move.getAttrs(TypelessAttr).length
|
2023-04-27 04:33:13 +01:00
|
|
|
const cancelled = new Utils.BooleanHolder(false);
|
2023-04-15 06:32:16 +01:00
|
|
|
const power = new Utils.NumberHolder(move.power);
|
2023-04-27 19:30:03 +01:00
|
|
|
const typeMultiplier = new Utils.NumberHolder(!typeless
|
|
|
|
? getTypeDamageMultiplier(move.type, this.getSpeciesForm().type1) * (this.getSpeciesForm().type2 !== null ? getTypeDamageMultiplier(move.type, this.getSpeciesForm().type2) : 1)
|
|
|
|
: 1);
|
|
|
|
if (typeless)
|
|
|
|
typeMultiplier.value = 1;
|
2023-04-27 19:56:30 +01:00
|
|
|
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
|
|
|
|
|
|
|
|
if (!typeless)
|
|
|
|
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
|
2023-04-15 22:40:18 +01:00
|
|
|
|
2023-04-27 04:33:13 +01:00
|
|
|
if (cancelled.value)
|
2023-05-18 16:11:06 +01:00
|
|
|
result = HitResult.NO_EFFECT;
|
2023-04-27 04:33:13 +01:00
|
|
|
else {
|
2023-04-27 19:30:03 +01:00
|
|
|
if (source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === move.type))
|
|
|
|
power.value *= 1.5;
|
|
|
|
const weatherTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(move.type);
|
2023-04-27 04:33:13 +01:00
|
|
|
applyMoveAttrs(VariablePowerAttr, source, this, move, power);
|
2023-04-27 19:30:03 +01:00
|
|
|
if (!typeless) {
|
|
|
|
this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power);
|
|
|
|
this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, power);
|
|
|
|
}
|
2023-05-05 23:20:55 +01:00
|
|
|
let isCritical: boolean;
|
|
|
|
const critOnly = new Utils.BooleanHolder(false);
|
|
|
|
applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly);
|
|
|
|
if (critOnly.value)
|
|
|
|
isCritical = true;
|
|
|
|
else {
|
|
|
|
const critLevel = new Utils.IntegerHolder(0);
|
|
|
|
applyMoveAttrs(HighCritAttr, source, this, move, critLevel);
|
|
|
|
this.scene.applyModifiers(TempBattleStatBoosterModifier, source.isPlayer(), TempBattleStat.CRIT, critLevel);
|
|
|
|
if (source.getTag(BattlerTagType.CRIT_BOOST))
|
|
|
|
critLevel.value += 2;
|
|
|
|
const critChance = Math.ceil(16 / Math.pow(2, critLevel.value));
|
|
|
|
isCritical = !source.getTag(BattlerTagType.NO_CRIT) && !(this.getAbility().hasAttr(BlockCritAbAttr)) && (critChance === 1 || !Utils.randInt(critChance));
|
|
|
|
}
|
2023-04-27 04:33:13 +01:00
|
|
|
const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK);
|
|
|
|
const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF);
|
|
|
|
const stabMultiplier = source.species.type1 === move.type || (source.species.type2 !== null && source.species.type2 === move.type) ? 1.5 : 1;
|
|
|
|
const criticalMultiplier = isCritical ? 2 : 1;
|
|
|
|
damage = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier * typeMultiplier.value * weatherTypeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier;
|
|
|
|
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN)
|
|
|
|
damage = Math.floor(damage / 2);
|
|
|
|
move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
|
|
|
|
if (this.getTag(hta.tagType))
|
|
|
|
damage *= 2;
|
|
|
|
});
|
2023-04-13 17:16:36 +01:00
|
|
|
|
2023-04-27 04:33:13 +01:00
|
|
|
const fixedDamage = new Utils.IntegerHolder(0);
|
|
|
|
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
|
|
|
|
if (damage && fixedDamage.value) {
|
|
|
|
damage = fixedDamage.value;
|
|
|
|
isCritical = false;
|
2023-05-18 16:11:06 +01:00
|
|
|
result = HitResult.EFFECTIVE;
|
2023-04-27 04:33:13 +01:00
|
|
|
}
|
2023-04-15 06:32:16 +01:00
|
|
|
|
2023-04-27 04:33:13 +01:00
|
|
|
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
if (typeMultiplier.value >= 2)
|
2023-05-18 16:11:06 +01:00
|
|
|
result = HitResult.SUPER_EFFECTIVE;
|
2023-04-27 04:33:13 +01:00
|
|
|
else if (typeMultiplier.value >= 1)
|
2023-05-18 16:11:06 +01:00
|
|
|
result = HitResult.EFFECTIVE;
|
2023-04-27 04:33:13 +01:00
|
|
|
else if (typeMultiplier.value > 0)
|
2023-05-18 16:11:06 +01:00
|
|
|
result = HitResult.NOT_VERY_EFFECTIVE;
|
2023-04-27 04:33:13 +01:00
|
|
|
else
|
2023-05-18 16:11:06 +01:00
|
|
|
result = HitResult.NO_EFFECT;
|
2023-04-27 04:33:13 +01:00
|
|
|
}
|
2023-04-15 06:32:16 +01:00
|
|
|
|
2023-04-27 04:33:13 +01:00
|
|
|
if (damage) {
|
2023-05-18 16:11:06 +01:00
|
|
|
this.scene.unshiftPhase(new DamagePhase(this.scene, this.getBattlerIndex(), result as DamageResult));
|
2023-04-27 04:33:13 +01:00
|
|
|
if (isCritical)
|
|
|
|
this.scene.queueMessage('A critical hit!');
|
2023-05-18 16:11:06 +01:00
|
|
|
this.scene.setPhaseQueueSplice();
|
2023-04-27 04:33:13 +01:00
|
|
|
this.damage(damage);
|
|
|
|
source.turnData.damageDealt += damage;
|
2023-05-06 05:42:01 +01:00
|
|
|
this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage, sourceId: source.id });
|
2023-04-27 04:33:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (result) {
|
2023-05-18 16:11:06 +01:00
|
|
|
case HitResult.SUPER_EFFECTIVE:
|
2023-04-27 04:33:13 +01:00
|
|
|
this.scene.queueMessage('It\'s super effective!');
|
|
|
|
break;
|
2023-05-18 16:11:06 +01:00
|
|
|
case HitResult.NOT_VERY_EFFECTIVE:
|
2023-04-27 04:33:13 +01:00
|
|
|
this.scene.queueMessage('It\'s not very effective!');
|
|
|
|
break;
|
2023-05-18 16:11:06 +01:00
|
|
|
case HitResult.NO_EFFECT:
|
2023-04-27 04:33:13 +01:00
|
|
|
this.scene.queueMessage(`It doesn\'t affect ${this.name}!`);
|
|
|
|
break;
|
|
|
|
}
|
2023-05-18 16:11:06 +01:00
|
|
|
|
|
|
|
if (damage)
|
|
|
|
this.scene.clearPhaseQueueSplice();
|
2023-04-15 06:32:16 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case MoveCategory.STATUS:
|
2023-05-18 16:11:06 +01:00
|
|
|
result = HitResult.STATUS;
|
2023-04-15 06:32:16 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2023-04-13 17:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-04-24 02:31:06 +01:00
|
|
|
damage(damage: integer, preventEndure?: boolean): void {
|
2023-05-18 16:11:06 +01:00
|
|
|
if (this.isFainted())
|
2023-04-16 23:40:32 +01:00
|
|
|
return;
|
|
|
|
|
2023-04-24 02:31:06 +01:00
|
|
|
if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) {
|
|
|
|
const surviveDamage = new Utils.BooleanHolder(false);
|
|
|
|
this.scene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage);
|
|
|
|
if (surviveDamage.value)
|
|
|
|
damage = this.hp - 1;
|
|
|
|
}
|
|
|
|
|
2023-04-16 23:40:32 +01:00
|
|
|
this.hp = Math.max(this.hp - damage, 0);
|
2023-05-18 16:11:06 +01:00
|
|
|
if (this.isFainted()) {
|
|
|
|
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex()));
|
2023-04-21 02:32:48 +01:00
|
|
|
this.resetSummonData();
|
2023-04-16 23:40:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 19:30:21 +01:00
|
|
|
addTag(tagType: BattlerTagType, turnCount?: integer, sourceMove?: Moves, sourceId?: integer): boolean {
|
2023-04-15 06:32:16 +01:00
|
|
|
const existingTag = this.getTag(tagType);
|
|
|
|
if (existingTag) {
|
|
|
|
existingTag.onOverlap(this);
|
2023-04-13 17:16:36 +01:00
|
|
|
return false;
|
2023-04-15 06:32:16 +01:00
|
|
|
}
|
2023-04-13 17:16:36 +01:00
|
|
|
|
2023-04-24 19:30:21 +01:00
|
|
|
const newTag = getBattlerTag(tagType, turnCount || 0, sourceMove, sourceId);
|
2023-04-22 00:30:04 +01:00
|
|
|
|
2023-05-04 17:57:55 +01:00
|
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
|
|
applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled);
|
|
|
|
|
|
|
|
if (!cancelled.value && newTag.canAdd(this)) {
|
2023-04-23 03:14:53 +01:00
|
|
|
this.summonData.tags.push(newTag);
|
|
|
|
newTag.onAdd(this);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2023-04-13 17:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
getTag(tagType: BattlerTagType | { new(...args: any[]): BattlerTag }): BattlerTag {
|
2023-04-15 06:32:16 +01:00
|
|
|
return typeof(tagType) === 'number'
|
|
|
|
? this.summonData.tags.find(t => t.tagType === tagType)
|
|
|
|
: this.summonData.tags.find(t => t instanceof tagType);
|
2023-04-13 17:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
findTag(tagFilter: ((tag: BattlerTag) => boolean)) {
|
2023-04-15 06:32:16 +01:00
|
|
|
return this.summonData.tags.find(t => tagFilter(t));
|
2023-04-13 17:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
getTags(tagType: BattlerTagType | { new(...args: any[]): BattlerTag }): BattlerTag[] {
|
2023-04-15 06:32:16 +01:00
|
|
|
return typeof(tagType) === 'number'
|
|
|
|
? this.summonData.tags.filter(t => t.tagType === tagType)
|
|
|
|
: this.summonData.tags.filter(t => t instanceof tagType);
|
|
|
|
}
|
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
findTags(tagFilter: ((tag: BattlerTag) => boolean)) {
|
2023-04-15 06:32:16 +01:00
|
|
|
return this.summonData.tags.filter(t => tagFilter(t));
|
|
|
|
}
|
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
lapseTag(tagType: BattlerTagType): boolean {
|
2023-04-13 17:16:36 +01:00
|
|
|
const tags = this.summonData.tags;
|
2023-04-15 06:32:16 +01:00
|
|
|
const tag = tags.find(t => t.tagType === tagType);
|
2023-04-22 00:30:04 +01:00
|
|
|
if (tag && !(tag.lapse(this, BattlerTagLapseType.CUSTOM))) {
|
2023-04-15 06:32:16 +01:00
|
|
|
tag.onRemove(this);
|
|
|
|
tags.splice(tags.indexOf(tag), 1);
|
|
|
|
}
|
2023-04-18 17:30:47 +01:00
|
|
|
return !!tag;
|
2023-04-15 06:32:16 +01:00
|
|
|
}
|
|
|
|
|
2023-04-22 00:30:04 +01:00
|
|
|
lapseTags(lapseType: BattlerTagLapseType): void {
|
2023-04-15 06:32:16 +01:00
|
|
|
const tags = this.summonData.tags;
|
2023-04-22 00:30:04 +01:00
|
|
|
tags.filter(t => lapseType === BattlerTagLapseType.FAINT || ((t.lapseType === lapseType) && !(t.lapse(this, lapseType))) || (lapseType === BattlerTagLapseType.TURN_END && t.turnCount < 1)).forEach(t => {
|
2023-04-15 06:32:16 +01:00
|
|
|
t.onRemove(this);
|
|
|
|
tags.splice(tags.indexOf(t), 1);
|
|
|
|
});
|
2023-04-13 17:16:36 +01:00
|
|
|
}
|
|
|
|
|
2023-04-23 03:14:53 +01:00
|
|
|
removeTagsBySourceId(sourceId: integer): void {
|
|
|
|
const tags = this.summonData.tags;
|
|
|
|
tags.filter(t => t.sourceId === sourceId).forEach(t => {
|
|
|
|
t.onRemove(this);
|
|
|
|
tags.splice(tags.indexOf(t), 1);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-04-29 00:26:41 +01:00
|
|
|
transferTagsBySourceId(sourceId: integer, newSourceId: integer): void {
|
|
|
|
const tags = this.summonData.tags;
|
|
|
|
tags.filter(t => t.sourceId === sourceId).forEach(t => t.sourceId = newSourceId);
|
|
|
|
}
|
|
|
|
|
|
|
|
transferSummon(source: Pokemon): void {
|
|
|
|
const battleStats = Utils.getEnumValues(BattleStat);
|
|
|
|
for (let stat of battleStats)
|
|
|
|
this.summonData.battleStats[stat] = source.summonData.battleStats[stat];
|
|
|
|
for (let tag of source.summonData.tags)
|
|
|
|
this.summonData.tags.push(tag);
|
|
|
|
}
|
|
|
|
|
2023-04-21 02:32:48 +01:00
|
|
|
getMoveHistory(): TurnMove[] {
|
2023-04-28 20:03:42 +01:00
|
|
|
return this.battleSummonData.moveHistory;
|
2023-04-21 02:32:48 +01:00
|
|
|
}
|
|
|
|
|
2023-04-25 06:32:48 +01:00
|
|
|
pushMoveHistory(turnMove: TurnMove) {
|
|
|
|
turnMove.turn = this.scene.currentBattle?.turn;
|
|
|
|
this.getMoveHistory().push(turnMove);
|
|
|
|
}
|
|
|
|
|
2023-04-13 17:16:36 +01:00
|
|
|
getLastXMoves(turnCount?: integer): TurnMove[] {
|
2023-04-21 02:32:48 +01:00
|
|
|
const moveHistory = this.getMoveHistory();
|
2023-04-18 17:30:47 +01:00
|
|
|
return moveHistory.slice(turnCount >= 0 ? Math.max(moveHistory.length - (turnCount || 1), 0) : 0, moveHistory.length).reverse();
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-04-21 02:32:48 +01:00
|
|
|
getMoveQueue(): QueuedMove[] {
|
|
|
|
return this.summonData.moveQueue;
|
|
|
|
}
|
|
|
|
|
2023-04-12 00:08:03 +01:00
|
|
|
cry(soundConfig?: Phaser.Types.Sound.SoundConfig): integer {
|
2023-04-18 03:44:41 +01:00
|
|
|
return this.getSpeciesForm().cry(this.scene, soundConfig);
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
faintCry(callback: Function) {
|
2023-03-28 19:54:52 +01:00
|
|
|
const key = this.species.speciesId.toString();
|
|
|
|
let i = 0;
|
|
|
|
let rate = 0.85;
|
|
|
|
this.scene.sound.play(key, {
|
|
|
|
rate: rate
|
|
|
|
});
|
|
|
|
const sprite = this.getSprite();
|
2023-04-04 01:47:41 +01:00
|
|
|
const tintSprite = this.getTintSprite();
|
2023-03-28 19:54:52 +01:00
|
|
|
const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25);
|
|
|
|
let frameProgress = 0;
|
|
|
|
let frameThreshold: number;
|
|
|
|
sprite.anims.pause();
|
2023-04-04 01:47:41 +01:00
|
|
|
tintSprite.anims.pause();
|
2023-03-28 19:54:52 +01:00
|
|
|
let faintCryTimer = this.scene.time.addEvent({
|
|
|
|
delay: delay,
|
|
|
|
repeat: -1,
|
|
|
|
callback: () => {
|
|
|
|
++i;
|
|
|
|
frameThreshold = sprite.anims.msPerFrame / rate;
|
|
|
|
frameProgress += delay;
|
|
|
|
while (frameProgress > frameThreshold) {
|
2023-04-04 01:47:41 +01:00
|
|
|
if (sprite.anims.duration) {
|
2023-03-28 19:54:52 +01:00
|
|
|
sprite.anims.nextFrame();
|
2023-04-04 01:47:41 +01:00
|
|
|
tintSprite.anims.nextFrame();
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
frameProgress -= frameThreshold;
|
|
|
|
}
|
|
|
|
const crySound = this.scene.sound.get(key);
|
|
|
|
if (crySound) {
|
|
|
|
rate *= 0.99;
|
|
|
|
crySound.play({
|
|
|
|
rate: rate,
|
|
|
|
seek: (i * delay * 0.001) * rate
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
faintCryTimer.destroy();
|
|
|
|
faintCryTimer = null;
|
|
|
|
if (callback)
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Failsafe
|
2023-04-12 00:08:03 +01:00
|
|
|
this.scene.time.delayedCall(3000, () => {
|
2023-03-28 19:54:52 +01:00
|
|
|
if (!faintCryTimer || !this.scene)
|
|
|
|
return;
|
|
|
|
const crySound = this.scene.sound.get(key);
|
|
|
|
if (crySound?.isPlaying)
|
|
|
|
crySound.stop();
|
|
|
|
faintCryTimer.destroy();
|
|
|
|
if (callback)
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-05-04 17:57:55 +01:00
|
|
|
isOppositeGender(pokemon: Pokemon): boolean {
|
|
|
|
return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE);
|
|
|
|
}
|
|
|
|
|
2023-04-12 00:08:03 +01:00
|
|
|
trySetStatus(effect: StatusEffect): boolean {
|
2023-05-19 17:35:06 +01:00
|
|
|
if (this.status && effect !== StatusEffect.FAINT)
|
2023-04-12 00:08:03 +01:00
|
|
|
return false;
|
|
|
|
switch (effect) {
|
|
|
|
case StatusEffect.POISON:
|
|
|
|
case StatusEffect.TOXIC:
|
2023-04-23 03:14:53 +01:00
|
|
|
if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL))
|
2023-04-12 00:08:03 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case StatusEffect.FREEZE:
|
2023-04-23 03:14:53 +01:00
|
|
|
if (this.isOfType(Type.ICE))
|
2023-04-12 00:08:03 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case StatusEffect.BURN:
|
2023-04-23 03:14:53 +01:00
|
|
|
if (this.isOfType(Type.FIRE))
|
2023-04-12 00:08:03 +01:00
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
2023-05-04 17:57:55 +01:00
|
|
|
|
|
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
|
|
applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled);
|
|
|
|
|
|
|
|
if (cancelled.value)
|
|
|
|
return false;
|
|
|
|
|
2023-04-12 00:08:03 +01:00
|
|
|
this.status = new Status(effect);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
resetStatus(): void {
|
2023-04-16 23:40:32 +01:00
|
|
|
const lastStatus = this.status.effect;
|
2023-04-12 00:08:03 +01:00
|
|
|
this.status = undefined;
|
2023-04-16 23:40:32 +01:00
|
|
|
if (lastStatus === StatusEffect.SLEEP) {
|
2023-04-22 00:30:04 +01:00
|
|
|
if (this.getTag(BattlerTagType.NIGHTMARE))
|
|
|
|
this.lapseTag(BattlerTagType.NIGHTMARE);
|
2023-04-16 23:40:32 +01:00
|
|
|
}
|
2023-04-12 00:08:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
resetSummonData(): void {
|
2023-04-04 04:38:31 +01:00
|
|
|
this.summonData = new PokemonSummonData();
|
|
|
|
this.resetBattleSummonData();
|
|
|
|
}
|
|
|
|
|
2023-04-12 00:08:03 +01:00
|
|
|
resetBattleSummonData(): void {
|
2023-04-04 04:38:31 +01:00
|
|
|
this.battleSummonData = new PokemonBattleSummonData();
|
2023-04-22 00:30:04 +01:00
|
|
|
if (this.getTag(BattlerTagType.SEEDED))
|
|
|
|
this.lapseTag(BattlerTagType.SEEDED);
|
2023-04-04 04:38:31 +01:00
|
|
|
}
|
|
|
|
|
2023-04-12 00:08:03 +01:00
|
|
|
resetTurnData(): void {
|
2023-04-04 04:38:31 +01:00
|
|
|
this.turnData = new PokemonTurnData();
|
|
|
|
}
|
|
|
|
|
2023-04-16 03:12:59 +01:00
|
|
|
getExpValue(): integer {
|
|
|
|
// Logic to factor in victor level has been removed for balancing purposes, so the player doesn't have to focus on EXP maxxing
|
2023-04-18 03:44:41 +01:00
|
|
|
return (this.getSpeciesForm().baseExp * this.level) / 5 + 1;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
tint(color: number, alpha?: number, duration?: integer, ease?: string) {
|
|
|
|
const tintSprite = this.getTintSprite();
|
|
|
|
tintSprite.setTintFill(color);
|
|
|
|
tintSprite.setVisible(true);
|
|
|
|
|
|
|
|
if (duration) {
|
|
|
|
tintSprite.setAlpha(0);
|
|
|
|
|
|
|
|
this.scene.tweens.add({
|
|
|
|
targets: tintSprite,
|
|
|
|
alpha: alpha || 1,
|
|
|
|
duration: duration,
|
|
|
|
ease: ease || 'Linear'
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
tintSprite.setAlpha(alpha);
|
|
|
|
}
|
|
|
|
|
|
|
|
untint(duration: integer, ease?: string) {
|
|
|
|
const tintSprite = this.getTintSprite();
|
|
|
|
|
|
|
|
if (duration) {
|
|
|
|
this.scene.tweens.add({
|
|
|
|
targets: tintSprite,
|
|
|
|
alpha: 0,
|
|
|
|
duration: duration,
|
|
|
|
ease: ease || 'Linear',
|
2023-04-11 04:15:06 +01:00
|
|
|
onComplete: () => {
|
|
|
|
tintSprite.setVisible(false);
|
|
|
|
tintSprite.setAlpha(1);
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
});
|
2023-04-11 04:15:06 +01:00
|
|
|
} else {
|
2023-03-28 19:54:52 +01:00
|
|
|
tintSprite.setVisible(false);
|
2023-04-11 04:15:06 +01:00
|
|
|
tintSprite.setAlpha(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enableMask() {
|
|
|
|
if (!this.maskEnabled) {
|
|
|
|
this.maskSprite = this.getTintSprite();
|
|
|
|
this.maskSprite.setVisible(true);
|
|
|
|
this.maskSprite.setPosition(this.x * 6, this.y * 6);
|
|
|
|
this.maskSprite.setScale(6);
|
|
|
|
this.maskEnabled = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
disableMask() {
|
|
|
|
if (this.maskEnabled) {
|
|
|
|
this.maskSprite.setVisible(false);
|
|
|
|
this.maskSprite.setPosition(0, 0);
|
|
|
|
this.maskSprite.setScale(1);
|
|
|
|
this.maskSprite = null;
|
|
|
|
this.maskEnabled = false;
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sparkle(): void {
|
|
|
|
if (this.shinySparkle) {
|
|
|
|
this.shinySparkle.play('sparkle');
|
2023-04-10 18:54:06 +01:00
|
|
|
this.scene.sound.play('sparkle');
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
}
|
2023-04-14 04:50:48 +01:00
|
|
|
|
|
|
|
destroy(): void {
|
|
|
|
this.battleInfo.destroy();
|
|
|
|
super.destroy();
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-04-14 23:21:33 +01:00
|
|
|
export default interface Pokemon {
|
|
|
|
scene: BattleScene
|
|
|
|
}
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
export class PlayerPokemon extends Pokemon {
|
2023-04-23 23:40:21 +01:00
|
|
|
public metBiome: Biome;
|
|
|
|
public metLevel: integer;
|
2023-04-08 05:21:44 +01:00
|
|
|
public compatibleTms: Moves[];
|
|
|
|
|
2023-04-28 20:03:42 +01:00
|
|
|
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, dataSource?: Pokemon | PokemonData) {
|
2023-04-23 06:03:09 +01:00
|
|
|
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, dataSource);
|
2023-04-21 05:04:48 +01:00
|
|
|
|
2023-04-26 04:56:38 +01:00
|
|
|
this.metBiome = scene.arena?.biomeType || Biome.TOWN;
|
2023-04-23 23:40:21 +01:00
|
|
|
this.metLevel = level;
|
2023-04-08 05:21:44 +01:00
|
|
|
this.generateCompatibleTms();
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-04-08 05:21:44 +01:00
|
|
|
isPlayer(): boolean {
|
2023-03-28 19:54:52 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
getFieldIndex(): integer {
|
|
|
|
return this.scene.getPlayerField().indexOf(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
getBattlerIndex(): BattlerIndex {
|
|
|
|
return this.getFieldIndex();
|
|
|
|
}
|
|
|
|
|
2023-04-08 05:21:44 +01:00
|
|
|
generateCompatibleTms(): void {
|
|
|
|
this.compatibleTms = [];
|
|
|
|
|
|
|
|
const tms = Object.keys(tmSpecies);
|
|
|
|
for (let tm of tms) {
|
|
|
|
const moveId = parseInt(tm) as Moves;
|
|
|
|
for (let p of tmSpecies[tm]) {
|
|
|
|
if (Array.isArray(p)) {
|
|
|
|
if (p[0] === this.species.speciesId) {
|
|
|
|
this.compatibleTms.push(moveId);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (p === this.species.speciesId) {
|
|
|
|
this.compatibleTms.push(moveId);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-10 18:54:06 +01:00
|
|
|
|
|
|
|
evolve(evolution: SpeciesEvolution): Promise<void> {
|
|
|
|
return new Promise(resolve => {
|
2023-04-14 23:21:33 +01:00
|
|
|
this.handleSpecialEvolutions(evolution);
|
2023-04-10 18:54:06 +01:00
|
|
|
this.species = getPokemonSpecies(evolution.speciesId);
|
|
|
|
this.name = this.species.name.toUpperCase();
|
2023-04-26 17:50:21 +01:00
|
|
|
const abilityCount = this.species.getAbilityCount();
|
|
|
|
if (this.abilityIndex >= abilityCount) // Shouldn't happen
|
|
|
|
this.abilityIndex = abilityCount - 1;
|
2023-04-18 03:44:41 +01:00
|
|
|
this.getSpeciesForm().generateIconAnim(this.scene, this.gender === Gender.FEMALE, this.formIndex);
|
2023-04-10 18:54:06 +01:00
|
|
|
this.compatibleTms.splice(0, this.compatibleTms.length);
|
|
|
|
this.generateCompatibleTms();
|
2023-04-18 06:32:26 +01:00
|
|
|
this.scene.gameData.setPokemonSeen(this);
|
|
|
|
this.scene.gameData.setPokemonCaught(this);
|
2023-04-10 18:54:06 +01:00
|
|
|
this.loadAssets().then(() => {
|
|
|
|
this.calculateStats();
|
|
|
|
this.updateInfo().then(() => resolve());
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2023-04-14 23:21:33 +01:00
|
|
|
|
|
|
|
private handleSpecialEvolutions(evolution: SpeciesEvolution) {
|
|
|
|
if (this.species.speciesId === Species.NINCADA && evolution.speciesId === Species.NINJASK) {
|
|
|
|
const newEvolution = pokemonEvolutions[this.species.speciesId][1];
|
|
|
|
if (newEvolution.condition.predicate(this)) {
|
2023-04-23 06:03:09 +01:00
|
|
|
const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny);
|
2023-04-14 23:21:33 +01:00
|
|
|
this.scene.getParty().push(newPokemon);
|
|
|
|
newPokemon.evolve(newEvolution);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export class EnemyPokemon extends Pokemon {
|
|
|
|
public aiType: AiType;
|
|
|
|
|
2023-04-28 20:03:42 +01:00
|
|
|
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, dataSource?: PokemonData) {
|
|
|
|
super(scene, -66, 84, species, level, dataSource?.abilityIndex, dataSource ? dataSource.formIndex : scene.arena.getFormIndex(species),
|
|
|
|
dataSource?.gender, dataSource?.shiny, dataSource);
|
|
|
|
|
|
|
|
if (!dataSource) {
|
|
|
|
let prevolution: Species;
|
|
|
|
let speciesId = species.speciesId;
|
|
|
|
while ((prevolution = pokemonPrevolutions[speciesId])) {
|
|
|
|
const evolution = pokemonEvolutions[prevolution].find(pe => pe.speciesId === speciesId);
|
|
|
|
if (evolution.condition?.enforceFunc)
|
|
|
|
evolution.condition.enforceFunc(this);
|
|
|
|
speciesId = prevolution;
|
|
|
|
}
|
2023-04-18 20:07:10 +01:00
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
this.aiType = AiType.SMART_RANDOM;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-04-29 06:40:24 +01:00
|
|
|
generateAndPopulateMoveset(): void {
|
|
|
|
switch (true) {
|
|
|
|
case (this.species.speciesId === Species.ETERNATUS):
|
|
|
|
this.moveset = [
|
|
|
|
new PokemonMove(Moves.DYNAMAX_CANNON),
|
|
|
|
new PokemonMove(Moves.CROSS_POISON),
|
|
|
|
new PokemonMove(Moves.DRAGON_DANCE),
|
|
|
|
new PokemonMove(Moves.RECOVER)
|
|
|
|
];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
super.generateAndPopulateMoveset();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-01 04:58:16 +01:00
|
|
|
getNextMove(): QueuedMove {
|
2023-04-21 02:32:48 +01:00
|
|
|
const queuedMove = this.getMoveQueue().length
|
2023-05-06 05:42:01 +01:00
|
|
|
? this.getMoveset().find(m => m.moveId === this.getMoveQueue()[0].move)
|
2023-04-13 17:16:36 +01:00
|
|
|
: null;
|
2023-04-19 23:19:55 +01:00
|
|
|
if (queuedMove) {
|
2023-04-21 02:32:48 +01:00
|
|
|
if (queuedMove.isUsable(this.getMoveQueue()[0].ignorePP))
|
2023-05-18 16:11:06 +01:00
|
|
|
return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP };
|
2023-04-19 23:19:55 +01:00
|
|
|
else {
|
2023-04-21 02:32:48 +01:00
|
|
|
this.getMoveQueue().shift();
|
2023-04-19 23:19:55 +01:00
|
|
|
return this.getNextMove();
|
|
|
|
}
|
|
|
|
}
|
2023-04-13 17:16:36 +01:00
|
|
|
|
2023-05-06 05:42:01 +01:00
|
|
|
const movePool = this.getMoveset().filter(m => m.isUsable());
|
2023-03-28 19:54:52 +01:00
|
|
|
if (movePool.length) {
|
|
|
|
if (movePool.length === 1)
|
2023-05-18 16:11:06 +01:00
|
|
|
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
|
2023-03-28 19:54:52 +01:00
|
|
|
switch (this.aiType) {
|
|
|
|
case AiType.RANDOM:
|
2023-05-18 16:11:06 +01:00
|
|
|
const moveId = movePool[Utils.randInt(movePool.length)].moveId;
|
|
|
|
return { move: moveId, targets: this.getNextTargets(moveId) };
|
2023-03-29 05:31:25 +01:00
|
|
|
case AiType.SMART_RANDOM:
|
2023-03-28 19:54:52 +01:00
|
|
|
case AiType.SMART:
|
|
|
|
const moveScores = movePool.map(() => 0);
|
2023-05-18 16:11:06 +01:00
|
|
|
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
|
2023-03-28 19:54:52 +01:00
|
|
|
for (let m in movePool) {
|
|
|
|
const pokemonMove = movePool[m];
|
|
|
|
const move = pokemonMove.getMove();
|
2023-04-11 04:15:06 +01:00
|
|
|
let moveScore = moveScores[m];
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
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);
|
2023-04-11 04:15:06 +01:00
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
moveScore /= moveTargets[move.id].length
|
|
|
|
|
2023-04-11 04:15:06 +01:00
|
|
|
// could make smarter by checking opponent def/spdef
|
|
|
|
moveScores[m] = moveScore;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
2023-03-29 05:31:25 +01:00
|
|
|
|
|
|
|
console.log(moveScores);
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
const sortedMovePool = movePool.slice(0);
|
|
|
|
sortedMovePool.sort((a, b) => {
|
|
|
|
const scoreA = moveScores[movePool.indexOf(a)];
|
|
|
|
const scoreB = moveScores[movePool.indexOf(b)];
|
|
|
|
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
|
|
|
|
});
|
|
|
|
let r = 0;
|
2023-03-29 05:31:25 +01:00
|
|
|
if (this.aiType === AiType.SMART_RANDOM) {
|
2023-04-10 12:59:00 +01:00
|
|
|
while (r < sortedMovePool.length - 1 && Utils.randInt(8) >= 5)
|
2023-03-29 05:31:25 +01:00
|
|
|
r++;
|
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName()));
|
2023-05-18 16:11:06 +01:00
|
|
|
return { move: sortedMovePool[r].moveId, targets: moveTargets[sortedMovePool[r].moveId] };
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
}
|
2023-04-19 23:19:55 +01:00
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
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] ];
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
isPlayer() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
getFieldIndex(): integer {
|
|
|
|
return this.scene.getEnemyField().indexOf(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
getBattlerIndex(): BattlerIndex {
|
|
|
|
return BattlerIndex.ENEMY + this.getFieldIndex();
|
|
|
|
}
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
addToParty() {
|
2023-04-14 23:21:33 +01:00
|
|
|
const party = this.scene.getParty();
|
2023-04-01 01:19:57 +01:00
|
|
|
let ret: PlayerPokemon = null;
|
2023-03-28 19:54:52 +01:00
|
|
|
|
2023-04-01 01:19:57 +01:00
|
|
|
if (party.length < 6) {
|
2023-04-23 06:03:09 +01:00
|
|
|
const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this);
|
2023-04-01 01:19:57 +01:00
|
|
|
party.push(newPokemon);
|
|
|
|
ret = newPokemon;
|
|
|
|
}
|
2023-04-01 03:31:20 +01:00
|
|
|
|
2023-04-01 01:19:57 +01:00
|
|
|
return ret;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 17:16:36 +01:00
|
|
|
export interface TurnMove {
|
|
|
|
move: Moves;
|
2023-05-18 16:11:06 +01:00
|
|
|
targets?: BattlerIndex[];
|
2023-04-13 17:16:36 +01:00
|
|
|
result: MoveResult;
|
2023-04-19 23:19:55 +01:00
|
|
|
virtual?: boolean;
|
2023-04-25 06:32:48 +01:00
|
|
|
turn?: integer;
|
2023-04-13 17:16:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface QueuedMove {
|
|
|
|
move: Moves;
|
2023-05-18 16:11:06 +01:00
|
|
|
targets: BattlerIndex[];
|
2023-04-13 17:16:36 +01:00
|
|
|
ignorePP?: boolean;
|
|
|
|
}
|
|
|
|
|
2023-05-06 05:42:01 +01:00
|
|
|
export interface AttackMoveResult {
|
|
|
|
move: Moves;
|
|
|
|
result: DamageResult;
|
|
|
|
damage: integer;
|
|
|
|
sourceId: integer;
|
|
|
|
}
|
|
|
|
|
2023-04-04 04:38:31 +01:00
|
|
|
export class PokemonSummonData {
|
2023-04-11 04:15:06 +01:00
|
|
|
public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ];
|
2023-04-13 17:16:36 +01:00
|
|
|
public moveQueue: QueuedMove[] = [];
|
2023-04-22 00:30:04 +01:00
|
|
|
public tags: BattlerTag[] = [];
|
2023-05-06 05:42:01 +01:00
|
|
|
public moveset: PokemonMove[];
|
2023-04-22 00:30:04 +01:00
|
|
|
public types: Type[];
|
2023-04-04 04:38:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export class PokemonBattleSummonData {
|
2023-04-15 22:40:18 +01:00
|
|
|
public turnCount: integer = 1;
|
2023-04-28 20:03:42 +01:00
|
|
|
public moveHistory: TurnMove[] = [];
|
2023-04-04 04:38:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export class PokemonTurnData {
|
|
|
|
public flinched: boolean;
|
2023-04-10 21:17:25 +01:00
|
|
|
public hitCount: integer;
|
2023-04-04 04:38:31 +01:00
|
|
|
public hitsLeft: integer;
|
2023-04-14 06:08:44 +01:00
|
|
|
public damageDealt: integer = 0;
|
2023-05-06 05:42:01 +01:00
|
|
|
public attacksReceived: AttackMoveResult[] = [];
|
2023-04-04 04:38:31 +01:00
|
|
|
}
|
|
|
|
|
2023-03-29 05:31:25 +01:00
|
|
|
export enum AiType {
|
2023-03-28 19:54:52 +01:00
|
|
|
RANDOM,
|
2023-03-29 05:31:25 +01:00
|
|
|
SMART_RANDOM,
|
2023-03-28 19:54:52 +01:00
|
|
|
SMART
|
2023-04-29 06:40:24 +01:00
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
|
|
|
|
export enum MoveResult {
|
2023-05-18 16:11:06 +01:00
|
|
|
PENDING,
|
|
|
|
SUCCESS,
|
|
|
|
FAIL,
|
|
|
|
MISS,
|
|
|
|
OTHER
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum HitResult {
|
2023-04-15 22:40:18 +01:00
|
|
|
EFFECTIVE = 1,
|
2023-03-28 19:54:52 +01:00
|
|
|
SUPER_EFFECTIVE,
|
|
|
|
NOT_VERY_EFFECTIVE,
|
|
|
|
NO_EFFECT,
|
2023-04-13 17:16:36 +01:00
|
|
|
STATUS,
|
2023-05-18 16:11:06 +01:00
|
|
|
FAIL,
|
|
|
|
MISS,
|
2023-03-28 19:54:52 +01:00
|
|
|
OTHER
|
2023-04-29 06:40:24 +01:00
|
|
|
}
|
2023-03-28 19:54:52 +01:00
|
|
|
|
2023-05-18 16:11:06 +01:00
|
|
|
export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.OTHER;
|
2023-04-15 06:32:16 +01:00
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
export class PokemonMove {
|
2023-04-13 17:16:36 +01:00
|
|
|
public moveId: Moves;
|
2023-03-28 19:54:52 +01:00
|
|
|
public ppUsed: integer;
|
|
|
|
public ppUp: integer;
|
2023-04-19 23:19:55 +01:00
|
|
|
public virtual: boolean;
|
2023-03-28 19:54:52 +01:00
|
|
|
public disableTurns: integer;
|
|
|
|
|
2023-04-19 23:19:55 +01:00
|
|
|
constructor(moveId: Moves, ppUsed?: integer, ppUp?: integer, virtual?: boolean) {
|
2023-03-28 19:54:52 +01:00
|
|
|
this.moveId = moveId;
|
2023-04-14 04:04:51 +01:00
|
|
|
this.ppUsed = ppUsed || 0;
|
|
|
|
this.ppUp = ppUp || 0;
|
2023-04-19 23:19:55 +01:00
|
|
|
this.virtual = !!virtual;
|
2023-03-28 19:54:52 +01:00
|
|
|
this.disableTurns = 0;
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:19:55 +01:00
|
|
|
isUsable(ignorePp?: boolean): boolean {
|
|
|
|
if (this.isDisabled())
|
2023-03-28 19:54:52 +01:00
|
|
|
return false;
|
2023-04-19 23:19:55 +01:00
|
|
|
return ignorePp || this.ppUsed < this.getMove().pp + this.ppUp || this.getMove().pp === -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
isDisabled(): boolean {
|
|
|
|
return !!this.disableTurns;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
getMove(): Move {
|
2023-04-21 02:32:48 +01:00
|
|
|
return allMoves[this.moveId];
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
|
2023-04-28 05:25:33 +01:00
|
|
|
getPpRatio(): number {
|
|
|
|
return 1 - (this.ppUsed / (this.getMove().pp + this.ppUp));
|
|
|
|
}
|
|
|
|
|
2023-03-28 19:54:52 +01:00
|
|
|
getName(): string {
|
2023-04-07 03:24:13 +01:00
|
|
|
return this.getMove().name;
|
2023-03-28 19:54:52 +01:00
|
|
|
}
|
|
|
|
}
|