Merge branch 'beta' into evil-team-monogen

This commit is contained in:
Madmadness65 2024-11-03 21:51:14 -06:00 committed by GitHub
commit c4232bca12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 459 additions and 395 deletions

View File

@ -13,6 +13,7 @@ import { Arena, ArenaBase } from "#app/field/arena";
import { GameData } from "#app/system/game-data";
import { addTextObject, getTextColor, TextStyle } from "#app/ui/text";
import { allMoves } from "#app/data/move";
import { MusicPreference } from "#app/system/settings/settings";
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import AbilityBar from "#app/ui/ability-bar";
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, applyPostItemLostAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr, PostItemLostAbAttr } from "#app/data/ability";
@ -169,7 +170,7 @@ export default class BattleScene extends SceneBase {
public uiTheme: UiTheme = UiTheme.DEFAULT;
public windowType: integer = 0;
public experimentalSprites: boolean = false;
public musicPreference: integer = 0;
public musicPreference: number = MusicPreference.MIXED;
public moveAnimations: boolean = true;
public expGainsSpeed: ExpGainsSpeed = ExpGainsSpeed.DEFAULT;
public skipSeenDialogues: boolean = false;
@ -2998,22 +2999,16 @@ export default class BattleScene extends SceneBase {
*/
getActiveKeys(): string[] {
const keys: string[] = [];
const playerParty = this.getPlayerParty();
playerParty.forEach(p => {
let activePokemon: (PlayerPokemon | EnemyPokemon)[] = this.getPlayerParty();
activePokemon = activePokemon.concat(this.getEnemyParty());
activePokemon.forEach((p) => {
keys.push(p.getSpriteKey(true));
if (p instanceof PlayerPokemon) {
keys.push(p.getBattleSpriteKey(true, true));
keys.push("cry/" + p.species.getCryKey(p.formIndex));
if (p.fusionSpecies) {
keys.push("cry/" + p.fusionSpecies.getCryKey(p.fusionFormIndex));
}
});
// enemyParty has to be operated on separately from playerParty because playerPokemon =/= enemyPokemon
const enemyParty = this.getEnemyParty();
enemyParty.forEach(p => {
keys.push(p.getSpriteKey(true));
keys.push("cry/" + p.species.getCryKey(p.formIndex));
keys.push(p.species.getCryKey(p.formIndex));
if (p.fusionSpecies) {
keys.push("cry/" + p.fusionSpecies.getCryKey(p.fusionFormIndex));
keys.push(p.fusionSpecies.getCryKey(p.fusionFormIndex));
}
});
return keys;

View File

@ -6,11 +6,13 @@ import { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball";
import { trainerConfigs } from "#app/data/trainer-config";
import { SpeciesFormKey } from "#enums/species-form-key";
import Pokemon, { EnemyPokemon, PlayerPokemon, QueuedMove } from "#app/field/pokemon";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattleSpec } from "#enums/battle-spec";
import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender";
import { MusicPreference } from "#app/system/settings/settings";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import i18next from "#app/plugins/i18n";
@ -212,7 +214,6 @@ export default class Battle {
}
getBgmOverride(scene: BattleScene): string | null {
const battlers = this.enemyParty.slice(0, this.getBattlerCount());
if (this.isBattleMysteryEncounter() && this.mysteryEncounter?.encounterMode === MysteryEncounterMode.DEFAULT) {
// Music is overridden for MEs during ME onInit()
// Should not use any BGM overrides before swapping from DEFAULT mode
@ -221,7 +222,7 @@ export default class Battle {
if (!this.started && this.trainer?.config.encounterBgm && this.trainer?.getEncounterMessages()?.length) {
return `encounter_${this.trainer?.getEncounterBgm()}`;
}
if (scene.musicPreference === 0) {
if (scene.musicPreference === MusicPreference.CONSISTENT) {
return this.trainer?.getBattleBgm() ?? null;
} else {
return this.trainer?.getMixedBattleBgm() ?? null;
@ -229,143 +230,163 @@ export default class Battle {
} else if (this.gameMode.isClassic && this.waveIndex > 195 && this.battleSpec !== BattleSpec.FINAL_BOSS) {
return "end_summit";
}
for (const pokemon of battlers) {
const wildOpponents = scene.getEnemyParty();
for (const pokemon of wildOpponents) {
if (this.battleSpec === BattleSpec.FINAL_BOSS) {
if (pokemon.formIndex) {
if (pokemon.species.getFormSpriteKey(pokemon.formIndex) === SpeciesFormKey.ETERNAMAX) {
return "battle_final";
}
return "battle_final_encounter";
}
if (pokemon.species.legendary || pokemon.species.subLegendary || pokemon.species.mythical) {
if (scene.musicPreference === 0) {
if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) {
if (scene.musicPreference === MusicPreference.CONSISTENT) {
switch (pokemon.species.speciesId) {
case Species.REGIROCK:
case Species.REGICE:
case Species.REGISTEEL:
case Species.REGIGIGAS:
case Species.REGIDRAGO:
case Species.REGIELEKI:
return "battle_legendary_regis_g5";
}
if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
return "battle_legendary_unova";
}
if (pokemon.species.speciesId === Species.KYUREM) {
case Species.KYUREM:
return "battle_legendary_kyurem";
}
default:
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
return "battle_legendary_unova";
} else {
if (pokemon.species.speciesId === Species.ARTICUNO || pokemon.species.speciesId === Species.ZAPDOS || pokemon.species.speciesId === Species.MOLTRES || pokemon.species.speciesId === Species.MEWTWO || pokemon.species.speciesId === Species.MEW) {
}
} else if (scene.musicPreference === MusicPreference.MIXED) {
switch (pokemon.species.speciesId) {
case Species.ARTICUNO:
case Species.ZAPDOS:
case Species.MOLTRES:
case Species.MEWTWO:
case Species.MEW:
return "battle_legendary_kanto";
}
if (pokemon.species.speciesId === Species.RAIKOU) {
case Species.RAIKOU:
return "battle_legendary_raikou";
}
if (pokemon.species.speciesId === Species.ENTEI) {
case Species.ENTEI:
return "battle_legendary_entei";
}
if (pokemon.species.speciesId === Species.SUICUNE) {
case Species.SUICUNE:
return "battle_legendary_suicune";
}
if (pokemon.species.speciesId === Species.LUGIA) {
case Species.LUGIA:
return "battle_legendary_lugia";
}
if (pokemon.species.speciesId === Species.HO_OH) {
case Species.HO_OH:
return "battle_legendary_ho_oh";
}
if (pokemon.species.speciesId === Species.REGIROCK || pokemon.species.speciesId === Species.REGICE || pokemon.species.speciesId === Species.REGISTEEL || pokemon.species.speciesId === Species.REGIGIGAS || pokemon.species.speciesId === Species.REGIELEKI || pokemon.species.speciesId === Species.REGIDRAGO) {
case Species.REGIROCK:
case Species.REGICE:
case Species.REGISTEEL:
case Species.REGIGIGAS:
case Species.REGIDRAGO:
case Species.REGIELEKI:
return "battle_legendary_regis_g6";
}
if (pokemon.species.speciesId === Species.GROUDON || pokemon.species.speciesId === Species.KYOGRE) {
case Species.GROUDON:
case Species.KYOGRE:
return "battle_legendary_gro_kyo";
}
if (pokemon.species.speciesId === Species.RAYQUAZA) {
case Species.RAYQUAZA:
return "battle_legendary_rayquaza";
}
if (pokemon.species.speciesId === Species.DEOXYS) {
case Species.DEOXYS:
return "battle_legendary_deoxys";
}
if (pokemon.species.speciesId === Species.UXIE || pokemon.species.speciesId === Species.MESPRIT || pokemon.species.speciesId === Species.AZELF) {
case Species.UXIE:
case Species.MESPRIT:
case Species.AZELF:
return "battle_legendary_lake_trio";
}
if (pokemon.species.speciesId === Species.HEATRAN || pokemon.species.speciesId === Species.CRESSELIA || pokemon.species.speciesId === Species.DARKRAI || pokemon.species.speciesId === Species.SHAYMIN) {
case Species.HEATRAN:
case Species.CRESSELIA:
case Species.DARKRAI:
case Species.SHAYMIN:
return "battle_legendary_sinnoh";
}
if (pokemon.species.speciesId === Species.DIALGA || pokemon.species.speciesId === Species.PALKIA) {
if (pokemon.getFormKey() === "") {
return "battle_legendary_dia_pal";
}
if (pokemon.getFormKey() === "origin") {
case Species.DIALGA:
case Species.PALKIA:
if (pokemon.species.getFormSpriteKey(pokemon.formIndex) === SpeciesFormKey.ORIGIN) {
return "battle_legendary_origin_forme";
}
}
if (pokemon.species.speciesId === Species.GIRATINA) {
return "battle_legendary_dia_pal";
case Species.GIRATINA:
return "battle_legendary_giratina";
}
if (pokemon.species.speciesId === Species.ARCEUS) {
case Species.ARCEUS:
return "battle_legendary_arceus";
}
if (pokemon.species.speciesId === Species.COBALION || pokemon.species.speciesId === Species.TERRAKION || pokemon.species.speciesId === Species.VIRIZION || pokemon.species.speciesId === Species.TORNADUS || pokemon.species.speciesId === Species.THUNDURUS || pokemon.species.speciesId === Species.LANDORUS || pokemon.species.speciesId === Species.KELDEO || pokemon.species.speciesId === Species.MELOETTA || pokemon.species.speciesId === Species.GENESECT) {
case Species.COBALION:
case Species.TERRAKION:
case Species.VIRIZION:
case Species.KELDEO:
case Species.TORNADUS:
case Species.LANDORUS:
case Species.THUNDURUS:
case Species.MELOETTA:
case Species.GENESECT:
return "battle_legendary_unova";
}
if (pokemon.species.speciesId === Species.KYUREM) {
case Species.KYUREM:
return "battle_legendary_kyurem";
}
if (pokemon.species.speciesId === Species.XERNEAS || pokemon.species.speciesId === Species.YVELTAL || pokemon.species.speciesId === Species.ZYGARDE) {
case Species.XERNEAS:
case Species.YVELTAL:
case Species.ZYGARDE:
return "battle_legendary_xern_yvel";
}
if (pokemon.species.speciesId === Species.TAPU_KOKO || pokemon.species.speciesId === Species.TAPU_LELE || pokemon.species.speciesId === Species.TAPU_BULU || pokemon.species.speciesId === Species.TAPU_FINI) {
case Species.TAPU_KOKO:
case Species.TAPU_LELE:
case Species.TAPU_BULU:
case Species.TAPU_FINI:
return "battle_legendary_tapu";
}
if ([ Species.COSMOG, Species.COSMOEM, Species.SOLGALEO, Species.LUNALA ].includes(pokemon.species.speciesId)) {
case Species.SOLGALEO:
case Species.LUNALA:
return "battle_legendary_sol_lun";
}
if (pokemon.species.speciesId === Species.NECROZMA) {
if (pokemon.getFormKey() === "") {
return "battle_legendary_sol_lun";
}
if (pokemon.getFormKey() === "dusk-mane" || pokemon.getFormKey() === "dawn-wings") {
case Species.NECROZMA:
switch (pokemon.getFormKey()) {
case "dusk-mane":
case "dawn-wings":
return "battle_legendary_dusk_dawn";
}
if (pokemon.getFormKey() === "ultra") {
case "ultra":
return "battle_legendary_ultra_nec";
default:
return "battle_legendary_sol_lun";
}
}
if ([ Species.NIHILEGO, Species.BUZZWOLE, Species.PHEROMOSA, Species.XURKITREE, Species.CELESTEELA, Species.KARTANA, Species.GUZZLORD, Species.POIPOLE, Species.NAGANADEL, Species.STAKATAKA, Species.BLACEPHALON ].includes(pokemon.species.speciesId)) {
case Species.NIHILEGO:
case Species.PHEROMOSA:
case Species.BUZZWOLE:
case Species.XURKITREE:
case Species.CELESTEELA:
case Species.KARTANA:
case Species.GUZZLORD:
case Species.POIPOLE:
case Species.NAGANADEL:
case Species.STAKATAKA:
case Species.BLACEPHALON:
return "battle_legendary_ub";
}
if (pokemon.species.speciesId === Species.ZACIAN || pokemon.species.speciesId === Species.ZAMAZENTA) {
case Species.ZACIAN:
case Species.ZAMAZENTA:
return "battle_legendary_zac_zam";
}
if (pokemon.species.speciesId === Species.GLASTRIER || pokemon.species.speciesId === Species.SPECTRIER) {
case Species.GLASTRIER:
case Species.SPECTRIER:
return "battle_legendary_glas_spec";
}
if (pokemon.species.speciesId === Species.CALYREX) {
if (pokemon.getFormKey() === "") {
return "battle_legendary_calyrex";
}
case Species.CALYREX:
if (pokemon.getFormKey() === "ice" || pokemon.getFormKey() === "shadow") {
return "battle_legendary_riders";
}
}
if (pokemon.species.speciesId === Species.GALAR_ARTICUNO || pokemon.species.speciesId === Species.GALAR_ZAPDOS || pokemon.species.speciesId === Species.GALAR_MOLTRES) {
return "battle_legendary_calyrex";
case Species.GALAR_ARTICUNO:
case Species.GALAR_ZAPDOS:
case Species.GALAR_MOLTRES:
return "battle_legendary_birds_galar";
}
if (pokemon.species.speciesId === Species.WO_CHIEN || pokemon.species.speciesId === Species.CHIEN_PAO || pokemon.species.speciesId === Species.TING_LU || pokemon.species.speciesId === Species.CHI_YU) {
case Species.WO_CHIEN:
case Species.CHIEN_PAO:
case Species.TING_LU:
case Species.CHI_YU:
return "battle_legendary_ruinous";
}
if (pokemon.species.speciesId === Species.KORAIDON || pokemon.species.speciesId === Species.MIRAIDON) {
case Species.KORAIDON:
case Species.MIRAIDON:
return "battle_legendary_kor_mir";
}
if (pokemon.species.speciesId === Species.OKIDOGI || pokemon.species.speciesId === Species.MUNKIDORI || pokemon.species.speciesId === Species.FEZANDIPITI) {
case Species.OKIDOGI:
case Species.MUNKIDORI:
case Species.FEZANDIPITI:
return "battle_legendary_loyal_three";
}
if (pokemon.species.speciesId === Species.OGERPON) {
case Species.OGERPON:
return "battle_legendary_ogerpon";
}
if (pokemon.species.speciesId === Species.TERAPAGOS) {
case Species.TERAPAGOS:
return "battle_legendary_terapagos";
}
if (pokemon.species.speciesId === Species.PECHARUNT) {
case Species.PECHARUNT:
return "battle_legendary_pecharunt";
}
default:
if (pokemon.species.legendary) {
return "battle_legendary_res_zek";
}
@ -373,6 +394,7 @@ export default class Battle {
}
}
}
}
if (scene.gameMode.isClassic && this.waveIndex <= 4) {
return "battle_wild";

View File

@ -313,8 +313,8 @@ export class ConditionalProtectTag extends ArenaTag {
* protection effect.
* @param arena {@linkcode Arena} The arena containing the protection effect
* @param moveId {@linkcode Moves} The move to check against this condition
* @returns `true` if the incoming move's priority is greater than 0. This includes
* moves with modified priorities from abilities (e.g. Prankster)
* @returns `true` if the incoming move's priority is greater than 0.
* This includes moves with modified priorities from abilities (e.g. Prankster)
*/
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
const move = allMoves[moveId];
@ -322,10 +322,12 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
const effectPhase = arena.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
const attacker = effectPhase.getUserPokemon()!;
const attacker = effectPhase.getUserPokemon();
applyMoveAttrs(IncrementMovePriorityAttr, attacker, null, move, priority);
if (attacker) {
applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, false, move, priority);
}
}
return priority.value > 0;
};

View File

@ -2496,7 +2496,10 @@ export class SubstituteTag extends BattlerTag {
onHit(pokemon: Pokemon): void {
const moveEffectPhase = pokemon.scene.getCurrentPhase();
if (moveEffectPhase instanceof MoveEffectPhase) {
const attacker = moveEffectPhase.getUserPokemon()!;
const attacker = moveEffectPhase.getUserPokemon();
if (!attacker) {
return;
}
const move = moveEffectPhase.move.getMove();
const firstHit = (attacker.turnData.hitCount === attacker.turnData.hitsLeft);

View File

@ -1,6 +1,7 @@
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TrainerSlot, } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings";
import { getPlayerModifierTypeOptions, ModifierPoolType, ModifierTypeOption, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
@ -105,7 +106,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
// Load bgm
let bgmKey: string;
if (scene.musicPreference === 0) {
if (scene.musicPreference === MusicPreference.CONSISTENT) {
bgmKey = "mystery_encounter_gen_5_gts";
scene.loadBgm(bgmKey, `${bgmKey}.mp3`);
} else {

View File

@ -460,7 +460,7 @@ export abstract class PokemonSpeciesForm {
break;
}
}
return ret;
return `cry/${ret}`;
}
validateStarterMoveset(moveset: StarterMoveset, eggMoves: number): boolean {
@ -488,7 +488,7 @@ export abstract class PokemonSpeciesForm {
return new Promise(resolve => {
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant);
scene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant));
scene.load.audio(`cry/${this.getCryKey(formIndex)}`, `audio/cry/${this.getCryKey(formIndex)}.m4a`);
scene.load.audio(`${this.getCryKey(formIndex)}`, `audio/${this.getCryKey(formIndex)}.m4a`);
scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
const originalWarn = console.warn;
// Ignore warnings for missing frames, because there will be a lot
@ -546,7 +546,7 @@ export abstract class PokemonSpeciesForm {
if (cry?.pendingRemove) {
cry = null;
}
cry = scene.playSound(`cry/${(cry ?? cryKey)}`, soundConfig);
cry = scene.playSound(cry ?? cryKey, soundConfig);
if (ignorePlay) {
cry.stop();
}

View File

@ -230,7 +230,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (this.variant === undefined) {
this.variant = this.shiny ? this.generateVariant() : 0;
this.variant = this.shiny ? this.generateShinyVariant() : 0;
}
this.customPokemonData = new CustomPokemonData();
@ -1199,7 +1199,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns an array of {@linkcode Moves}, the length of which is determined
* by how many learnable moves there are for the {@linkcode Pokemon}.
*/
getLearnableLevelMoves(): Moves[] {
public getLearnableLevelMoves(): Moves[] {
let levelMoves = this.getLevelMoves(1, true, false, true).map(lm => lm[1]);
if (this.metBiome === -1 && !this.scene.gameMode.isFreshStartChallenge() && !this.scene.gameMode.isDaily) {
levelMoves = this.getUnlockedEggMoves().concat(levelMoves);
@ -1213,13 +1213,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/**
* Gets the types of a pokemon
* @param includeTeraType boolean to include tera-formed type, default false
* @param forDefend boolean if the pokemon is defending from an attack
* @param ignoreOverride boolean if true, ignore ability changing effects
* @param includeTeraType - `true` to include tera-formed type; Default: `false`
* @param forDefend - `true` if the pokemon is defending from an attack; Default: `false`
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false`
* @returns array of {@linkcode Type}
*/
getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] {
const types : Type[] = [];
public getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride: boolean = false): Type[] {
const types: Type[] = [];
if (includeTeraType) {
const teraType = this.getTeraType();
@ -1284,14 +1284,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
// this.scene potentially can be undefined for a fainted pokemon in doubles
// use optional chaining to avoid runtime errors
if (!types.length) { // become UNKNOWN if no types are present
// become UNKNOWN if no types are present
if (!types.length) {
types.push(Type.UNKNOWN);
}
if (types.length > 1 && types.includes(Type.UNKNOWN)) { // remove UNKNOWN if other types are present
// remove UNKNOWN if other types are present
if (types.length > 1 && types.includes(Type.UNKNOWN)) {
const index = types.indexOf(Type.UNKNOWN);
if (index !== -1) {
types.splice(index, 1);
@ -1311,19 +1310,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return types;
}
isOfType(type: Type, includeTeraType: boolean = true, forDefend: boolean = false, ignoreOverride?: boolean): boolean {
return !!this.getTypes(includeTeraType, forDefend, ignoreOverride).some(t => t === type);
/**
* Checks if the pokemon's typing includes the specified type
* @param type - {@linkcode Type} to check
* @param includeTeraType - `true` to include tera-formed type; Default: `true`
* @param forDefend - `true` if the pokemon is defending from an attack; Default: `false`
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false`
* @returns `true` if the Pokemon's type matches
*/
public isOfType(type: Type, includeTeraType: boolean = true, forDefend: boolean = false, ignoreOverride: boolean = false): boolean {
return this.getTypes(includeTeraType, forDefend, ignoreOverride).some((t) => t === type);
}
/**
* Gets the non-passive ability of the pokemon. This accounts for fusions and ability changing effects.
* This should rarely be called, most of the time {@link hasAbility} or {@link hasAbilityWithAttr} are better used as
* This should rarely be called, most of the time {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr} are better used as
* those check both the passive and non-passive abilities and account for ability suppression.
* @see {@link hasAbility} {@link hasAbilityWithAttr} Intended ways to check abilities in most cases
* @param {boolean} ignoreOverride If true, ignore ability changing effects
* @returns {Ability} The non-passive ability of the pokemon
* @see {@linkcode hasAbility} {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases
* @param ignoreOverride - If `true`, ignore ability changing effects; Default: `false`
* @returns The non-passive {@linkcode Ability} of the pokemon
*/
getAbility(ignoreOverride?: boolean): Ability {
public getAbility(ignoreOverride: boolean = false): Ability {
if (!ignoreOverride && this.summonData?.ability) {
return allAbilities[this.summonData.ability];
}
@ -1352,12 +1359,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/**
* Gets the passive ability of the pokemon. This should rarely be called, most of the time
* {@link hasAbility} or {@link hasAbilityWithAttr} are better used as those check both the passive and
* {@linkcode hasAbility} or {@linkcode hasAbilityWithAttr} are better used as those check both the passive and
* non-passive abilities and account for ability suppression.
* @see {@link hasAbility} {@link hasAbilityWithAttr} Intended ways to check abilities in most cases
* @returns {Ability} The passive ability of the pokemon
* @see {@linkcode hasAbility} {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases
* @returns The passive {@linkcode Ability} of the pokemon
*/
getPassiveAbility(): Ability {
public getPassiveAbility(): Ability {
if (Overrides.PASSIVE_ABILITY_OVERRIDE && this.isPlayer()) {
return allAbilities[Overrides.PASSIVE_ABILITY_OVERRIDE];
}
@ -1379,12 +1386,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Gets a list of all instances of a given ability attribute among abilities this pokemon has.
* Accounts for all the various effects which can affect whether an ability will be present or
* in effect, and both passive and non-passive.
* @param attrType {@linkcode AbAttr} The ability attribute to check for.
* @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active
* @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects
* @returns A list of all the ability attributes on this ability.
* @param attrType - {@linkcode AbAttr} The ability attribute to check for.
* @param canApply - If `false`, it doesn't check whether the ability is currently active; Default `true`
* @param ignoreOverride - If `true`, it ignores ability changing effects; Default `false`
* @returns An array of all the ability attributes on this ability.
*/
getAbilityAttrs<T extends AbAttr = AbAttr>(attrType: { new(...args: any[]): T }, canApply: boolean = true, ignoreOverride?: boolean): T[] {
public getAbilityAttrs<T extends AbAttr = AbAttr>(attrType: { new(...args: any[]): T }, canApply: boolean = true, ignoreOverride: boolean = false): T[] {
const abilityAttrs: T[] = [];
if (!canApply || this.canApplyAbility()) {
@ -1403,12 +1410,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* - bought with starter candy
* - set by override
* - is a boss pokemon
* @returns whether or not a pokemon should have a passive
* @returns `true` if the Pokemon has a passive
*/
hasPassive(): boolean {
public hasPassive(): boolean {
// returns override if valid for current case
if ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && this.isPlayer()) ||
(Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) {
if ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && this.isPlayer())
|| (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) {
return true;
}
@ -1427,12 +1434,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/**
* Checks whether an ability of a pokemon can be currently applied. This should rarely be
* directly called, as {@link hasAbility} and {@link hasAbilityWithAttr} already call this.
* @see {@link hasAbility} {@link hasAbilityWithAttr} Intended ways to check abilities in most cases
* @param {boolean} passive If true, check if passive can be applied instead of non-passive
* @returns {Ability} The passive ability of the pokemon
* directly called, as {@linkcode hasAbility} and {@linkcode hasAbilityWithAttr} already call this.
* @see {@linkcode hasAbility} {@linkcode hasAbilityWithAttr} Intended ways to check abilities in most cases
* @param passive If true, check if passive can be applied instead of non-passive
* @returns `true` if the ability can be applied
*/
canApplyAbility(passive: boolean = false): boolean {
public canApplyAbility(passive: boolean = false): boolean {
if (passive && !this.hasPassive()) {
return false;
}
@ -1461,7 +1468,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false;
}
}
return (!!this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this));
return (this.hp > 0 || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this));
}
/**
@ -1473,7 +1480,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param {boolean} ignoreOverride If true, it ignores ability changing effects
* @returns {boolean} Whether the ability is present and active
*/
hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean {
public hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean {
if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
return true;
}
@ -1493,7 +1500,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param {boolean} ignoreOverride If true, it ignores ability changing effects
* @returns {boolean} Whether an ability with that attribute is present and active
*/
hasAbilityWithAttr(attrType: Constructor<AbAttr>, canApply: boolean = true, ignoreOverride?: boolean): boolean {
public hasAbilityWithAttr(attrType: Constructor<AbAttr>, canApply: boolean = true, ignoreOverride?: boolean): boolean {
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).hasAttr(attrType)) {
return true;
}
@ -1508,7 +1515,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal)
* @returns the kg of the Pokemon (minimum of 0.1)
*/
getWeight(): number {
public getWeight(): number {
const autotomizedTag = this.getTag(AutotomizedTag);
let weightRemoved = 0;
if (!Utils.isNullOrUndefined(autotomizedTag)) {
@ -1523,10 +1530,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
/**
* Gets the tera-formed type of the pokemon, or UNKNOWN if not present
* @returns the {@linkcode Type}
* @returns The tera-formed type of the pokemon, or {@linkcode Type.UNKNOWN} if not present
*/
getTeraType(): Type {
public getTeraType(): Type {
// this.scene can be undefined for a fainted mon in doubles
if (this.scene !== undefined) {
const teraModifier = this.scene.findModifier(m => m instanceof TerastallizeModifier
@ -1540,23 +1546,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return Type.UNKNOWN;
}
isTerastallized(): boolean {
public isTerastallized(): boolean {
return this.getTeraType() !== Type.UNKNOWN;
}
isGrounded(): boolean {
public isGrounded(): boolean {
return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.FLOATING) && !this.getTag(SemiInvulnerableTag));
}
/**
* Determines whether this Pokemon is prevented from running or switching due
* to effects from moves and/or abilities.
* @param trappedAbMessages `string[]` If defined, ability trigger messages
* @param trappedAbMessages - If defined, ability trigger messages
* (e.g. from Shadow Tag) are forwarded through this array.
* @param simulated `boolean` if `true`, applies abilities via simulated calls.
* @returns
* @param simulated - If `true`, applies abilities via simulated calls.
* @returns `true` if the pokemon is trapped
*/
isTrapped(trappedAbMessages: string[] = [], simulated: boolean = true): boolean {
public isTrapped(trappedAbMessages: string[] = [], simulated: boolean = true): boolean {
if (this.isOfType(Type.GHOST)) {
return false;
}
@ -1564,7 +1570,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const trappedByAbility = new Utils.BooleanHolder(false);
const opposingField = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
opposingField.forEach(opponent =>
opposingField.forEach((opponent) =>
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated)
);
@ -1575,11 +1581,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/**
* Calculates the type of a move when used by this Pokemon after
* type-changing move and ability attributes have applied.
* @param move {@linkcode Move} The move being used.
* @param simulated If `true`, prevents showing abilities applied in this calculation.
* @returns the {@linkcode Type} of the move after attributes are applied
* @param move - {@linkcode Move} The move being used.
* @param simulated - If `true`, prevents showing abilities applied in this calculation.
* @returns The {@linkcode Type} of the move after attributes are applied
*/
getMoveType(move: Move, simulated: boolean = true): Type {
public getMoveType(move: Move, simulated: boolean = true): Type {
const moveTypeHolder = new Utils.NumberHolder(move.type);
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder);
@ -1944,13 +1950,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* Function that tries to set a Pokemon shiny based on seed.
* For manual use only, usually to roll a Pokemon's shiny chance a second time.
*
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / 65536
* @param thresholdOverride number that is divided by 2^16 (65536) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
* @param applyModifiersToOverride If {@linkcode thresholdOverride} is set and this is true, will apply Shiny Charm and event modifiers to {@linkcode thresholdOverride}
* @returns true if the Pokemon has been set as a shiny, false otherwise
* @returns `true` if the Pokemon has been set as a shiny, `false` otherwise
*/
trySetShinySeed(thresholdOverride?: integer, applyModifiersToOverride?: boolean): boolean {
const shinyThreshold = new Utils.IntegerHolder(BASE_SHINY_CHANCE);
public trySetShinySeed(thresholdOverride?: number, applyModifiersToOverride?: boolean): boolean {
const shinyThreshold = new Utils.NumberHolder(BASE_SHINY_CHANCE);
if (thresholdOverride === undefined || applyModifiersToOverride) {
if (thresholdOverride !== undefined && applyModifiersToOverride) {
shinyThreshold.value = thresholdOverride;
@ -1975,13 +1981,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
/**
* Generates a variant
* Has a 10% of returning 2 (epic variant)
* And a 30% of returning 1 (rare variant)
* Returns 0 (basic shiny) if there is no variant or 60% of the time otherwise
* @returns the shiny variant
* Generates a shiny variant
* @returns `0-2`, with the following probabilities:
* - Has a 10% chance of returning `2` (epic variant)
* - Has a 30% chance of returning `1` (rare variant)
* - Has a 60% chance of returning `0` (basic shiny)
*/
generateVariant(): Variant {
protected generateShinyVariant(): Variant {
const formIndex: number = this.formIndex;
let variantDataIndex: string | number = this.species.speciesId;
if (this.species.forms.length > 0) {
@ -2007,8 +2013,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
generateFusionSpecies(forStarter?: boolean): void {
const hiddenAbilityChance = new Utils.IntegerHolder(BASE_HIDDEN_ABILITY_CHANCE);
public generateFusionSpecies(forStarter?: boolean): void {
const hiddenAbilityChance = new Utils.NumberHolder(BASE_HIDDEN_ABILITY_CHANCE);
if (!this.hasTrainer()) {
this.scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
}
@ -2057,7 +2063,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.generateName();
}
clearFusionSpecies(): void {
public clearFusionSpecies(): void {
this.fusionSpecies = null;
this.fusionFormIndex = 0;
this.fusionAbilityIndex = 0;
@ -2071,12 +2077,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.calculateStats();
}
generateAndPopulateMoveset(): void {
/** Generates a semi-random moveset for a Pokemon */
public generateAndPopulateMoveset(): void {
this.moveset = [];
let movePool: [Moves, number][] = [];
const allLevelMoves = this.getLevelMoves(1, true, true);
if (!allLevelMoves) {
console.log(this.species.speciesId, "ERROR");
console.warn("Error encountered trying to generate moveset for:", this.species.name);
return;
}
@ -2086,15 +2093,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break;
}
let weight = levelMove[0];
if (weight === 0) { // Evo Moves
// Evolution Moves
if (weight === 0) {
weight = 50;
}
if (weight === 1 && allMoves[levelMove[1]].power >= 80) { // Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight
// Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight
if (weight === 1 && allMoves[levelMove[1]].power >= 80) {
weight = 40;
}
if (allMoves[levelMove[1]].name.endsWith(" (N)")) {
weight /= 100;
} // Unimplemented level up moves are possible to generate, but 1% of their normal chance.
if (!movePool.some(m => m[0] === levelMove[1])) {
movePool.push([ levelMove[1], weight ]);
}
@ -2127,7 +2133,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
if (this.level >= 60) { // No egg moves below level 60
// No egg moves below level 60
if (this.level >= 60) {
for (let i = 0; i < 3; i++) {
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][i];
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
@ -2135,7 +2142,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][3];
if (this.level >= 170 && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)") && !this.isBoss()) { // No rare egg moves before e4
// No rare egg moves before e4
if (this.level >= 170 && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)") && !this.isBoss()) {
movePool.push([ moveId, 30 ]);
}
if (this.fusionSpecies) {
@ -2146,14 +2154,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3];
if (this.level >= 170 && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)") && !this.isBoss()) {// No rare egg moves before e4
// No rare egg moves before e4
if (this.level >= 170 && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)") && !this.isBoss()) {
movePool.push([ moveId, 30 ]);
}
}
}
}
if (this.isBoss()) { // Bosses never get self ko moves
// Bosses never get self ko moves
if (this.isBoss()) {
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr));
}
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit));
@ -2182,7 +2192,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const statRatio = worseCategory === MoveCategory.PHYSICAL ? atk / spAtk : spAtk / atk;
movePool = movePool.map(m => [ m[0], m[1] * (allMoves[m[0]].category === worseCategory ? statRatio : 1) ]);
let weightMultiplier = 0.9; // The higher this is the more the game weights towards higher level moves. At 0 all moves are equal weight.
/** The higher this is the more the game weights towards higher level moves. At `0` all moves are equal weight. */
let weightMultiplier = 0.9;
if (this.hasTrainer()) {
weightMultiplier += 0.7;
}
@ -2191,7 +2202,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
const baseWeights: [Moves, number][] = movePool.map(m => [ m[0], Math.ceil(Math.pow(m[1], weightMultiplier) * 100) ]);
if (this.hasTrainer() || this.isBoss()) { // Trainers and bosses always force a stab move
// Trainers and bosses always force a stab move
if (this.hasTrainer() || this.isBoss()) {
const stabMovePool = baseWeights.filter(m => allMoves[m[0]].category !== MoveCategory.STATUS && this.isOfType(allMoves[m[0]].type));
if (stabMovePool.length) {
@ -2221,8 +2233,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Sqrt the weight of any damaging moves with overlapping types. This is about a 0.05 - 0.1 multiplier.
// Other damaging moves 2x weight if 0-1 damaging moves, 0.5x if 2, 0.125x if 3. These weights double if STAB.
// Status moves remain unchanged on weight, this encourages 1-2
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo?.moveId)).map(m => [ m[0], this.moveset.some(mo => mo?.getMove().category !== MoveCategory.STATUS && mo?.getMove().type === allMoves[m[0]].type) ? Math.ceil(Math.sqrt(m[1])) : allMoves[m[0]].category !== MoveCategory.STATUS ? Math.ceil(m[1] / Math.max(Math.pow(4, this.moveset.filter(mo => (mo?.getMove().power!) > 1).length) / 8, 0.5) * (this.isOfType(allMoves[m[0]].type) ? 2 : 1)) : m[1] ]); // TODO: is this bang correct?
} else { // Non-trainer pokemon just use normal weights
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo?.moveId)).map((m) => {
let ret: number;
if (this.moveset.some(mo => mo?.getMove().category !== MoveCategory.STATUS && mo?.getMove().type === allMoves[m[0]].type)) {
ret = Math.ceil(Math.sqrt(m[1]));
} else if (allMoves[m[0]].category !== MoveCategory.STATUS) {
ret = Math.ceil(m[1] / Math.max(Math.pow(4, this.moveset.filter(mo => (mo?.getMove().power ?? 0) > 1).length) / 8, 0.5) * (this.isOfType(allMoves[m[0]].type) ? 2 : 1));
} else {
ret = m[1];
}
return [ m[0], ret ];
});
} else {
// Non-trainer pokemon just use normal weights
movePool = baseWeights.filter(m => !this.moveset.some(mo => m[0] === mo?.moveId));
}
const totalWeight = movePool.reduce((v, m) => v + m[1], 0);
@ -2240,11 +2263,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean {
public trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean {
const move = this.getMoveset().length > moveIndex
? this.getMoveset()[moveIndex]
: null;
return move?.isUsable(this, ignorePp)!; // TODO: is this bang correct?
return move?.isUsable(this, ignorePp) ?? false;
}
showInfo(): void {
@ -3243,7 +3266,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
try {
SoundFade.fadeOut(scene, cry, Utils.fixedInt(Math.ceil(duration * 0.2)));
fusionCry = this.getFusionSpeciesForm().cry(scene, Object.assign({ seek: Math.max(fusionCry.totalDuration * 0.4, 0) }, soundConfig));
SoundFade.fadeIn(scene, fusionCry, Utils.fixedInt(Math.ceil(duration * 0.2)), scene.masterVolume * scene.seVolume, 0);
SoundFade.fadeIn(scene, fusionCry, Utils.fixedInt(Math.ceil(duration * 0.2)), scene.masterVolume * scene.fieldVolume, 0);
} catch (err) {
console.error(err);
}
@ -3258,11 +3281,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.fusionFaintCry(callback);
}
const key = `cry/${this.species.getCryKey(this.formIndex)}`;
//eslint-disable-next-line @typescript-eslint/no-unused-vars
let i = 0;
const key = this.species.getCryKey(this.formIndex);
let rate = 0.85;
const cry = this.scene.playSound(key, { rate: rate }) as AnySound;
if (!cry || this.scene.fieldVolume === 0) {
return callback();
}
const sprite = this.getSprite();
const tintSprite = this.getTintSprite();
const delay = Math.max(this.scene.sound.get(key).totalDuration * 50, 25);
@ -3277,7 +3301,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
delay: Utils.fixedInt(delay),
repeat: -1,
callback: () => {
++i;
frameThreshold = sprite.anims.msPerFrame / rate;
frameProgress += delay;
while (frameProgress > frameThreshold) {
@ -3316,7 +3339,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
private fusionFaintCry(callback: Function): void {
const key = `cry/${this.species.getCryKey(this.formIndex)}`;
const key = this.species.getCryKey(this.formIndex);
let i = 0;
let rate = 0.85;
const cry = this.scene.playSound(key, { rate: rate }) as AnySound;
@ -3324,12 +3347,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const tintSprite = this.getTintSprite();
let duration = cry.totalDuration * 1000;
const fusionCryKey = `cry/${this.fusionSpecies?.getCryKey(this.fusionFormIndex)}`;
const fusionCryKey = this.fusionSpecies!.getCryKey(this.fusionFormIndex);
let fusionCry = this.scene.playSound(fusionCryKey, { rate: rate }) as AnySound;
if (!cry || !fusionCry || this.scene.fieldVolume === 0) {
return callback();
}
fusionCry.stop();
duration = Math.min(duration, fusionCry.totalDuration * 1000);
fusionCry.destroy();
const delay = Math.max(duration * 0.05, 25);
let transitionIndex = 0;
@ -3367,10 +3392,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
frameProgress -= frameThreshold;
}
if (i === transitionIndex) {
if (i === transitionIndex && fusionCryKey) {
SoundFade.fadeOut(this.scene, cry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)));
fusionCry = this.scene.playSound(fusionCryKey, 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);
SoundFade.fadeIn(this.scene, fusionCry, Utils.fixedInt(Math.ceil((duration / rate) * 0.2)), this.scene.masterVolume * this.scene.fieldVolume, 0);
}
rate *= 0.99;
if (cry && !cry.pendingRemove) {
@ -4576,7 +4601,7 @@ export class EnemyPokemon extends Pokemon {
}
if (this.shiny) {
this.variant = this.generateVariant();
this.variant = this.generateShinyVariant();
if (Overrides.OPP_VARIANT_OVERRIDE !== null) {
this.variant = Overrides.OPP_VARIANT_OVERRIDE;
}

View File

@ -232,7 +232,7 @@ export class LoadingScene extends SceneBase {
// Get current lang and load the types atlas for it. English will only load types while all other languages will load types and types_<lang>
const lang = i18next.resolvedLanguage;
if (lang !== "en") {
if (Utils.verifyLang(lang)) {
if (Utils.hasAllLocalizedSprites(lang)) {
this.loadAtlas(`statuses_${lang}`, "");
this.loadAtlas(`types_${lang}`, "");
} else {

View File

@ -1,20 +1,20 @@
import { type PokeballCounts } from "#app/battle-scene";
import { Gender } from "#app/data/gender";
import { Variant } from "#app/data/variant";
import { type ModifierOverride } from "#app/modifier/modifier-type";
import { Unlockables } from "#app/system/unlockables";
import { Abilities } from "#enums/abilities";
import { Biome } from "#enums/biome";
import { EggTier } from "#enums/egg-type";
import { Moves } from "#enums/moves";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PokeballType } from "#enums/pokeball";
import { Species } from "#enums/species";
import { StatusEffect } from "#enums/status-effect";
import { TimeOfDay } from "#enums/time-of-day";
import { VariantTier } from "#enums/variant-tier";
import { WeatherType } from "#enums/weather-type";
import { type PokeballCounts } from "./battle-scene";
import { Gender } from "./data/gender";
import { Variant } from "./data/variant";
import { type ModifierOverride } from "./modifier/modifier-type";
import { Unlockables } from "./system/unlockables";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/**
* Overrides that are using when testing different in game situations

View File

@ -1,12 +1,12 @@
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { Phase } from "#app/phase";
import BattleScene from "#app/battle-scene";
import BattleScene, { AnySound } from "#app/battle-scene";
import { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
import * as Utils from "#app/utils";
import { Mode } from "#app/ui/ui";
import { cos, sin } from "#app/field/anims";
import { PlayerPokemon } from "#app/field/pokemon";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { getTypeRgb } from "#app/data/type";
import i18next from "i18next";
import { getPokemonNameWithAffix } from "#app/messages";
@ -17,7 +17,11 @@ export class EvolutionPhase extends Phase {
protected pokemon: PlayerPokemon;
protected lastLevel: integer;
private preEvolvedPokemonName: string;
private evolution: SpeciesFormEvolution | null;
private evolutionBgm: AnySound;
private evolutionHandler: EvolutionSceneHandler;
protected evolutionContainer: Phaser.GameObjects.Container;
protected evolutionBaseBg: Phaser.GameObjects.Image;
@ -35,6 +39,8 @@ export class EvolutionPhase extends Phase {
this.pokemon = pokemon;
this.evolution = evolution;
this.lastLevel = lastLevel;
this.evolutionBgm = this.scene.playSoundWithoutBgm("evolution");
this.preEvolvedPokemonName = getPokemonNameWithAffix(this.pokemon);
}
validate(): boolean {
@ -117,10 +123,9 @@ export class EvolutionPhase extends Phase {
}
doEvolution(): void {
const evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler;
const preName = getPokemonNameWithAffix(this.pokemon);
this.evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler;
this.scene.ui.showText(i18next.t("menu:evolving", { pokemonName: preName }), null, () => {
this.scene.ui.showText(i18next.t("menu:evolving", { pokemonName: this.preEvolvedPokemonName }), null, () => {
this.pokemon.cry();
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
@ -140,7 +145,6 @@ export class EvolutionPhase extends Phase {
});
this.scene.time.delayedCall(1000, () => {
const evolutionBgm = this.scene.playSoundWithoutBgm("evolution");
this.scene.tweens.add({
targets: this.evolutionBgOverlay,
alpha: 1,
@ -174,10 +178,30 @@ export class EvolutionPhase extends Phase {
this.scene.time.delayedCall(1500, () => {
this.pokemonEvoTintSprite.setScale(0.25);
this.pokemonEvoTintSprite.setVisible(true);
evolutionHandler.canCancel = true;
this.evolutionHandler.canCancel = true;
this.doCycle(1).then(success => {
if (!success) {
if (success) {
this.handleSuccessEvolution(evolvedPokemon);
} else {
this.handleFailedEvolution(evolvedPokemon);
}
});
});
});
}
});
}
});
});
});
}, 1000);
}
/**
* Handles a failed/stopped evolution
* @param evolvedPokemon - The evolved Pokemon
*/
private handleFailedEvolution(evolvedPokemon: Pokemon): void {
this.pokemonSprite.setVisible(true);
this.pokemonTintSprite.setScale(1);
this.scene.tweens.add({
@ -189,12 +213,12 @@ export class EvolutionPhase extends Phase {
}
});
SoundFade.fadeOut(this.scene, evolutionBgm, 100);
SoundFade.fadeOut(this.scene, this.evolutionBgm, 100);
this.scene.unshiftPhase(new EndEvolutionPhase(this.scene));
this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: preName }), null, () => {
this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: preName }), null, () => {
this.scene.ui.showText(i18next.t("menu:stoppedEvolving", { pokemonName: this.preEvolvedPokemonName }), null, () => {
this.scene.ui.showText(i18next.t("menu:pauseEvolutionsQuestion", { pokemonName: this.preEvolvedPokemonName }), null, () => {
const end = () => {
this.scene.ui.showText("", 0);
this.scene.playBgm();
@ -204,21 +228,40 @@ export class EvolutionPhase extends Phase {
this.scene.ui.setOverlayMode(Mode.CONFIRM, () => {
this.scene.ui.revertMode();
this.pokemon.pauseEvolutions = true;
this.scene.ui.showText(i18next.t("menu:evolutionsPaused", { pokemonName: preName }), null, end, 3000);
this.scene.ui.showText(i18next.t("menu:evolutionsPaused", { pokemonName: this.preEvolvedPokemonName }), null, end, 3000);
}, () => {
this.scene.ui.revertMode();
this.scene.time.delayedCall(3000, end);
});
});
}, null, true);
return;
}
/**
* Handles a successful evolution
* @param evolvedPokemon - The evolved Pokemon
*/
private handleSuccessEvolution(evolvedPokemon: Pokemon): void {
this.scene.playSound("se/sparkle");
this.pokemonEvoSprite.setVisible(true);
this.doCircleInward();
const onEvolutionComplete = () => {
SoundFade.fadeOut(this.scene, this.evolutionBgm, 100);
this.scene.time.delayedCall(250, () => {
this.pokemon.cry();
this.scene.time.delayedCall(1250, () => {
this.scene.playSoundWithoutBgm("evolution_fanfare");
evolvedPokemon.destroy();
this.scene.ui.showText(i18next.t("menu:evolutionDone", { pokemonName: this.preEvolvedPokemonName, evolvedPokemonName: this.pokemon.name }), null, () => this.end(), null, true, Utils.fixedInt(4000));
this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm());
});
});
};
this.scene.time.delayedCall(900, () => {
evolutionHandler.canCancel = false;
this.evolutionHandler.canCancel = false;
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => {
const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true);
@ -248,28 +291,7 @@ export class EvolutionPhase extends Phase {
targets: this.evolutionBgOverlay,
alpha: 0,
duration: 250,
onComplete: () => {
SoundFade.fadeOut(this.scene, evolutionBgm, 100);
this.scene.time.delayedCall(250, () => {
this.pokemon.cry();
this.scene.time.delayedCall(1250, () => {
this.scene.playSoundWithoutBgm("evolution_fanfare");
evolvedPokemon.destroy();
this.scene.ui.showText(i18next.t("menu:evolutionDone", { pokemonName: preName, evolvedPokemonName: this.pokemon.name }), null, () => this.end(), null, true, Utils.fixedInt(4000));
this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm());
});
});
}
});
}
});
}
});
});
});
});
});
onComplete: onEvolutionComplete
});
}
});
@ -277,7 +299,6 @@ export class EvolutionPhase extends Phase {
});
});
});
}, 1000);
}
doSpiralUpward() {
@ -320,7 +341,6 @@ export class EvolutionPhase extends Phase {
doCycle(l: number, lastCycle: integer = 15): Promise<boolean> {
return new Promise(resolve => {
const evolutionHandler = this.scene.ui.getHandler() as EvolutionSceneHandler;
const isLastCycle = l === lastCycle;
this.scene.tweens.add({
targets: this.pokemonTintSprite,
@ -336,7 +356,7 @@ export class EvolutionPhase extends Phase {
duration: 500 / l,
yoyo: !isLastCycle,
onComplete: () => {
if (evolutionHandler.cancelled) {
if (this.evolutionHandler.cancelled) {
return resolve(false);
}
if (l < lastCycle) {

View File

@ -52,11 +52,11 @@ import {
HitHealModifier,
PokemonMultiHitModifier,
} from "#app/modifier/modifier";
import { PokemonPhase } from "#app/phases/pokemon-phase";
import { BooleanHolder, executeIf, NumberHolder } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import i18next from "i18next";
import { PokemonPhase } from "./pokemon-phase";
export class MoveEffectPhase extends PokemonPhase {
public move: PokemonMove;
@ -517,7 +517,11 @@ export class MoveEffectPhase extends PokemonPhase {
return true;
}
const user = this.getUserPokemon()!; // TODO: is this bang correct?
const user = this.getUserPokemon();
if (!user) {
return false;
}
// Hit check only calculated on first hit for multi-hit moves unless flag is set to check all hits.
// However, if an ability with the MaxMultiHitAbAttr, namely Skill Link, is present, act as a normal
@ -566,9 +570,9 @@ export class MoveEffectPhase extends PokemonPhase {
}
/** @returns The {@linkcode Pokemon} using this phase's invoked move */
public getUserPokemon(): Pokemon | undefined {
public getUserPokemon(): Pokemon | null {
if (this.battlerIndex > BattlerIndex.ENEMY_2) {
return this.scene.getPokemonById(this.battlerIndex) ?? undefined;
return this.scene.getPokemonById(this.battlerIndex);
}
return (this.player ? this.scene.getPlayerField() : this.scene.getEnemyField())[this.fieldIndex];
}
@ -596,8 +600,7 @@ export class MoveEffectPhase extends PokemonPhase {
/**
* Prevents subsequent strikes of this phase's invoked move from occurring
* @param target {@linkcode Pokemon} if defined, only stop subsequent
* strikes against this Pokemon
* @param target - If defined, only stop subsequent strikes against this {@linkcode Pokemon}
*/
public stopMultiHit(target?: Pokemon): void {
// If given a specific target, remove the target from subsequent strikes

View File

@ -163,6 +163,11 @@ export const SettingKeys = {
Shop_Overlay_Opacity: "SHOP_OVERLAY_OPACITY"
};
export enum MusicPreference {
CONSISTENT,
MIXED
}
/**
* All Settings not related to controls
*/
@ -634,7 +639,7 @@ export const Setting: Array<Setting> = [
label: i18next.t("settings:mixed")
}
],
default: 0,
default: MusicPreference.MIXED,
type: SettingType.AUDIO,
requireReload: true
},

View File

@ -216,7 +216,7 @@ describe("Abilities - Unburden", () => {
.moveset([ Moves.SPLASH ]);
await game.classicMode.startBattle([ Species.TREECKO, Species.MEOWTH, Species.WEEZING ]);
const playerPokemon = game.scene.getParty();
const playerPokemon = game.scene.getPlayerParty();
const treecko = playerPokemon[0];
const weezing = playerPokemon[2];
treecko.abilityIndex = 2;

View File

@ -156,6 +156,7 @@ export default class GameManager {
this.scene.enableTutorials = false;
this.scene.gameData.gender = PlayerGender.MALE; // set initial player gender
this.scene.battleStyle = this.settings.battleStyle;
this.scene.fieldVolume = 0;
}
/**

View File

@ -121,18 +121,6 @@ export function randSeedWeightedItem<T>(items: T[]): T {
: Phaser.Math.RND.weightedPick(items);
}
export function randSeedEasedWeightedItem<T>(items: T[], easingFunction: string = "Sine.easeIn"): T | null {
if (!items.length) {
return null;
}
if (items.length === 1) {
return items[0];
}
const value = Phaser.Math.RND.realInRange(0, 1);
const easedValue = Phaser.Tweens.Builders.GetEaseFunction(easingFunction)(value);
return items[Math.floor(easedValue * items.length)];
}
/**
* Shuffle a list using the seeded rng. Utilises the Fisher-Yates algorithm.
* @param {Array} items An array of items.
@ -380,18 +368,21 @@ export class NumberHolder {
}
}
/** @deprecated Use {@linkcode NumberHolder} */
export class IntegerHolder extends NumberHolder {
constructor(value: integer) {
super(value);
}
}
/** @deprecated Use {@linkcode NumberHolder}*/
export class FixedInt extends IntegerHolder {
constructor(value: integer) {
super(value);
}
}
/** @deprecated */
export function fixedInt(value: integer): integer {
return new FixedInt(value) as unknown as integer;
}
@ -474,14 +465,15 @@ export function hslToHex(h: number, s: number, l: number): string {
return `#${f(0)}${f(8)}${f(4)}`;
}
/*This function returns true if the current lang is available for some functions
If the lang is not in the function, it usually means that lang is going to use the default english version
This function is used in:
- summary-ui-handler.ts: If the lang is not available, it'll use types.json (english)
English itself counts as not available
/**
* This function returns `true` if all localized images used by the game have been added for the given language.
*
* If the lang is not in the function, it usually means that lang is going to use the default english version
*
* English itself counts as not available
*/
export function verifyLang(lang?: string): boolean {
//IMPORTANT - ONLY ADD YOUR LANG HERE IF YOU'VE ALREADY ADDED ALL THE NECESSARY IMAGES
export function hasAllLocalizedSprites(lang?: string): boolean {
// IMPORTANT - ONLY ADD YOUR LANG HERE IF YOU'VE ALREADY ADDED ALL THE NECESSARY IMAGES
if (!lang) {
lang = i18next.resolvedLanguage;
}
@ -503,7 +495,7 @@ export function verifyLang(lang?: string): boolean {
}
/**
* Prints the type and name of all game objects in a container for debuggin purposes
* Prints the type and name of all game objects in a container for debugging purposes
* @param container container with game objects inside it
*/
export function printContainerList(container: Phaser.GameObjects.Container): void {
@ -578,17 +570,12 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole
return null;
}
/**
* Returns if an object is null or undefined
* @param object
*/
export function isNullOrUndefined(object: any): object is undefined | null {
return null === object || undefined === object;
}
/**
* Capitalizes the first letter of a string
* @param str
*/
export function capitalizeFirstLetter(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1);
@ -614,7 +601,7 @@ export function toDmgValue(value: number, minValue: number = 1) {
* @returns the localized sprite key
*/
export function getLocalizedSpriteKey(baseKey: string) {
return `${baseKey}${verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`;
return `${baseKey}${hasAllLocalizedSprites(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`;
}
/**
@ -622,7 +609,7 @@ export function getLocalizedSpriteKey(baseKey: string) {
* @param num the number to check
* @param min the minimum value (included)
* @param max the maximum value (included)
* @returns true if number is **inclusive** between min and max
* @returns `true` if number is **inclusive** between min and max
*/
export function isBetween(num: number, min: number, max: number): boolean {
return num >= min && num <= max;