pokerogue/src/pokemon.ts

1793 lines
61 KiB
TypeScript
Raw Normal View History

2023-03-28 11:54:52 -07:00
import Phaser from 'phaser';
2023-10-26 13:33:59 -07:00
import BattleScene, { AnySound } from './battle-scene';
2023-04-20 12:46:05 -07:00
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from './ui/battle-info';
2023-11-07 19:23:42 -08:00
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariablePowerAttr, Moves, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, AttackMove, AddBattlerTagAttr, OneHitKOAttr } from "./data/move";
2023-04-20 12:46:05 -07:00
import { default as PokemonSpecies, PokemonSpeciesForm, getPokemonSpecies } from './data/pokemon-species';
2023-03-28 11:54:52 -07:00
import * as Utils from './utils';
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier } from './data/type';
2023-04-20 12:46:05 -07:00
import { getLevelTotalExp } from './data/exp';
import { Stat } from './data/pokemon-stat';
import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier } from './modifier/modifier';
2023-04-20 12:46:05 -07: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-10-31 11:09:33 -07:00
import { DamagePhase, FaintPhase, SwitchSummonPhase } from './battle-phases';
2023-04-20 12:46:05 -07:00
import { BattleStat } from './data/battle-stat';
import { BattlerTag, BattlerTagLapseType, BattlerTagType, TypeBoostTag, getBattlerTag } from './data/battler-tag';
2023-04-20 12:46:05 -07:00
import { Species } from './data/species';
import { WeatherType } from './data/weather';
import { TempBattleStat } from './data/temp-battle-stat';
import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag';
2023-04-23 15:40:21 -07:00
import { Biome } from './data/biome';
2023-11-05 20:27:40 -08:00
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
2023-04-28 12:03:42 -07:00
import PokemonData from './system/pokemon-data';
2023-10-25 06:41:37 -07:00
import { BattlerIndex } from './battle';
2023-10-31 11:09:33 -07:00
import { Mode } from './ui/ui';
import PartyUiHandler, { PartyOption, PartyUiMode } from './ui/party-ui-handler';
2023-11-03 21:32:12 -07:00
import SoundFade from 'phaser3-rex-plugins/plugins/soundfade';
import { GameMode } from './game-mode';
import { LevelMoves } from './data/pokemon-level-moves';
export enum FieldPosition {
CENTER,
LEFT,
RIGHT
}
2023-03-28 11:54:52 -07:00
2023-07-11 08:32:56 -07:00
const ABILITY_OVERRIDE = Abilities.NONE;
const MOVE_OVERRIDE = Moves.NONE;
const OPP_ABILITY_OVERRIDE = Abilities.NONE;
const OPP_MOVE_OVERRIDE = Moves.NONE;
2023-03-28 11:54:52 -07:00
export default abstract class Pokemon extends Phaser.GameObjects.Container {
public id: integer;
public name: string;
public species: PokemonSpecies;
public formIndex: integer;
2023-04-22 22:03:09 -07:00
public abilityIndex: integer;
2023-03-28 11:54:52 -07:00
public shiny: boolean;
public pokeball: PokeballType;
protected battleInfo: BattleInfo;
public level: integer;
public exp: integer;
public levelExp: integer;
2023-04-01 17:06:44 -07:00
public gender: Gender;
2023-03-28 11:54:52 -07:00
public hp: integer;
public stats: integer[];
public ivs: integer[];
public moveset: PokemonMove[];
2023-04-11 16:08:03 -07:00
public status: Status;
2023-03-28 11:54:52 -07:00
public winCount: integer;
public pokerus: boolean;
2023-03-28 11:54:52 -07:00
2023-11-03 21:32:12 -07:00
public fusionSpecies: PokemonSpecies;
public fusionFormIndex: integer;
public fusionAbilityIndex: integer;
public fusionShiny: boolean;
public fusionGender: Gender;
2023-04-03 20:38:31 -07:00
public summonData: PokemonSummonData;
public battleSummonData: PokemonBattleSummonData;
public turnData: PokemonTurnData;
public fieldPosition: FieldPosition;
2023-04-10 20:15:06 -07:00
public maskEnabled: boolean;
public maskSprite: Phaser.GameObjects.Sprite;
2023-03-28 11:54:52 -07:00
private shinySparkle: Phaser.GameObjects.Sprite;
2023-04-28 12:03:42 -07: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 11:54:52 -07:00
super(scene, x, y);
2023-04-26 13:07:29 -07:00
if (!species.isObtainable() && this.isPlayer())
throw `Cannot create a player Pokemon for species '${species.name}'`;
const hiddenAbilityChance = new Utils.IntegerHolder(256);
if (!this.hasTrainer())
this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value);
const randAbilityIndex = Utils.randSeedInt(2);
this.name = species.name;
2023-03-28 11:54:52 -07:00
this.species = species;
this.battleInfo = this.isPlayer()
? new PlayerBattleInfo(scene)
: new EnemyBattleInfo(scene);
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level;
2023-10-29 20:11:30 -07:00
this.abilityIndex = abilityIndex || (species.abilityHidden && hasHiddenAbility ? species.ability2 ? 2 : 1 : species.ability2 ? randAbilityIndex : 0);
this.formIndex = formIndex || 0;
if (gender !== undefined)
this.gender = gender;
if (shiny !== undefined)
this.shiny = shiny;
2023-03-28 11:54:52 -07: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-03 20:38:31 -07:00
this.status = dataSource.status;
2023-03-28 11:54:52 -07:00
this.winCount = dataSource.winCount;
this.pokerus = !!dataSource.pokerus;
2023-11-03 21:32:12 -07:00
this.fusionSpecies = dataSource.fusionSpecies instanceof PokemonSpecies ? dataSource.fusionSpecies : getPokemonSpecies(dataSource.fusionSpecies);
this.fusionFormIndex = dataSource.fusionFormIndex;
this.fusionAbilityIndex = dataSource.fusionAbilityIndex;
this.fusionShiny = dataSource.fusionShiny;
this.fusionGender = dataSource.fusionGender;
2023-03-28 11:54:52 -07:00
} 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))
];
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 11:54:52 -07:00
}
if (this.shiny === undefined)
this.trySetShiny();
2023-03-28 11:54:52 -07:00
this.winCount = 0;
this.pokerus = false;
2023-11-08 15:36:30 -08:00
if (scene.gameMode === GameMode.SPLICED_ENDLESS)
this.generateFusionSpecies();
2023-03-28 11:54:52 -07:00
}
2023-04-26 13:07:29 -07:00
if (!species.isObtainable())
this.shiny = false;
2023-03-28 11:54:52 -07:00
this.calculateStats();
this.fieldPosition = FieldPosition.CENTER;
scene.fieldUI.addAt(this.battleInfo, 0);
2023-03-28 11:54:52 -07:00
this.battleInfo.initInfo(this);
2023-06-04 18:47:43 -07:00
const getSprite = (hasShadow?: boolean) => {
2023-03-28 11:54:52 -07:00
const ret = this.scene.add.sprite(0, 0, `pkmn__${this.isPlayer() ? 'back__' : ''}sub`);
ret.setOrigin(0.5, 1);
2023-06-04 18:47:43 -07:00
ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow });
2023-03-28 11:54:52 -07:00
return ret;
};
2023-06-04 18:47:43 -07:00
const sprite = getSprite(true);
2023-03-28 11:54:52 -07:00
const tintSprite = getSprite();
tintSprite.setVisible(false);
this.add(sprite);
this.add(tintSprite);
this.getSpeciesForm().generateIconAnim(scene, this.getGender() === Gender.FEMALE, formIndex);
2023-03-28 11:54:52 -07: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-06-06 07:14:53 -07:00
isOnField(): boolean {
2023-10-31 11:09:33 -07:00
if (!this.scene)
return false;
2023-06-06 07:14:53 -07:00
return this.scene.field.getIndex(this) > -1;
}
isFainted(checkStatus?: boolean): boolean {
return !this.hp && (!checkStatus || this.status?.effect === StatusEffect.FAINT);
}
isActive(onField?: boolean): boolean {
2023-10-31 11:09:33 -07:00
if (!this.scene)
return false;
2023-06-06 07:14:53 -07:00
return !this.isFainted() && !!this.scene && (!onField || this.isOnField());
}
2023-03-28 11:54:52 -07:00
abstract isPlayer(): boolean;
abstract hasTrainer(): boolean;
abstract getFieldIndex(): integer;
abstract getBattlerIndex(): BattlerIndex;
2023-03-28 21:31:25 -07:00
loadAssets(): Promise<void> {
return new Promise(resolve => {
const moveIds = this.getMoveset().map(m => m.getMove().id);
2023-04-11 16:08:03 -07:00
Promise.allSettled(moveIds.map(m => initMoveAnim(m)))
2023-04-03 17:47:41 -07:00
.then(() => {
2023-04-14 15:21:33 -07:00
loadMoveAnimAssets(this.scene, moveIds);
this.getSpeciesForm().loadAssets(this.scene, this.getGender() === Gender.FEMALE, this.formIndex, this.shiny);
2023-11-03 21:32:12 -07:00
if (this.fusionSpecies)
this.getFusionSpeciesForm().loadAssets(this.scene, this.getGender() === Gender.FEMALE, this.fusionFormIndex, this.shiny);
if (this.isPlayer())
2023-04-14 15:21:33 -07:00
this.scene.loadAtlas(this.getBattleSpriteKey(), 'pokemon', this.getBattleSpriteAtlasPath());
2023-04-03 17:47:41 -07:00
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
if (this.isPlayer()) {
2023-04-12 16:09:15 -07: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-04 18:10:11 -07:00
this.playAnim();
2023-04-03 17:47:41 -07:00
resolve();
});
if (!this.scene.load.isLoading())
this.scene.load.start();
2023-03-28 21:31:25 -07:00
});
});
}
getSpriteAtlasPath(ignoreOverride?: boolean): string {
return this.getSpriteId(ignoreOverride).replace(/\_{2}/g, '/');
2023-03-28 11:54:52 -07:00
}
getBattleSpriteAtlasPath(ignoreOverride?: boolean): string {
return this.getBattleSpriteId(ignoreOverride).replace(/\_{2}/g, '/');
}
getSpriteId(ignoreOverride?: boolean): string {
return this.getSpeciesForm(ignoreOverride).getSpriteId(this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex, this.shiny);
}
getBattleSpriteId(ignoreOverride?: boolean): string {
return `${this.isPlayer() ? 'back__' : ''}${this.getSpriteId(ignoreOverride)}`;
2023-03-28 11:54:52 -07:00
}
getSpriteKey(ignoreOverride?: boolean): string {
return this.getSpeciesForm(ignoreOverride).getSpriteKey(this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex, this.shiny);
2023-03-28 11:54:52 -07:00
}
getBattleSpriteKey(ignoreOverride?: boolean): string {
return `pkmn__${this.getBattleSpriteId(ignoreOverride)}`;
}
getIconAtlasKey(ignoreOverride?: boolean): string {
return this.getSpeciesForm(ignoreOverride).getIconAtlasKey(this.formIndex);
}
getIconId(ignoreOverride?: boolean): string {
return this.getSpeciesForm(ignoreOverride).getIconId(this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex);
2023-03-28 11:54:52 -07:00
}
getIconKey(ignoreOverride?: boolean): string {
return `pkmn_icon__${this.getIconId(ignoreOverride)}`;
2023-03-28 11:54:52 -07:00
}
getSpeciesForm(ignoreOverride?: boolean): PokemonSpeciesForm {
if (!ignoreOverride && this.summonData?.speciesForm)
return this.summonData.speciesForm;
if (!this.species.forms?.length)
return this.species;
return this.species.forms[this.formIndex];
}
2023-11-03 21:32:12 -07:00
getFusionSpeciesForm(): PokemonSpeciesForm {
if (!this.fusionSpecies.forms?.length || this.fusionFormIndex >= this.fusionSpecies.forms.length)
return this.fusionSpecies;
return this.fusionSpecies.forms[this.fusionFormIndex];
}
2023-03-28 21:31:25 -07:00
getSprite(): Phaser.GameObjects.Sprite {
2023-03-28 11:54:52 -07:00
return this.getAt(0) as Phaser.GameObjects.Sprite;
}
2023-03-28 21:31:25 -07:00
getTintSprite(): Phaser.GameObjects.Sprite {
2023-04-10 20:15:06 -07:00
return !this.maskEnabled
? this.getAt(1) as Phaser.GameObjects.Sprite
: this.maskSprite;
2023-03-28 11:54:52 -07:00
}
playAnim(): void {
this.getSprite().play(this.getBattleSpriteKey());
this.getTintSprite().play(this.getBattleSpriteKey());
2023-04-04 18:10:11 -07: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;
}
});
}
getStat(stat: Stat): integer {
return this.stats[stat];
}
2023-11-05 20:27:40 -08:00
getBattleStat(stat: Stat, opponent?: Pokemon): integer {
2023-04-10 20:15:06 -07:00
if (stat === Stat.HP)
return this.getStat(Stat.HP);
const battleStat = (stat - 1) as BattleStat;
const statLevel = new Utils.IntegerHolder(this.summonData.battleStats[battleStat]);
2023-11-05 20:27:40 -08:00
if (opponent)
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, statLevel);
if (this.isPlayer())
2023-04-20 16:44:56 -07:00
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), battleStat as integer as TempBattleStat, statLevel);
const statValue = new Utils.NumberHolder(this.getStat(stat));
2023-05-02 12:56:41 -07:00
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, battleStat, statValue);
let ret = statValue.value * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value));
if (stat === Stat.SPDEF && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM)
ret *= 1.5;
2023-04-20 21:10:45 -07:00
if (stat === Stat.SPD && this.status && this.status.effect === StatusEffect.PARALYSIS)
2023-04-11 16:08:03 -07:00
ret >>= 2;
return ret;
2023-04-10 20:15:06 -07:00
}
2023-04-10 04:59:00 -07:00
calculateStats(): void {
2023-03-28 11:54:52 -07:00
if (!this.stats)
this.stats = [ 0, 0, 0, 0, 0, 0 ];
const baseStats = this.getSpeciesForm().baseStats.slice(0);
2023-11-03 21:32:12 -07:00
if (this.fusionSpecies) {
const fusionBaseStats = this.getFusionSpeciesForm().baseStats;
for (let s = 0; s < this.stats.length; s++)
baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2);
}
2023-04-20 16:44:56 -07:00
this.scene.applyModifiers(PokemonBaseStatModifier, this.isPlayer(), this, baseStats);
2023-03-28 11:54:52 -07: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);
2023-05-29 10:25:36 -07:00
if (this.getAbility().hasAttr(NonSuperEffectiveImmunityAbAttr))
value = 1;
2023-03-28 11:54:52 -07:00
if (this.hp > value || this.hp === undefined)
this.hp = value;
2023-04-24 19:32:12 -07:00
else if (this.hp) {
2023-03-28 11:54:52 -07: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 04:59:00 -07:00
getMaxHp(): integer {
return this.getStat(Stat.HP);
2023-03-28 11:54:52 -07:00
}
2023-04-11 08:04:39 -07:00
getInverseHp(): integer {
return this.getMaxHp() - this.hp;
}
2023-04-10 04:59:00 -07:00
getHpRatio(): number {
2023-03-29 09:23:52 -07:00
return Math.floor((this.hp / this.getMaxHp()) * 100) / 100;
}
getGender(ignoreOverride?: boolean): Gender {
if (!ignoreOverride && this.summonData?.gender !== undefined)
return this.summonData.gender;
return this.gender;
}
2023-11-05 20:48:04 -08:00
isShiny(): boolean {
return this.shiny || (this.fusionSpecies && this.fusionShiny);
}
getMoveset(ignoreOverride?: boolean): PokemonMove[] {
if (!ignoreOverride && this.summonData?.moveset)
return this.summonData.moveset;
return this.moveset;
}
getLearnableLevelMoves(): Moves[] {
return this.getLevelMoves(1).filter(lm => !this.moveset.filter(m => m.moveId === lm).length);
}
getTypes(ignoreOverride?: boolean): Type[] {
2023-04-21 16:30:04 -07:00
const types = [];
if (!ignoreOverride && this.summonData?.types)
2023-04-21 16:30:04 -07:00
this.summonData.types.forEach(t => types.push(t));
else {
const speciesForm = this.getSpeciesForm();
types.push(speciesForm.type1);
if (speciesForm.type2 !== null)
types.push(speciesForm.type2);
2023-04-21 16:30:04 -07:00
}
2023-04-15 21:29:55 -07:00
2023-05-08 15:48:35 -07:00
if (this.getTag(BattlerTagType.IGNORE_FLYING) || this.scene.arena.getTag(ArenaTagType.GRAVITY)) {
2023-04-21 16:30:04 -07:00
const flyingIndex = types.indexOf(Type.FLYING);
2023-04-15 21:29:55 -07:00
if (flyingIndex > -1)
2023-04-21 16:30:04 -07:00
types.splice(flyingIndex, 1);
2023-04-15 21:29:55 -07:00
}
2023-04-21 16:30:04 -07:00
if (!types.length)
types.push(Type.NORMAL);
2023-04-15 21:29:55 -07:00
2023-04-21 16:30:04 -07:00
return types;
2023-04-15 21:29:55 -07:00
}
2023-04-22 19:14:53 -07:00
isOfType(type: Type) {
return this.getTypes().indexOf(type) > -1;
}
2023-04-26 20:33:13 -07:00
getAbility(): Ability {
2023-07-11 08:32:56 -07:00
if (ABILITY_OVERRIDE && this.isPlayer())
return abilities[ABILITY_OVERRIDE];
if (OPP_ABILITY_OVERRIDE && !this.isPlayer())
return abilities[OPP_ABILITY_OVERRIDE];
2023-11-03 21:32:12 -07:00
if (this.fusionSpecies)
return abilities[this.getFusionSpeciesForm().getAbility(this.fusionAbilityIndex)];
2023-10-20 11:19:22 -07:00
return abilities[this.getSpeciesForm().getAbility(this.abilityIndex)];
2023-04-26 20:33:13 -07:00
}
canApplyAbility(): boolean {
return this.hp && !this.getAbility().conditions.find(condition => !condition(this));
}
getAttackMoveEffectiveness(moveType: Type): TypeDamageMultiplier {
const types = this.getTypes();
return getTypeDamageMultiplier(moveType, types[0]) * (types.length > 1 ? getTypeDamageMultiplier(moveType, types[1]) : 1) as TypeDamageMultiplier;
}
2023-10-07 13:08:33 -07:00
getMatchupScore(pokemon: Pokemon): number {
const types = this.getTypes();
const enemyTypes = pokemon.getTypes();
let atkScore = pokemon.getAttackMoveEffectiveness(types[0]);
let defScore = 1 / this.getAttackMoveEffectiveness(enemyTypes[0]);
if (types.length > 1)
atkScore *= pokemon.getAttackMoveEffectiveness(types[1]);
if (enemyTypes.length > 1)
defScore *= (1 / this.getAttackMoveEffectiveness(enemyTypes[1]));
return (atkScore + defScore) * (this.getHpRatio() + (1 - pokemon.getHpRatio()));
}
2023-04-10 04:59:00 -07:00
getEvolution(): SpeciesEvolution {
if (!pokemonEvolutions.hasOwnProperty(this.species.speciesId))
return null;
const evolutions = pokemonEvolutions[this.species.speciesId];
for (let e of evolutions) {
2023-04-15 19:51:33 -07:00
if (!e.item && this.level >= e.level) {
if (e.condition === null || (e.condition as SpeciesEvolutionCondition).predicate(this))
2023-04-10 04:59:00 -07:00
return e;
}
}
return null;
}
2023-04-10 10:54:06 -07:00
getLevelMoves(startingLevel?: integer): Moves[] {
const ret: Moves[] = [];
let levelMoves = this.getSpeciesForm().getLevelMoves();
if (!startingLevel)
2023-04-10 10:54:06 -07:00
startingLevel = this.level;
if (this.fusionSpecies) {
const fusionLevelMoves = this.getFusionSpeciesForm().getLevelMoves();
const newLevelMoves: LevelMoves = [];
for (let l = startingLevel; l <= this.level; l++) {
while (levelMoves.length && levelMoves[0][0] === l) {
const levelMove = levelMoves.shift();
if (!newLevelMoves.find(lm => lm[1] === levelMove[1]))
newLevelMoves.push(levelMove);
}
while (fusionLevelMoves.length && fusionLevelMoves[0][0] === l) {
const fusionLevelMove = fusionLevelMoves.shift();
if (!newLevelMoves.find(lm => lm[1] === fusionLevelMove[1]))
newLevelMoves.push(fusionLevelMove);
}
}
levelMoves = newLevelMoves;
}
if (levelMoves) {
2023-04-10 10:54:06 -07:00
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 04:59:00 -07:00
}
setMove(moveIndex: integer, moveId: Moves): void {
const move = moveId ? new PokemonMove(moveId) : null;
2023-05-06 14:31:45 -07:00
this.moveset[moveIndex] = move;
if (this.summonData?.moveset)
this.summonData.moveset[moveIndex] = move;
}
2023-04-10 04:59:00 -07:00
trySetShiny(): boolean {
const rand1 = Utils.binToDec(Utils.decToBin(this.id).substring(0, 16));
const rand2 = Utils.binToDec(Utils.decToBin(this.id).substring(16, 32));
const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId;
const F = rand1 ^ rand2;
let shinyThreshold = new Utils.IntegerHolder(32);
if (!this.hasTrainer()) {
this.scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
console.log(shinyThreshold.value);
}
this.shiny = (E ^ F) < shinyThreshold.value;
if ((E ^ F) < 32)
console.log('REAL SHINY!!');
return this.shiny;
}
2023-11-08 15:36:30 -08:00
generateFusionSpecies(forStarter?: boolean): void {
const hiddenAbilityChance = new Utils.IntegerHolder(256);
if (!this.hasTrainer())
this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !Utils.randSeedInt(hiddenAbilityChance.value);
const randAbilityIndex = Utils.randSeedInt(2);
const filter = !forStarter ? this.species.getCompatibleFusionSpeciesFilter()
: species => {
return pokemonEvolutions.hasOwnProperty(species.speciesId)
&& !pokemonPrevolutions.hasOwnProperty(species.speciesId)
&& !species.pseudoLegendary
&& !species.legendary
&& !species.mythical
};
this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true);
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? this.fusionSpecies.ability2 ? 2 : 1 : this.fusionSpecies.ability2 ? randAbilityIndex : 0);
this.fusionFormIndex = this.scene.getSpeciesFormIndex(this.fusionSpecies);
this.fusionShiny = this.shiny;
if (this.getFusionSpeciesForm().malePercent === null)
this.fusionGender = Gender.GENDERLESS;
else {
const genderChance = (this.id % 256) * 0.390625;
if (genderChance < this.getFusionSpeciesForm().malePercent)
this.fusionGender = Gender.MALE;
else
this.fusionGender = Gender.FEMALE;
}
}
2023-04-10 04:59:00 -07:00
generateAndPopulateMoveset(): void {
2023-03-28 11:54:52 -07:00
this.moveset = [];
const movePool = [];
2023-11-03 21:32:12 -07:00
const allLevelMoves = this.getSpeciesForm().getLevelMoves();
2023-03-28 11:54:52 -07:00
if (!allLevelMoves) {
console.log(this.species.speciesId, 'ERROR')
return;
}
2023-04-28 22:40:24 -07:00
2023-03-28 11:54:52 -07: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-20 18:32:48 -07:00
const move = allMoves[m];
2023-03-28 11:54:52 -07:00
return move.category !== MoveCategory.STATUS;
});
if (attackMovePool.length) {
const moveIndex = Utils.randSeedInt(attackMovePool.length);
2023-03-28 11:54:52 -07:00
this.moveset.push(new PokemonMove(attackMovePool[moveIndex], 0, 0));
2023-04-20 18:32:48 -07:00
console.log(allMoves[attackMovePool[moveIndex]]);
2023-03-28 11:54:52 -07:00
movePool.splice(movePool.findIndex(m => m === attackMovePool[moveIndex]), 1);
}
while (movePool.length && this.moveset.length < 4) {
const moveIndex = Utils.randSeedInt(movePool.length);
2023-03-28 11:54:52 -07:00
this.moveset.push(new PokemonMove(movePool[moveIndex], 0, 0));
2023-04-20 18:32:48 -07:00
console.log(allMoves[movePool[moveIndex]]);
2023-03-28 11:54:52 -07:00
movePool.splice(moveIndex, 1);
}
2023-07-11 08:32:56 -07:00
if (MOVE_OVERRIDE && this.isPlayer())
this.moveset[0] = new PokemonMove(MOVE_OVERRIDE);
else if (OPP_MOVE_OVERRIDE && !this.isPlayer())
this.moveset[0] = new PokemonMove(OPP_MOVE_OVERRIDE);
2023-03-28 11:54:52 -07:00
}
trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean {
const move = this.getMoveset().length > moveIndex
? this.getMoveset()[moveIndex]
2023-03-28 11:54:52 -07:00
: null;
2023-10-25 06:41:37 -07:00
return move?.isUsable(this, ignorePp);
2023-03-28 11:54:52 -07:00
}
showInfo() {
if (!this.battleInfo.visible) {
const otherBattleInfo = this.scene.fieldUI.getAll().slice(0, 4).filter(ui => ui instanceof BattleInfo && ((ui as BattleInfo) instanceof PlayerBattleInfo) === this.isPlayer()).find(() => true);
if (!otherBattleInfo || !this.getFieldIndex())
this.scene.fieldUI.sendToBack(this.battleInfo);
else
this.scene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo);
2023-03-28 11:54:52 -07:00
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'
});
}
}
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 11:54:52 -07:00
}
2023-04-10 04:59:00 -07:00
updateInfo(instant?: boolean): Promise<void> {
return this.battleInfo.updateInfo(this, instant);
2023-03-28 11:54:52 -07:00
}
addExp(exp: integer) {
const maxExpLevel = this.scene.getMaxExpLevel();
const initialExp = this.exp;
2023-03-28 11:54:52 -07:00
this.exp += exp;
while (this.level < maxExpLevel && this.exp >= getLevelTotalExp(this.level + 1, this.getSpeciesForm().growthRate))
2023-03-28 11:54:52 -07:00
this.level++;
if (this.level >= maxExpLevel) {
console.log(initialExp, this.exp, getLevelTotalExp(this.level, this.getSpeciesForm().growthRate));
this.exp = Math.max(getLevelTotalExp(this.level, this.getSpeciesForm().growthRate), initialExp);
}
this.levelExp = this.exp - getLevelTotalExp(this.level, this.getSpeciesForm().growthRate);
2023-03-28 11:54:52 -07:00
}
getOpponent(targetIndex: integer): Pokemon {
const ret = this.getOpponents()[targetIndex];
if (ret.summonData)
return ret;
return null;
}
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-14 22:32:16 -07:00
const move = battlerMove.getMove();
const moveCategory = move.category;
let damage = new Utils.NumberHolder(0);
const cancelled = new Utils.BooleanHolder(false);
const typeless = !!move.getAttrs(TypelessAttr).length
const typeMultiplier = new Utils.NumberHolder(!typeless && moveCategory !== MoveCategory.STATUS
? 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-14 22:32:16 -07:00
switch (moveCategory) {
case MoveCategory.PHYSICAL:
case MoveCategory.SPECIAL:
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
const power = new Utils.NumberHolder(move.power);
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power);
if (!typeless)
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
2023-04-26 20:33:13 -07:00
if (cancelled.value)
result = HitResult.NO_EFFECT;
2023-04-26 20:33:13 -07:00
else {
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-26 20:33:13 -07:00
applyMoveAttrs(VariablePowerAttr, source, this, move, power);
if (!typeless) {
this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power);
this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, power);
}
2023-05-05 15:20:55 -07: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-11-05 20:27:40 -08:00
const sourceAtk = source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this);
const targetDef = this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source);
2023-10-26 17:02:30 -07:00
const stabMultiplier = new Utils.IntegerHolder(source.species.type1 === move.type || (source.species.type2 !== null && source.species.type2 === move.type) ? 1.5 : 1);
2023-04-26 20:33:13 -07:00
const criticalMultiplier = isCritical ? 2 : 1;
const isTypeImmune = (typeMultiplier.value * weatherTypeMultiplier) === 0;
2023-10-26 17:02:30 -07:00
applyAbAttrs(StabBoostAbAttr, source, null, stabMultiplier);
if (!isTypeImmune) {
damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk / targetDef) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * weatherTypeMultiplier * ((Utils.randInt(15) + 85) / 100)) * criticalMultiplier;
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN)
damage.value = Math.floor(damage.value / 2);
move.getAttrs(HitsTagAttr).map(hta => hta as HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => {
if (this.getTag(hta.tagType))
damage.value *= 2;
});
}
2023-04-13 09:16:36 -07:00
2023-04-26 20:33:13 -07:00
const fixedDamage = new Utils.IntegerHolder(0);
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage);
if (!isTypeImmune && fixedDamage.value) {
damage.value = fixedDamage.value;
2023-04-26 20:33:13 -07:00
isCritical = false;
result = HitResult.EFFECTIVE;
2023-04-26 20:33:13 -07:00
}
2023-04-14 22:32:16 -07:00
2023-04-26 20:33:13 -07:00
console.log('damage', damage, move.name, move.power, sourceAtk, targetDef);
if (!result) {
2023-11-07 19:23:42 -08:00
if (!typeMultiplier.value)
result = HitResult.NO_EFFECT;
2023-11-07 19:23:42 -08:00
else {
const oneHitKo = new Utils.BooleanHolder(false);
applyMoveAttrs(OneHitKOAttr, source, this, move, oneHitKo);
if (oneHitKo.value) {
result = HitResult.ONE_HIT_KO;
isCritical = false;
damage.value = this.hp;
} else if (typeMultiplier.value >= 2)
result = HitResult.SUPER_EFFECTIVE;
else if (typeMultiplier.value >= 1)
result = HitResult.EFFECTIVE;
else
result = HitResult.NOT_VERY_EFFECTIVE;
}
2023-04-26 20:33:13 -07:00
}
2023-04-14 22:32:16 -07:00
if (!source.isPlayer())
this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage);
if (!this.isPlayer())
this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage);
2023-04-26 20:33:13 -07:00
if (damage) {
this.scene.unshiftPhase(new DamagePhase(this.scene, this.getBattlerIndex(), result as DamageResult));
2023-04-26 20:33:13 -07:00
if (isCritical)
this.scene.queueMessage('A critical hit!');
this.scene.setPhaseQueueSplice();
damage.value = Math.min(damage.value, this.hp);
this.damage(damage.value);
source.turnData.damageDealt += damage.value;
this.turnData.attacksReceived.unshift({ move: move.id, result: result as DamageResult, damage: damage.value, critical: isCritical, sourceId: source.id });
if (source.isPlayer() && !this.isPlayer())
this.scene.applyModifiers(DamageMoneyRewardModifier, true, source, damage)
2023-04-26 20:33:13 -07:00
}
2023-10-30 09:33:20 -07:00
if (source.turnData.hitsLeft === 1) {
switch (result) {
case HitResult.SUPER_EFFECTIVE:
this.scene.queueMessage('It\'s super effective!');
break;
case HitResult.NOT_VERY_EFFECTIVE:
this.scene.queueMessage('It\'s not very effective!');
break;
case HitResult.NO_EFFECT:
this.scene.queueMessage(`It doesn\'t affect ${this.name}!`);
break;
2023-11-07 19:23:42 -08:00
case HitResult.ONE_HIT_KO:
this.scene.queueMessage('It\'s a one-hit KO!');
break;
2023-10-30 09:33:20 -07:00
}
2023-04-26 20:33:13 -07:00
}
if (damage)
this.scene.clearPhaseQueueSplice();
2023-04-14 22:32:16 -07:00
}
break;
case MoveCategory.STATUS:
if (!typeless)
applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier);
result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS;
2023-04-14 22:32:16 -07:00
break;
}
return result;
2023-04-13 09:16:36 -07:00
}
2023-04-23 18:31:06 -07:00
damage(damage: integer, preventEndure?: boolean): void {
if (this.isFainted())
2023-04-16 15:40:32 -07:00
return;
2023-04-23 18:31:06 -07: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 15:40:32 -07:00
this.hp = Math.max(this.hp - damage, 0);
if (this.isFainted()) {
this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), preventEndure));
2023-04-20 18:32:48 -07:00
this.resetSummonData();
2023-04-16 15:40:32 -07:00
}
}
2023-10-21 17:52:19 -07:00
heal(amount: integer): void {
this.hp = Math.min(this.hp + amount, this.getMaxHp());
}
addTag(tagType: BattlerTagType, turnCount?: integer, sourceMove?: Moves, sourceId?: integer): boolean {
2023-04-14 22:32:16 -07:00
const existingTag = this.getTag(tagType);
if (existingTag) {
existingTag.onOverlap(this);
2023-04-13 09:16:36 -07:00
return false;
2023-04-14 22:32:16 -07:00
}
2023-04-13 09:16:36 -07:00
const newTag = getBattlerTag(tagType, turnCount || 0, sourceMove, sourceId);
2023-04-21 16:30:04 -07:00
const cancelled = new Utils.BooleanHolder(false);
applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled);
if (!cancelled.value && newTag.canAdd(this)) {
2023-04-22 19:14:53 -07:00
this.summonData.tags.push(newTag);
newTag.onAdd(this);
return true;
}
return false;
2023-04-13 09:16:36 -07:00
}
2023-04-21 16:30:04 -07:00
getTag(tagType: BattlerTagType | { new(...args: any[]): BattlerTag }): BattlerTag {
if (!this.summonData)
return null;
2023-04-14 22:32:16 -07:00
return typeof(tagType) === 'number'
? this.summonData.tags.find(t => t.tagType === tagType)
: this.summonData.tags.find(t => t instanceof tagType);
2023-04-13 09:16:36 -07:00
}
2023-04-21 16:30:04 -07:00
findTag(tagFilter: ((tag: BattlerTag) => boolean)) {
if (!this.summonData)
return null;
2023-04-14 22:32:16 -07:00
return this.summonData.tags.find(t => tagFilter(t));
2023-04-13 09:16:36 -07:00
}
2023-04-21 16:30:04 -07:00
getTags(tagType: BattlerTagType | { new(...args: any[]): BattlerTag }): BattlerTag[] {
if (!this.summonData)
return [];
2023-04-14 22:32:16 -07:00
return typeof(tagType) === 'number'
? this.summonData.tags.filter(t => t.tagType === tagType)
: this.summonData.tags.filter(t => t instanceof tagType);
}
findTags(tagFilter: ((tag: BattlerTag) => boolean)): BattlerTag[] {
if (!this.summonData)
return [];
2023-04-14 22:32:16 -07:00
return this.summonData.tags.filter(t => tagFilter(t));
}
2023-04-21 16:30:04 -07:00
lapseTag(tagType: BattlerTagType): boolean {
2023-04-13 09:16:36 -07:00
const tags = this.summonData.tags;
2023-04-14 22:32:16 -07:00
const tag = tags.find(t => t.tagType === tagType);
2023-04-21 16:30:04 -07:00
if (tag && !(tag.lapse(this, BattlerTagLapseType.CUSTOM))) {
2023-04-14 22:32:16 -07:00
tag.onRemove(this);
tags.splice(tags.indexOf(tag), 1);
}
2023-04-18 09:30:47 -07:00
return !!tag;
2023-04-14 22:32:16 -07:00
}
2023-04-21 16:30:04 -07:00
lapseTags(lapseType: BattlerTagLapseType): void {
2023-04-14 22:32:16 -07:00
const tags = this.summonData.tags;
2023-04-21 16:30:04 -07: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-14 22:32:16 -07:00
t.onRemove(this);
tags.splice(tags.indexOf(t), 1);
});
2023-04-13 09:16:36 -07:00
}
2023-04-22 19:14:53 -07: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-28 16:26:41 -07: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-20 18:32:48 -07:00
getMoveHistory(): TurnMove[] {
2023-04-28 12:03:42 -07:00
return this.battleSummonData.moveHistory;
2023-04-20 18:32:48 -07:00
}
2023-04-24 22:32:48 -07:00
pushMoveHistory(turnMove: TurnMove) {
turnMove.turn = this.scene.currentBattle?.turn;
this.getMoveHistory().push(turnMove);
}
2023-04-13 09:16:36 -07:00
getLastXMoves(turnCount?: integer): TurnMove[] {
2023-04-20 18:32:48 -07:00
const moveHistory = this.getMoveHistory();
2023-04-18 09:30:47 -07:00
return moveHistory.slice(turnCount >= 0 ? Math.max(moveHistory.length - (turnCount || 1), 0) : 0, moveHistory.length).reverse();
2023-03-28 11:54:52 -07:00
}
2023-04-20 18:32:48 -07:00
getMoveQueue(): QueuedMove[] {
return this.summonData.moveQueue;
}
2023-11-03 21:32:12 -07:00
cry(soundConfig?: Phaser.Types.Sound.SoundConfig): AnySound {
const cry = this.getSpeciesForm().cry(this.scene, soundConfig);
let duration = cry.totalDuration * 1000;
if (this.fusionSpecies) {
let fusionCry = this.getFusionSpeciesForm().cry(this.scene, soundConfig, true);
duration = Math.min(duration, fusionCry.totalDuration * 1000);
fusionCry.destroy();
this.scene.time.delayedCall(Utils.fixedInt(Math.ceil(duration * 0.4)), () => {
try {
SoundFade.fadeOut(this.scene, cry, Utils.fixedInt(Math.ceil(duration * 0.2)));
fusionCry = this.getFusionSpeciesForm().cry(this.scene, Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig));
SoundFade.fadeIn(this.scene, fusionCry, Utils.fixedInt(Math.ceil(duration * 0.2)), this.scene.masterVolume * this.scene.seVolume, 0);
} catch (err) {
console.error(err);
}
});
}
return cry;
2023-03-28 11:54:52 -07:00
}
2023-11-03 21:32:12 -07:00
faintCry(callback: Function): void {
if (this.fusionSpecies)
return this.fusionFaintCry(callback);
2023-11-05 19:32:08 -08:00
const key = this.getSpeciesForm().getCryKey(this.formIndex);
2023-03-28 11:54:52 -07:00
let i = 0;
let rate = 0.85;
2023-11-03 21:32:12 -07:00
const cry = this.scene.playSound(key, { rate: rate }) as AnySound;
2023-03-28 11:54:52 -07:00
const sprite = this.getSprite();
2023-04-03 17:47:41 -07:00
const tintSprite = this.getTintSprite();
2023-11-03 21:32:12 -07:00
2023-03-28 11:54:52 -07:00
const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25);
2023-11-03 21:32:12 -07:00
2023-03-28 11:54:52 -07:00
let frameProgress = 0;
let frameThreshold: number;
2023-11-03 21:32:12 -07:00
2023-03-28 11:54:52 -07:00
sprite.anims.pause();
2023-04-03 17:47:41 -07:00
tintSprite.anims.pause();
2023-11-03 21:32:12 -07:00
2023-03-28 11:54:52 -07:00
let faintCryTimer = this.scene.time.addEvent({
2023-10-26 13:33:59 -07:00
delay: Utils.fixedInt(delay),
2023-03-28 11:54:52 -07:00
repeat: -1,
callback: () => {
++i;
frameThreshold = sprite.anims.msPerFrame / rate;
frameProgress += delay;
while (frameProgress > frameThreshold) {
2023-04-03 17:47:41 -07:00
if (sprite.anims.duration) {
2023-03-28 11:54:52 -07:00
sprite.anims.nextFrame();
2023-04-03 17:47:41 -07:00
tintSprite.anims.nextFrame();
}
2023-03-28 11:54:52 -07:00
frameProgress -= frameThreshold;
}
2023-11-03 21:32:12 -07:00
if (cry && !cry.pendingRemove) {
2023-03-28 11:54:52 -07:00
rate *= 0.99;
2023-11-03 21:32:12 -07:00
cry.setRate(rate);
2023-11-10 13:41:02 -08:00
} else {
2023-03-28 11:54:52 -07:00
faintCryTimer.destroy();
faintCryTimer = null;
if (callback)
callback();
}
}
});
2023-11-03 21:32:12 -07:00
2023-03-28 11:54:52 -07:00
// Failsafe
2023-10-26 13:33:59 -07:00
this.scene.time.delayedCall(Utils.fixedInt(3000), () => {
2023-03-28 11:54:52 -07:00
if (!faintCryTimer || !this.scene)
return;
2023-11-03 21:32:12 -07:00
if (cry?.isPlaying)
cry.stop();
faintCryTimer.destroy();
if (callback)
callback();
});
}
private fusionFaintCry(callback: Function): void {
const key = this.getSpeciesForm().getCryKey();
let i = 0;
let rate = 0.85;
let cry = this.scene.playSound(key, { rate: rate }) as AnySound;
const sprite = this.getSprite();
const tintSprite = this.getTintSprite();
let duration = cry.totalDuration * 1000;
let fusionCry = this.scene.playSound(this.getFusionSpeciesForm().getCryKey(this.fusionFormIndex), { rate: rate }) as AnySound;
fusionCry.stop();
duration = Math.min(duration, fusionCry.totalDuration * 1000);
fusionCry.destroy();
const delay = Math.max(duration * 0.05, 25);
let transitionIndex = 0;
let durationProgress = 0;
const transitionThreshold = Math.ceil(duration * 0.4);
while (durationProgress < transitionThreshold) {
++i;
durationProgress += delay * rate;
rate *= 0.99;
}
transitionIndex = i;
i = 0;
rate = 0.85;
let frameProgress = 0;
let frameThreshold: number;
sprite.anims.pause();
tintSprite.anims.pause();
let faintCryTimer = this.scene.time.addEvent({
delay: Utils.fixedInt(delay),
repeat: -1,
callback: () => {
++i;
frameThreshold = sprite.anims.msPerFrame / rate;
frameProgress += delay;
while (frameProgress > frameThreshold) {
if (sprite.anims.duration) {
sprite.anims.nextFrame();
tintSprite.anims.nextFrame();
}
frameProgress -= frameThreshold;
}
if (i === transitionIndex) {
SoundFade.fadeOut(this.scene, cry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)));
fusionCry = this.scene.playSound(this.getFusionSpeciesForm().getCryKey(this.fusionFormIndex), Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0), rate: rate }));
SoundFade.fadeIn(this.scene, fusionCry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), this.scene.masterVolume * this.scene.seVolume, 0);
}
rate *= 0.99;
if (cry && !cry.pendingRemove)
cry.setRate(rate);
if (fusionCry && !fusionCry.pendingRemove)
fusionCry.setRate(rate);
if ((!cry || cry.pendingRemove) && (!fusionCry || fusionCry.pendingRemove)) {
faintCryTimer.destroy();
faintCryTimer = null;
if (callback)
callback();
}
}
});
// Failsafe
this.scene.time.delayedCall(Utils.fixedInt(3000), () => {
if (!faintCryTimer || !this.scene)
return;
if (cry?.isPlaying)
cry.stop();
if (fusionCry?.isPlaying)
fusionCry.stop();
2023-03-28 11:54:52 -07:00
faintCryTimer.destroy();
if (callback)
callback();
});
}
isOppositeGender(pokemon: Pokemon): boolean {
return this.gender !== Gender.GENDERLESS && pokemon.gender === (this.gender === Gender.MALE ? Gender.FEMALE : Gender.MALE);
}
2023-04-11 16:08:03 -07:00
trySetStatus(effect: StatusEffect): boolean {
if (this.status && effect !== StatusEffect.FAINT)
2023-04-11 16:08:03 -07:00
return false;
switch (effect) {
case StatusEffect.POISON:
case StatusEffect.TOXIC:
2023-04-22 19:14:53 -07:00
if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL))
2023-04-11 16:08:03 -07:00
return false;
break;
case StatusEffect.FREEZE:
2023-04-22 19:14:53 -07:00
if (this.isOfType(Type.ICE))
2023-04-11 16:08:03 -07:00
return false;
break;
case StatusEffect.BURN:
2023-04-22 19:14:53 -07:00
if (this.isOfType(Type.FIRE))
2023-04-11 16:08:03 -07:00
return false;
break;
}
const cancelled = new Utils.BooleanHolder(false);
applyPreSetStatusAbAttrs(StatusEffectImmunityAbAttr, this, effect, cancelled);
if (cancelled.value)
return false;
if (effect === StatusEffect.SLEEP)
this.setFrameRate(4);
2023-04-11 16:08:03 -07:00
this.status = new Status(effect);
return true;
}
resetStatus(): void {
const lastStatus = this.status?.effect;
2023-04-11 16:08:03 -07:00
this.status = undefined;
2023-04-16 15:40:32 -07:00
if (lastStatus === StatusEffect.SLEEP) {
this.setFrameRate(12);
2023-04-21 16:30:04 -07:00
if (this.getTag(BattlerTagType.NIGHTMARE))
this.lapseTag(BattlerTagType.NIGHTMARE);
2023-04-16 15:40:32 -07:00
}
2023-04-11 16:08:03 -07:00
}
resetSummonData(): void {
2023-04-03 20:38:31 -07:00
this.summonData = new PokemonSummonData();
this.resetBattleSummonData();
}
2023-04-11 16:08:03 -07:00
resetBattleSummonData(): void {
2023-04-03 20:38:31 -07:00
this.battleSummonData = new PokemonBattleSummonData();
2023-04-21 16:30:04 -07:00
if (this.getTag(BattlerTagType.SEEDED))
this.lapseTag(BattlerTagType.SEEDED);
2023-04-03 20:38:31 -07:00
}
2023-04-11 16:08:03 -07:00
resetTurnData(): void {
2023-04-03 20:38:31 -07:00
this.turnData = new PokemonTurnData();
}
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
return ((this.getSpeciesForm().baseExp * this.level) / 5 + 1);
2023-03-28 11:54:52 -07:00
}
setFrameRate(frameRate: integer) {
this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate;
this.getSprite().play(this.getBattleSpriteKey());
this.getTintSprite().play(this.getBattleSpriteKey());
}
2023-03-28 11:54:52 -07: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-10 20:15:06 -07:00
onComplete: () => {
tintSprite.setVisible(false);
tintSprite.setAlpha(1);
}
2023-03-28 11:54:52 -07:00
});
2023-04-10 20:15:06 -07:00
} else {
2023-03-28 11:54:52 -07:00
tintSprite.setVisible(false);
2023-04-10 20:15:06 -07: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 11:54:52 -07:00
}
sparkle(): void {
if (this.shinySparkle) {
this.shinySparkle.play('sparkle');
2023-10-21 05:58:39 -07:00
this.scene.playSound('sparkle');
2023-03-28 11:54:52 -07:00
}
}
destroy(): void {
this.battleInfo.destroy();
super.destroy();
}
2023-03-28 11:54:52 -07:00
}
2023-04-14 15:21:33 -07:00
export default interface Pokemon {
scene: BattleScene
}
2023-03-28 11:54:52 -07:00
export class PlayerPokemon extends Pokemon {
2023-04-23 15:40:21 -07:00
public metBiome: Biome;
public metLevel: integer;
2023-04-07 21:21:44 -07:00
public compatibleTms: Moves[];
2023-04-28 12:03:42 -07:00
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, abilityIndex: integer, formIndex: integer, gender?: Gender, shiny?: boolean, dataSource?: Pokemon | PokemonData) {
2023-04-22 22:03:09 -07:00
super(scene, 106, 148, species, level, abilityIndex, formIndex, gender, shiny, dataSource);
2023-04-25 20:56:38 -07:00
this.metBiome = scene.arena?.biomeType || Biome.TOWN;
2023-04-23 15:40:21 -07:00
this.metLevel = level;
2023-04-07 21:21:44 -07:00
this.generateCompatibleTms();
2023-03-28 11:54:52 -07:00
}
2023-04-07 21:21:44 -07:00
isPlayer(): boolean {
2023-03-28 11:54:52 -07:00
return true;
}
hasTrainer(): boolean {
return true;
}
getFieldIndex(): integer {
return this.scene.getPlayerField().indexOf(this);
}
getBattlerIndex(): BattlerIndex {
return this.getFieldIndex();
}
2023-04-07 21:21:44 -07: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.fusionSpecies && p[0] === this.fusionSpecies.speciesId)) {
2023-04-07 21:21:44 -07:00
this.compatibleTms.push(moveId);
break;
}
} else if (p === this.species.speciesId || (this.fusionSpecies && p === this.fusionSpecies.speciesId)) {
2023-04-07 21:21:44 -07:00
this.compatibleTms.push(moveId);
break;
}
}
}
}
2023-04-10 10:54:06 -07:00
2023-10-31 11:09:33 -07:00
switchOut(batonPass: boolean): Promise<void> {
return new Promise(resolve => {
this.resetTurnData();
this.resetSummonData();
this.hideInfo();
this.setVisible(false);
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => {
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6)
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, this.getFieldIndex(), slotIndex, false, batonPass));
this.scene.ui.setMode(Mode.MESSAGE).then(() => resolve());
}, PartyUiHandler.FilterNonFainted);
});
}
2023-04-10 10:54:06 -07:00
evolve(evolution: SpeciesEvolution): Promise<void> {
return new Promise(resolve => {
2023-04-14 15:21:33 -07:00
this.handleSpecialEvolutions(evolution);
2023-04-10 10:54:06 -07:00
this.species = getPokemonSpecies(evolution.speciesId);
this.name = this.species.name;
2023-10-20 11:19:22 -07:00
const abilityCount = this.getSpeciesForm().getAbilityCount();
2023-04-26 09:50:21 -07:00
if (this.abilityIndex >= abilityCount) // Shouldn't happen
this.abilityIndex = abilityCount - 1;
this.getSpeciesForm().generateIconAnim(this.scene, this.gender === Gender.FEMALE, this.formIndex);
2023-04-10 10:54:06 -07:00
this.compatibleTms.splice(0, this.compatibleTms.length);
this.generateCompatibleTms();
2023-04-17 22:32:26 -07:00
this.scene.gameData.setPokemonSeen(this);
this.scene.gameData.setPokemonCaught(this);
2023-04-10 10:54:06 -07:00
this.loadAssets().then(() => {
this.calculateStats();
this.updateInfo().then(() => resolve());
});
});
}
2023-04-14 15:21:33 -07: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-22 22:03:09 -07:00
const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny);
2023-04-14 15:21:33 -07:00
this.scene.getParty().push(newPokemon);
newPokemon.evolve(newEvolution);
2023-10-24 15:44:38 -07:00
const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === this.id, true) as PokemonHeldItemModifier[];
modifiers.forEach(m => {
const clonedModifier = m.clone() as PokemonHeldItemModifier;
clonedModifier.pokemonId = newPokemon.id;
this.scene.addModifier(clonedModifier, true);
2023-10-24 15:44:38 -07:00
});
this.scene.updateModifiers(true);
2023-04-14 15:21:33 -07:00
}
}
}
2023-11-03 21:32:12 -07:00
isFusion(): boolean {
return !!(this.fusionSpecies || (this.species.speciesId === Species.KYUREM && this.formIndex));
}
fuse(pokemon: PlayerPokemon): Promise<void> {
return new Promise(resolve => {
if (this.species.speciesId === Species.KYUREM && (pokemon.species.speciesId === Species.RESHIRAM || pokemon.species.speciesId === Species.ZEKROM))
this.formIndex = pokemon.species.speciesId === Species.RESHIRAM ? 1 : 2;
else {
this.fusionSpecies = pokemon.species;
this.fusionFormIndex = pokemon.formIndex;
this.fusionAbilityIndex = pokemon.abilityIndex;
this.fusionShiny = pokemon.shiny;
this.fusionGender = pokemon.gender;
}
this.calculateStats();
this.generateCompatibleTms();
2023-11-03 21:32:12 -07:00
this.updateInfo(true).then(() => {
const fusedPartyMemberIndex = this.scene.getParty().indexOf(pokemon);
const fusedPartyMemberHeldModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === pokemon.id, true) as PokemonHeldItemModifier[];
const transferModifiers: Promise<boolean>[] = [];
for (let modifier of fusedPartyMemberHeldModifiers)
transferModifiers.push(this.scene.tryTransferHeldItemModifier(modifier, this, true, false));
Promise.allSettled(transferModifiers).then(() => {
this.scene.removePartyMemberModifiers(fusedPartyMemberIndex);
this.scene.getParty().splice(fusedPartyMemberIndex, 1)[0];
pokemon.destroy();
resolve();
});
});
});
}
unfuse(): Promise<void> {
return new Promise(resolve => {
this.fusionSpecies = undefined;
this.fusionFormIndex = 0;
this.fusionAbilityIndex = 0;
this.fusionShiny = false;
this.fusionGender = 0;
this.calculateStats();
this.generateCompatibleTms();
2023-11-03 21:32:12 -07:00
this.updateInfo(true).then(() => resolve());
});
}
2023-03-28 11:54:52 -07:00
}
export class EnemyPokemon extends Pokemon {
public trainer: boolean;
2023-03-28 11:54:52 -07:00
public aiType: AiType;
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainer: boolean, dataSource?: PokemonData) {
2023-10-30 11:57:23 -07:00
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource ? dataSource.formIndex : scene.getSpeciesFormIndex(species),
2023-04-28 12:03:42 -07:00
dataSource?.gender, dataSource?.shiny, dataSource);
this.trainer = trainer;
2023-04-28 12:03:42 -07:00
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 12:07:10 -07:00
}
2023-03-28 21:31:25 -07:00
this.aiType = AiType.SMART_RANDOM;
2023-03-28 11:54:52 -07:00
}
2023-04-28 22:40:24 -07: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;
}
}
getNextMove(): QueuedMove {
2023-04-20 18:32:48 -07:00
const queuedMove = this.getMoveQueue().length
? this.getMoveset().find(m => m.moveId === this.getMoveQueue()[0].move)
2023-04-13 09:16:36 -07:00
: null;
2023-04-19 15:19:55 -07:00
if (queuedMove) {
2023-10-25 06:41:37 -07:00
if (queuedMove.isUsable(this, this.getMoveQueue()[0].ignorePP))
return { move: queuedMove.moveId, targets: this.getMoveQueue()[0].targets, ignorePP: this.getMoveQueue()[0].ignorePP };
2023-04-19 15:19:55 -07:00
else {
2023-04-20 18:32:48 -07:00
this.getMoveQueue().shift();
2023-04-19 15:19:55 -07:00
return this.getNextMove();
}
}
2023-04-13 09:16:36 -07:00
2023-10-25 06:41:37 -07:00
const movePool = this.getMoveset().filter(m => m.isUsable(this));
2023-03-28 11:54:52 -07:00
if (movePool.length) {
if (movePool.length === 1)
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
2023-03-28 11:54:52 -07:00
switch (this.aiType) {
case AiType.RANDOM:
const moveId = movePool[Utils.randInt(movePool.length)].moveId;
return { move: moveId, targets: this.getNextTargets(moveId) };
2023-03-28 21:31:25 -07:00
case AiType.SMART_RANDOM:
2023-03-28 11:54:52 -07:00
case AiType.SMART:
const moveScores = movePool.map(() => 0);
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
2023-03-28 11:54:52 -07:00
for (let m in movePool) {
const pokemonMove = movePool[m];
const move = pokemonMove.getMove();
2023-04-10 20:15:06 -07:00
let moveScore = moveScores[m];
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-10 20:15:06 -07:00
}
moveScore /= moveTargets[move.id].length
2023-04-10 20:15:06 -07:00
// could make smarter by checking opponent def/spdef
moveScores[m] = moveScore;
2023-03-28 11:54:52 -07:00
}
2023-03-28 21:31:25 -07:00
console.log(moveScores);
2023-03-28 11:54:52 -07: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-28 21:31:25 -07:00
if (this.aiType === AiType.SMART_RANDOM) {
2023-04-10 04:59:00 -07:00
while (r < sortedMovePool.length - 1 && Utils.randInt(8) >= 5)
2023-03-28 21:31:25 -07:00
r++;
}
2023-03-28 11:54:52 -07:00
console.log(movePool.map(m => m.getName()), moveScores, r, sortedMovePool.map(m => m.getName()));
return { move: sortedMovePool[r].moveId, targets: moveTargets[sortedMovePool[r].moveId] };
2023-03-28 11:54:52 -07:00
}
}
2023-04-19 15:19:55 -07: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(true) && moveTargets.targets.indexOf(p.getBattlerIndex()) > -1);
if (moveTargets.multiple)
return targets.map(p => p.getBattlerIndex());
const move = allMoves[moveId];
const 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;
});
2023-05-23 07:44:33 -07:00
if (!sortedBenefitScores.length)
return [];
let targetWeights = sortedBenefitScores.map(s => s[1]);
const lowestWeight = targetWeights[targetWeights.length - 1];
if (lowestWeight < 1) {
for (let w = 0; w < targetWeights.length; w++)
targetWeights[w] += Math.abs(lowestWeight - 1);
}
const benefitCutoffIndex = targetWeights.findIndex(s => s < targetWeights[0] / 2);
if (benefitCutoffIndex > -1)
targetWeights = targetWeights.slice(0, benefitCutoffIndex);
const thresholds: integer[] = [];
let totalWeight: integer;
targetWeights.reduce((total: integer, w: integer) => {
total += w;
thresholds.push(total);
totalWeight = total;
return total;
}, 0);
const randValue = Utils.randInt(totalWeight);
let targetIndex: integer;
thresholds.every((t, i) => {
if (randValue >= t)
return true;
targetIndex = i;
return false;
});
return [ sortedBenefitScores[targetIndex][0] ];
2023-03-28 11:54:52 -07:00
}
isPlayer() {
return false;
}
hasTrainer(): boolean {
return this.trainer;
}
getFieldIndex(): integer {
return this.scene.getEnemyField().indexOf(this);
}
getBattlerIndex(): BattlerIndex {
return BattlerIndex.ENEMY + this.getFieldIndex();
}
2023-03-28 11:54:52 -07:00
addToParty() {
2023-04-14 15:21:33 -07:00
const party = this.scene.getParty();
2023-03-31 17:19:57 -07:00
let ret: PlayerPokemon = null;
2023-03-28 11:54:52 -07:00
2023-03-31 17:19:57 -07:00
if (party.length < 6) {
2023-04-22 22:03:09 -07:00
const newPokemon = new PlayerPokemon(this.scene, this.species, this.level, this.abilityIndex, this.formIndex, this.gender, this.shiny, this);
2023-03-31 17:19:57 -07:00
party.push(newPokemon);
ret = newPokemon;
}
2023-03-31 19:31:20 -07:00
2023-03-31 17:19:57 -07:00
return ret;
2023-03-28 11:54:52 -07:00
}
}
2023-04-13 09:16:36 -07:00
export interface TurnMove {
move: Moves;
targets?: BattlerIndex[];
2023-04-13 09:16:36 -07:00
result: MoveResult;
2023-04-19 15:19:55 -07:00
virtual?: boolean;
2023-04-24 22:32:48 -07:00
turn?: integer;
2023-04-13 09:16:36 -07:00
}
export interface QueuedMove {
move: Moves;
targets: BattlerIndex[];
2023-04-13 09:16:36 -07:00
ignorePP?: boolean;
}
export interface AttackMoveResult {
move: Moves;
result: DamageResult;
damage: integer;
2023-10-26 21:12:53 -07:00
critical: boolean;
sourceId: integer;
}
2023-04-03 20:38:31 -07:00
export class PokemonSummonData {
2023-04-10 20:15:06 -07:00
public battleStats: integer[] = [ 0, 0, 0, 0, 0, 0, 0 ];
2023-04-13 09:16:36 -07:00
public moveQueue: QueuedMove[] = [];
2023-10-25 06:41:37 -07:00
public disabledMove: Moves = Moves.NONE;
public disabledTurns: integer = 0;
2023-04-21 16:30:04 -07:00
public tags: BattlerTag[] = [];
public speciesForm: PokemonSpeciesForm;
public gender: Gender;
public stats: integer[];
public moveset: PokemonMove[];
2023-04-21 16:30:04 -07:00
public types: Type[];
2023-04-03 20:38:31 -07:00
}
export class PokemonBattleSummonData {
public turnCount: integer = 1;
2023-04-28 12:03:42 -07:00
public moveHistory: TurnMove[] = [];
2023-04-03 20:38:31 -07:00
}
export class PokemonTurnData {
public flinched: boolean;
2023-04-10 13:17:25 -07:00
public hitCount: integer;
2023-04-03 20:38:31 -07:00
public hitsLeft: integer;
2023-04-13 22:08:44 -07:00
public damageDealt: integer = 0;
public attacksReceived: AttackMoveResult[] = [];
2023-04-03 20:38:31 -07:00
}
2023-03-28 21:31:25 -07:00
export enum AiType {
2023-03-28 11:54:52 -07:00
RANDOM,
2023-03-28 21:31:25 -07:00
SMART_RANDOM,
2023-03-28 11:54:52 -07:00
SMART
2023-04-28 22:40:24 -07:00
}
2023-03-28 11:54:52 -07:00
export enum MoveResult {
PENDING,
SUCCESS,
FAIL,
MISS,
OTHER
}
export enum HitResult {
EFFECTIVE = 1,
2023-03-28 11:54:52 -07:00
SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE,
NO_EFFECT,
2023-11-07 19:23:42 -08:00
ONE_HIT_KO,
2023-04-13 09:16:36 -07:00
STATUS,
FAIL,
MISS,
2023-03-28 11:54:52 -07:00
OTHER
2023-04-28 22:40:24 -07:00
}
2023-03-28 11:54:52 -07:00
2023-11-07 19:23:42 -08:00
export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | HitResult.NOT_VERY_EFFECTIVE | HitResult.ONE_HIT_KO | HitResult.OTHER;
2023-04-14 22:32:16 -07:00
2023-03-28 11:54:52 -07:00
export class PokemonMove {
2023-04-13 09:16:36 -07:00
public moveId: Moves;
2023-03-28 11:54:52 -07:00
public ppUsed: integer;
public ppUp: integer;
2023-04-19 15:19:55 -07:00
public virtual: boolean;
2023-03-28 11:54:52 -07:00
2023-04-19 15:19:55 -07:00
constructor(moveId: Moves, ppUsed?: integer, ppUp?: integer, virtual?: boolean) {
2023-03-28 11:54:52 -07:00
this.moveId = moveId;
2023-04-13 20:04:51 -07:00
this.ppUsed = ppUsed || 0;
this.ppUp = ppUp || 0;
2023-04-19 15:19:55 -07:00
this.virtual = !!virtual;
2023-03-28 11:54:52 -07:00
}
2023-10-25 06:41:37 -07:00
isUsable(pokemon: Pokemon, ignorePp?: boolean): boolean {
if (pokemon.summonData?.disabledMove === this.moveId)
2023-03-28 11:54:52 -07:00
return false;
2023-04-19 15:19:55 -07:00
return ignorePp || this.ppUsed < this.getMove().pp + this.ppUp || this.getMove().pp === -1;
}
2023-03-28 11:54:52 -07:00
getMove(): Move {
2023-04-20 18:32:48 -07:00
return allMoves[this.moveId];
2023-03-28 11:54:52 -07:00
}
2023-04-27 21:25:33 -07:00
getPpRatio(): number {
return 1 - (this.ppUsed / (this.getMove().pp + this.ppUp));
}
2023-03-28 11:54:52 -07:00
getName(): string {
return this.getMove().name;
2023-03-28 11:54:52 -07:00
}
}