[Ability] Implement Illusion (#3273)
* implement illusion ability with unit test and localizations * try removing whitespace change on unnecessary files * nit corrections * nit update src/field/pokemon.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * nit update src/phases.ts Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * illusion test correction * unexpected error correction * refactor property pokemon.illusion to pokemon.battleData.illusion * nit * nit * update unit test up-to-date * add docs * merge up to date * bugfix * bugfix * merge up to date * refactor field illusion out of battleData * fix nit * fix nit * Zoroark change illusion after lastPokemon update * Zoroark change illusion after lastPokemon update * refactor bug fix * bugfix * bug fix on tests * Update src/field/pokemon.ts Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * use GetFailedText * remove useless import * add condition 'no illusion' into transform move * wild Zoroark creates an illusion according to the current biome * wild Zoroark creates an illusion according to the current biome * delete console.log() * add doc * Update src/field/pokemon.ts Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * fix tests * update locales submodule * update Illusion interface * bug fix * bug fix * bugfix * rename some params for future implementations * Zoroark keep illusion between battles * Zoroark keep illusion between battles * delete draft * merge up-to-date * bugfix * merge * merge * implement canApplyPresummon method * Update test/abilities/illusion.test.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update test/abilities/illusion.test.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update test/abilities/illusion.test.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * nit * review corrections * nit * type hints affected by enemy illusion * type hints affected by enemy illusionin fight-ui-handler * nit * rename some parameters back in useIllusion * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * refactor battleData.illusion as summonData.illusion and delete oncePerBattleClause * add comments * illusion will break before evolution * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * bug fix * g * get submodule back * get submodule back * bug fix to save illusion status * add pokemon.getPokeball() * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/field/pokemon.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
f9ff4abfb0
commit
15e535a1a0
|
@ -1072,7 +1072,7 @@ export default class BattleScene extends SceneBase {
|
|||
|
||||
container.add(icon);
|
||||
|
||||
if (pokemon.isFusion()) {
|
||||
if (pokemon.isFusion(true)) {
|
||||
const fusionIcon = this.add.sprite(0, 0, pokemon.getFusionIconAtlasKey(ignoreOverride));
|
||||
fusionIcon.setName("sprite-fusion-icon");
|
||||
fusionIcon.setOrigin(0.5, 0);
|
||||
|
|
|
@ -2370,6 +2370,18 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes illusions when a Pokemon is summoned.
|
||||
*/
|
||||
export class PostSummonRemoveIllusionAbAttr extends PostSummonAbAttr {
|
||||
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||
for (const pokemon of globalScene.getField(true)) {
|
||||
pokemon.breakIllusion();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr {
|
||||
//Attr doesn't force pokemon name on the message
|
||||
private message: string;
|
||||
|
@ -2812,7 +2824,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
|||
}
|
||||
|
||||
private getTarget(targets: Pokemon[]): Pokemon {
|
||||
let target: Pokemon;
|
||||
let target: Pokemon = targets[0];
|
||||
if (targets.length > 1) {
|
||||
globalScene.executeWithSeedOffset(() => {
|
||||
// in a double battle, if one of the opposing pokemon is fused the other one will be chosen
|
||||
|
@ -2829,6 +2841,7 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
|||
} else {
|
||||
target = targets[0];
|
||||
}
|
||||
|
||||
target = target!;
|
||||
|
||||
return target;
|
||||
|
@ -2836,6 +2849,12 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
|
|||
|
||||
override canApplyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||
const targets = pokemon.getOpponents();
|
||||
const target = this.getTarget(targets);
|
||||
|
||||
if (!!target.summonData?.illusion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (simulated || !targets.length) {
|
||||
return simulated;
|
||||
}
|
||||
|
@ -4741,8 +4760,8 @@ export class MaxMultiHitAbAttr extends AbAttr {
|
|||
}
|
||||
|
||||
export class PostBattleAbAttr extends AbAttr {
|
||||
constructor() {
|
||||
super(true);
|
||||
constructor(showAbility: boolean = true) {
|
||||
super(showAbility);
|
||||
}
|
||||
|
||||
canApplyPostBattle(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
||||
|
@ -5259,6 +5278,92 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for defining {@linkcode Ability} attributes before summon
|
||||
* (should use {@linkcode PostSummonAbAttr} for most ability)
|
||||
* @see {@linkcode applyPreSummon()}
|
||||
*/
|
||||
export class PreSummonAbAttr extends AbAttr {
|
||||
applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): void {}
|
||||
|
||||
canApplyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class IllusionPreSummonAbAttr extends PreSummonAbAttr {
|
||||
/**
|
||||
* Apply a new illusion when summoning Zoroark if the illusion is available
|
||||
*
|
||||
* @param pokemon - The Pokémon with the Illusion ability.
|
||||
* @param passive - N/A
|
||||
* @param args - N/A
|
||||
* @returns Whether the illusion was applied.
|
||||
*/
|
||||
override applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): void {
|
||||
const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p.isAllowedInBattle());
|
||||
const lastPokemon: Pokemon = party.filter(p => p !==pokemon).at(-1) || pokemon;
|
||||
pokemon.setIllusion(lastPokemon);
|
||||
}
|
||||
|
||||
override canApplyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
|
||||
pokemon.initSummondata()
|
||||
if(pokemon.hasTrainer()){
|
||||
const party: Pokemon[] = (pokemon.isPlayer() ? globalScene.getPlayerParty() : globalScene.getEnemyParty()).filter(p => p.isAllowedInBattle());
|
||||
const lastPokemon: Pokemon = party.filter(p => p !==pokemon).at(-1) || pokemon;
|
||||
const speciesId = lastPokemon.species.speciesId;
|
||||
|
||||
// If the last conscious Pokémon in the party is a Terastallized Ogerpon or Terapagos, Illusion will not activate.
|
||||
// Illusion will also not activate if the Pokémon with Illusion is Terastallized and the last Pokémon in the party is Ogerpon or Terapagos.
|
||||
if (
|
||||
lastPokemon === pokemon ||
|
||||
((speciesId === Species.OGERPON || speciesId === Species.TERAPAGOS) && (lastPokemon.isTerastallized || pokemon.isTerastallized))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !pokemon.summonData.illusionBroken;
|
||||
}
|
||||
}
|
||||
|
||||
export class IllusionBreakAbAttr extends PostDefendAbAttr {
|
||||
/**
|
||||
* Destroy the illusion upon taking damage
|
||||
*
|
||||
* @param pokemon - The Pokémon with the Illusion ability.
|
||||
* @param passive - unused
|
||||
* @param attacker - The attacking Pokémon.
|
||||
* @param move - The move being used.
|
||||
* @param hitResult - The type of hitResult the pokemon got
|
||||
* @param args - unused
|
||||
* @returns - Whether the illusion was destroyed.
|
||||
*/
|
||||
override applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): void {
|
||||
pokemon.breakIllusion();
|
||||
pokemon.summonData.illusionBroken = true;
|
||||
}
|
||||
|
||||
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
const breakIllusion: HitResult[] = [ HitResult.EFFECTIVE, HitResult.SUPER_EFFECTIVE, HitResult.NOT_VERY_EFFECTIVE, HitResult.ONE_HIT_KO ];
|
||||
return breakIllusion.includes(hitResult) && !!pokemon.summonData?.illusion
|
||||
}
|
||||
}
|
||||
|
||||
export class IllusionPostBattleAbAttr extends PostBattleAbAttr {
|
||||
/**
|
||||
* Break the illusion once the battle ends
|
||||
*
|
||||
* @param pokemon - The Pokémon with the Illusion ability.
|
||||
* @param passive - Unused
|
||||
* @param args - Unused
|
||||
* @returns - Whether the illusion was applied.
|
||||
*/
|
||||
override applyPostBattle(pokemon: Pokemon, passive: boolean, simulated:boolean, args: any[]): void {
|
||||
pokemon.breakIllusion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If a Pokémon with this Ability selects a damaging move, it has a 30% chance of going first in its priority bracket. If the Ability activates, this is announced at the start of the turn (after move selection).
|
||||
*
|
||||
|
@ -6017,6 +6122,20 @@ export function applyPostSummonAbAttrs(
|
|||
);
|
||||
}
|
||||
|
||||
export function applyPreSummonAbAttrs(
|
||||
attrType: Constructor<PreSummonAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
...args: any[]
|
||||
): void {
|
||||
applyAbAttrsInternal<PreSummonAbAttr>(
|
||||
attrType,
|
||||
pokemon,
|
||||
(attr, passive) => attr.applyPreSummon(pokemon, passive, args),
|
||||
(attr, passive) => attr.canApplyPreSummon(pokemon, passive, args),
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
export function applyPreSwitchOutAbAttrs(
|
||||
attrType: Constructor<PreSwitchOutAbAttr>,
|
||||
pokemon: Pokemon,
|
||||
|
@ -6811,8 +6930,14 @@ export function initAbilities() {
|
|||
return isNullOrUndefined(movePhase);
|
||||
}, 1.3),
|
||||
new Ability(Abilities.ILLUSION, 5)
|
||||
//The pokemon generate an illusion if it's available
|
||||
.attr(IllusionPreSummonAbAttr, false)
|
||||
//The pokemon loses his illusion when he is damaged by a move
|
||||
.attr(IllusionBreakAbAttr, true)
|
||||
//Illusion is available again after a battle
|
||||
.conditionalAttr((pokemon) => pokemon.isAllowedInBattle(), IllusionPostBattleAbAttr, false)
|
||||
.uncopiable()
|
||||
.unimplemented(),
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.IMPOSTER, 5)
|
||||
.attr(PostSummonTransformAbAttr)
|
||||
.uncopiable(),
|
||||
|
@ -7223,6 +7348,8 @@ export function initAbilities() {
|
|||
.attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)
|
||||
.uncopiable()
|
||||
.attr(NoTransformAbilityAbAttr)
|
||||
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
||||
.attr(PostSummonRemoveIllusionAbAttr)
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.PASTEL_VEIL, 8)
|
||||
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
||||
|
|
|
@ -7389,11 +7389,13 @@ export class AbilityChangeAttr extends MoveEffectAttr {
|
|||
|
||||
const moveTarget = this.selfTarget ? user : target;
|
||||
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name }));
|
||||
|
||||
globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger);
|
||||
if (moveTarget.breakIllusion()) {
|
||||
globalScene.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(moveTarget) }));
|
||||
}
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:acquiredAbility", { pokemonName: getPokemonNameWithAffix(moveTarget), abilityName: allAbilities[this.ability].name }));
|
||||
moveTarget.setTempAbility(allAbilities[this.ability]);
|
||||
globalScene.triggerPokemonFormChange(moveTarget, SpeciesFormChangeRevertWeatherFormTrigger);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -8673,6 +8675,8 @@ export function initMoves() {
|
|||
.makesContact(false),
|
||||
new StatusMove(Moves.TRANSFORM, PokemonType.NORMAL, -1, 10, -1, 0, 1)
|
||||
.attr(TransformAttr)
|
||||
.condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE))
|
||||
.condition((user, target, move) => !target.summonData?.illusion && !user.summonData?.illusion)
|
||||
// transforming from or into fusion pokemon causes various problems (such as crashes)
|
||||
.condition((user, target, move) => !target.getTag(BattlerTagType.SUBSTITUTE) && !user.fusionSpecies && !target.fusionSpecies)
|
||||
.ignoresProtect(),
|
||||
|
|
|
@ -536,21 +536,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
}
|
||||
|
||||
getNameToRender() {
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability).
|
||||
*/
|
||||
getNameToRender(useIllusion: boolean = true) {
|
||||
const name: string = (!useIllusion && !!this.summonData?.illusion) ? this.summonData?.illusion.basePokemon!.name : this.name;
|
||||
const nickname: string = (!useIllusion && !!this.summonData?.illusion) ? this.summonData?.illusion.basePokemon!.nickname : this.nickname;
|
||||
try {
|
||||
if (this.nickname) {
|
||||
return decodeURIComponent(escape(atob(this.nickname)));
|
||||
if (nickname) {
|
||||
return decodeURIComponent(escape(atob(nickname)));
|
||||
}
|
||||
return this.name;
|
||||
return name;
|
||||
} catch (err) {
|
||||
console.error(`Failed to decode nickname for ${this.name}`, err);
|
||||
return this.name;
|
||||
console.error(`Failed to decode nickname for ${name}`, err);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
getPokeball(useIllusion = false){
|
||||
if(useIllusion){
|
||||
return this.summonData?.illusion?.pokeball ?? this.pokeball
|
||||
} else {
|
||||
return this.pokeball
|
||||
}
|
||||
}
|
||||
|
||||
init(): void {
|
||||
this.fieldPosition = FieldPosition.CENTER;
|
||||
|
||||
this.initBattleInfo();
|
||||
|
||||
globalScene.fieldUI.addAt(this.battleInfo, 0);
|
||||
|
@ -584,7 +596,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
this.addAt(sprite, 0);
|
||||
this.addAt(tintSprite, 1);
|
||||
|
||||
if (this.isShiny() && !this.shinySparkle) {
|
||||
if (this.isShiny(true) && !this.shinySparkle) {
|
||||
this.initShinySparkle();
|
||||
}
|
||||
}
|
||||
|
@ -682,6 +694,92 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an illusion of the last pokemon in the party, as other wild pokemon in the area.
|
||||
*/
|
||||
setIllusion(pokemon: Pokemon): boolean {
|
||||
if(!!this.summonData?.illusion){
|
||||
this.breakIllusion();
|
||||
}
|
||||
if (this.hasTrainer()) {
|
||||
const speciesId = pokemon.species.speciesId;
|
||||
|
||||
this.summonData.illusion = {
|
||||
basePokemon: {
|
||||
name: this.name,
|
||||
nickname: this.nickname,
|
||||
shiny: this.shiny,
|
||||
variant: this.variant,
|
||||
fusionShiny: this.fusionShiny,
|
||||
fusionVariant: this.fusionVariant
|
||||
},
|
||||
species: speciesId,
|
||||
formIndex: pokemon.formIndex,
|
||||
gender: pokemon.gender,
|
||||
pokeball: pokemon.pokeball,
|
||||
fusionFormIndex: pokemon.fusionFormIndex,
|
||||
fusionSpecies: pokemon.fusionSpecies || undefined,
|
||||
fusionGender: pokemon.fusionGender
|
||||
};
|
||||
|
||||
this.name = pokemon.name;
|
||||
this.nickname = pokemon.nickname;
|
||||
this.shiny = pokemon.shiny;
|
||||
this.variant = pokemon.variant;
|
||||
this.fusionVariant = pokemon.fusionVariant;
|
||||
this.fusionShiny = pokemon.fusionShiny;
|
||||
if (this.shiny) {
|
||||
this.initShinySparkle();
|
||||
}
|
||||
this.loadAssets(false, true).then(() => this.playAnim());
|
||||
this.updateInfo();
|
||||
} else {
|
||||
const randomIllusion: PokemonSpecies = globalScene.arena.randomSpecies(globalScene.currentBattle.waveIndex, this.level);
|
||||
|
||||
this.summonData.illusion = {
|
||||
basePokemon: {
|
||||
name: this.name,
|
||||
nickname: this.nickname,
|
||||
shiny: this.shiny,
|
||||
variant: this.variant,
|
||||
fusionShiny: this.fusionShiny,
|
||||
fusionVariant: this.fusionVariant
|
||||
},
|
||||
species: randomIllusion.speciesId,
|
||||
formIndex: randomIllusion.formIndex,
|
||||
gender: this.gender,
|
||||
pokeball: this.pokeball
|
||||
};
|
||||
|
||||
this.name = randomIllusion.name;
|
||||
this.loadAssets(false, true).then(() => this.playAnim());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
breakIllusion(): boolean {
|
||||
if (!this.summonData?.illusion) {
|
||||
return false;
|
||||
} else {
|
||||
this.name = this.summonData?.illusion.basePokemon.name;
|
||||
this.nickname = this.summonData?.illusion.basePokemon.nickname;
|
||||
this.shiny = this.summonData?.illusion.basePokemon.shiny;
|
||||
this.variant = this.summonData?.illusion.basePokemon.variant;
|
||||
this.fusionVariant = this.summonData?.illusion.basePokemon.fusionVariant;
|
||||
this.fusionShiny = this.summonData?.illusion.basePokemon.fusionShiny;
|
||||
this.summonData.illusion = null;
|
||||
}
|
||||
if (this.isOnField()) {
|
||||
globalScene.playSound("PRSFX- Transform");
|
||||
}
|
||||
if (this.shiny) {
|
||||
this.initShinySparkle();
|
||||
}
|
||||
this.loadAssets(false).then(() => this.playAnim());
|
||||
this.updateInfo(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract isPlayer(): boolean;
|
||||
|
||||
abstract hasTrainer(): boolean;
|
||||
|
@ -690,29 +788,41 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
|
||||
abstract getBattlerIndex(): BattlerIndex;
|
||||
|
||||
async loadAssets(ignoreOverride = true): Promise<void> {
|
||||
/**
|
||||
* @param useIllusion - Whether we want the illusion or not.
|
||||
*/
|
||||
async loadAssets(ignoreOverride = true, useIllusion: boolean = false): Promise<void> {
|
||||
/** Promises that are loading assets and can be run concurrently. */
|
||||
const loadPromises: Promise<void>[] = [];
|
||||
// Assets for moves
|
||||
loadPromises.push(loadMoveAnimations(this.getMoveset().map(m => m.getMove().id)));
|
||||
|
||||
// Load the assets for the species form
|
||||
const formIndex = !!this.summonData?.illusion && useIllusion ? this.summonData?.illusion.formIndex : this.formIndex;
|
||||
loadPromises.push(
|
||||
this.getSpeciesForm().loadAssets(this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant),
|
||||
this.getSpeciesForm(false, useIllusion).loadAssets(
|
||||
this.getGender(useIllusion) === Gender.FEMALE,
|
||||
formIndex,
|
||||
this.isShiny(useIllusion),
|
||||
this.getVariant(useIllusion)
|
||||
),
|
||||
);
|
||||
|
||||
if (this.isPlayer() || this.getFusionSpeciesForm()) {
|
||||
if (this.isPlayer() || this.getFusionSpeciesForm(false, useIllusion)) {
|
||||
globalScene.loadPokemonAtlas(
|
||||
this.getBattleSpriteKey(true, ignoreOverride),
|
||||
this.getBattleSpriteAtlasPath(true, ignoreOverride),
|
||||
);
|
||||
}
|
||||
if (this.getFusionSpeciesForm()) {
|
||||
loadPromises.push(this.getFusionSpeciesForm().loadAssets(
|
||||
this.getFusionGender() === Gender.FEMALE,
|
||||
this.fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant,
|
||||
const fusionFormIndex = !!this.summonData?.illusion && useIllusion ? this.summonData?.illusion.fusionFormIndex : this.fusionFormIndex;
|
||||
const fusionShiny = !!this.summonData?.illusion && !useIllusion ? this.summonData?.illusion.basePokemon!.fusionShiny : this.fusionShiny;
|
||||
const fusionVariant = !!this.summonData?.illusion && !useIllusion ? this.summonData?.illusion.basePokemon!.fusionVariant : this.fusionVariant;
|
||||
loadPromises.push(this.getFusionSpeciesForm(false, useIllusion).loadAssets(
|
||||
this.getFusionGender(false, useIllusion) === Gender.FEMALE,
|
||||
fusionFormIndex,
|
||||
fusionShiny,
|
||||
fusionVariant
|
||||
));
|
||||
globalScene.loadPokemonAtlas(
|
||||
this.getFusionBattleSpriteKey(true, ignoreOverride),
|
||||
|
@ -720,7 +830,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.shiny) {
|
||||
if (this.isShiny(true)) {
|
||||
loadPromises.push(populateVariantColors(this, false, ignoreOverride))
|
||||
if (this.isPlayer()) {
|
||||
loadPromises.push(populateVariantColors(this, true, ignoreOverride));
|
||||
|
@ -870,11 +980,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
getSpriteId(ignoreOverride?: boolean): string {
|
||||
return this.getSpeciesForm(ignoreOverride).getSpriteId(
|
||||
this.getGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.formIndex,
|
||||
this.shiny,
|
||||
this.variant,
|
||||
const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex! : this.formIndex;
|
||||
return this.getSpeciesForm(ignoreOverride, true).getSpriteId(
|
||||
this.getGender(ignoreOverride, true) === Gender.FEMALE,
|
||||
formIndex,
|
||||
this.shiny,
|
||||
this.variant
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -882,21 +993,24 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
if (back === undefined) {
|
||||
back = this.isPlayer();
|
||||
}
|
||||
return this.getSpeciesForm(ignoreOverride).getSpriteId(
|
||||
this.getGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.formIndex,
|
||||
this.shiny,
|
||||
this.variant,
|
||||
back,
|
||||
|
||||
const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex! : this.formIndex;
|
||||
|
||||
return this.getSpeciesForm(ignoreOverride, true).getSpriteId(
|
||||
this.getGender(ignoreOverride, true) === Gender.FEMALE,
|
||||
formIndex,
|
||||
this.shiny,
|
||||
this.variant,
|
||||
back
|
||||
);
|
||||
}
|
||||
|
||||
getSpriteKey(ignoreOverride?: boolean): string {
|
||||
return this.getSpeciesForm(ignoreOverride).getSpriteKey(
|
||||
return this.getSpeciesForm(ignoreOverride, false).getSpriteKey(
|
||||
this.getGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.formIndex,
|
||||
this.shiny,
|
||||
this.variant,
|
||||
this.summonData?.illusion?.basePokemon.shiny ?? this.shiny,
|
||||
this.summonData?.illusion?.basePokemon.variant ?? this.variant
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -905,11 +1019,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
getFusionSpriteId(ignoreOverride?: boolean): string {
|
||||
return this.getFusionSpeciesForm(ignoreOverride).getSpriteId(
|
||||
this.getFusionGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant,
|
||||
const fusionFormIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex;
|
||||
return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId(
|
||||
this.getFusionGender(ignoreOverride, true) === Gender.FEMALE,
|
||||
fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -917,12 +1032,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
if (back === undefined) {
|
||||
back = this.isPlayer();
|
||||
}
|
||||
return this.getFusionSpeciesForm(ignoreOverride).getSpriteId(
|
||||
this.getFusionGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant,
|
||||
back,
|
||||
|
||||
const fusionFormIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex;
|
||||
|
||||
return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId(
|
||||
this.getFusionGender(ignoreOverride, true) === Gender.FEMALE,
|
||||
fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant,
|
||||
back
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -941,62 +1059,77 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
getIconAtlasKey(ignoreOverride?: boolean): string {
|
||||
return this.getSpeciesForm(ignoreOverride).getIconAtlasKey(
|
||||
this.formIndex,
|
||||
this.shiny,
|
||||
this.variant,
|
||||
const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex;
|
||||
return this.getSpeciesForm(ignoreOverride, true).getIconAtlasKey(
|
||||
formIndex,
|
||||
this.shiny,
|
||||
this.variant
|
||||
);
|
||||
}
|
||||
|
||||
getFusionIconAtlasKey(ignoreOverride?: boolean): string {
|
||||
return this.getFusionSpeciesForm(ignoreOverride).getIconAtlasKey(
|
||||
this.fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant,
|
||||
return this.getFusionSpeciesForm(ignoreOverride, true).getIconAtlasKey(
|
||||
this.fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant
|
||||
);
|
||||
}
|
||||
|
||||
getIconId(ignoreOverride?: boolean): string {
|
||||
return this.getSpeciesForm(ignoreOverride).getIconId(
|
||||
this.getGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.formIndex,
|
||||
this.shiny,
|
||||
this.variant,
|
||||
const formIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex;
|
||||
return this.getSpeciesForm(ignoreOverride, true).getIconId(
|
||||
this.getGender(ignoreOverride, true) === Gender.FEMALE,
|
||||
formIndex,
|
||||
this.shiny,
|
||||
this.variant
|
||||
);
|
||||
}
|
||||
|
||||
getFusionIconId(ignoreOverride?: boolean): string {
|
||||
return this.getFusionSpeciesForm(ignoreOverride).getIconId(
|
||||
this.getFusionGender(ignoreOverride) === Gender.FEMALE,
|
||||
this.fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant,
|
||||
const fusionFormIndex: integer = !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex;
|
||||
return this.getFusionSpeciesForm(ignoreOverride, true).getIconId(
|
||||
this.getFusionGender(ignoreOverride, true) === Gender.FEMALE,
|
||||
fusionFormIndex,
|
||||
this.fusionShiny,
|
||||
this.fusionVariant
|
||||
);
|
||||
}
|
||||
|
||||
getSpeciesForm(ignoreOverride?: boolean): PokemonSpeciesForm {
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the speciesForm of the illusion or not.
|
||||
*/
|
||||
getSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm {
|
||||
const species: PokemonSpecies = useIllusion && !!this.summonData?.illusion ? getPokemonSpecies(this.summonData?.illusion.species) : this.species;
|
||||
|
||||
const formIndex: integer = useIllusion && !!this.summonData?.illusion ? this.summonData?.illusion.formIndex : this.formIndex;
|
||||
|
||||
if (!ignoreOverride && this.summonData?.speciesForm) {
|
||||
return this.summonData.speciesForm;
|
||||
}
|
||||
if (this.species.forms && this.species.forms.length > 0) {
|
||||
return this.species.forms[this.formIndex];
|
||||
if (species.forms && species.forms.length > 0) {
|
||||
return species.forms[formIndex];
|
||||
}
|
||||
|
||||
return this.species;
|
||||
return species;
|
||||
}
|
||||
|
||||
getFusionSpeciesForm(ignoreOverride?: boolean): PokemonSpeciesForm {
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fusionSpeciesForm of the illusion or not.
|
||||
*/
|
||||
getFusionSpeciesForm(ignoreOverride?: boolean, useIllusion: boolean = false): PokemonSpeciesForm {
|
||||
const fusionSpecies: PokemonSpecies = useIllusion && !!this.summonData?.illusion ? this.summonData?.illusion.fusionSpecies! : this.fusionSpecies!;
|
||||
const fusionFormIndex: integer = useIllusion && !!this.summonData?.illusion ? this.summonData?.illusion.fusionFormIndex! : this.fusionFormIndex;
|
||||
|
||||
if (!ignoreOverride && this.summonData?.speciesForm) {
|
||||
return this.summonData.fusionSpeciesForm;
|
||||
}
|
||||
if (
|
||||
!this.fusionSpecies?.forms?.length ||
|
||||
this.fusionFormIndex >= this.fusionSpecies?.forms.length
|
||||
!fusionSpecies?.forms?.length ||
|
||||
fusionFormIndex >= fusionSpecies?.forms.length
|
||||
) {
|
||||
//@ts-ignore
|
||||
return this.fusionSpecies; // TODO: I don't even know how to fix this... A complete cluster of classes involved + null
|
||||
return fusionSpecies;
|
||||
}
|
||||
return this.fusionSpecies?.forms[this.fusionFormIndex];
|
||||
return fusionSpecies?.forms[fusionFormIndex];
|
||||
}
|
||||
|
||||
getSprite(): Phaser.GameObjects.Sprite {
|
||||
|
@ -1652,36 +1785,98 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
}
|
||||
|
||||
getGender(ignoreOverride?: boolean): Gender {
|
||||
if (!ignoreOverride && this.summonData?.gender !== undefined) {
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability).
|
||||
*/
|
||||
getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender {
|
||||
if (useIllusion && !!this.summonData?.illusion) {
|
||||
return this.summonData?.illusion.gender!;
|
||||
} else if (!ignoreOverride && this.summonData?.gender !== undefined) {
|
||||
return this.summonData.gender;
|
||||
}
|
||||
return this.gender;
|
||||
}
|
||||
|
||||
getFusionGender(ignoreOverride?: boolean): Gender {
|
||||
if (!ignoreOverride && this.summonData?.fusionGender !== undefined) {
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fake or real gender (illusion ability).
|
||||
*/
|
||||
getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender {
|
||||
if (useIllusion && !!this.summonData?.illusion) {
|
||||
return this.summonData?.illusion.fusionGender!;
|
||||
} else if (!ignoreOverride && this.summonData?.fusionGender !== undefined) {
|
||||
return this.summonData.fusionGender;
|
||||
}
|
||||
return this.fusionGender;
|
||||
}
|
||||
|
||||
isShiny(): boolean {
|
||||
return this.shiny || (this.isFusion() && this.fusionShiny);
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fake or real shininess (illusion ability).
|
||||
*/
|
||||
isShiny(useIllusion: boolean = false): boolean {
|
||||
if (!useIllusion && !!this.summonData?.illusion) {
|
||||
return this.summonData?.illusion.basePokemon?.shiny || (!!this.summonData?.illusion.fusionSpecies && this.summonData?.illusion.basePokemon?.fusionShiny) || false;
|
||||
} else {
|
||||
return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny);
|
||||
}
|
||||
}
|
||||
|
||||
getVariant(): Variant {
|
||||
return !this.isFusion()
|
||||
? this.variant
|
||||
: (Math.max(this.variant, this.fusionVariant) as Variant);
|
||||
/**
|
||||
*
|
||||
* @param useIllusion - Whether we want the fake or real shininess (illusion ability).
|
||||
* @returns `true` if the {@linkcode Pokemon} is shiny and the fusion is shiny as well, `false` otherwise
|
||||
*/
|
||||
isDoubleShiny(useIllusion: boolean = false): boolean {
|
||||
if (!useIllusion && !!this.summonData?.illusion) {
|
||||
return this.isFusion(false) && this.summonData?.illusion.basePokemon.shiny && this.summonData?.illusion.basePokemon.fusionShiny;
|
||||
} else {
|
||||
return this.isFusion(useIllusion) && this.shiny && this.fusionShiny;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fake or real variant (illusion ability).
|
||||
*/
|
||||
getVariant(useIllusion: boolean = false): Variant {
|
||||
if (!useIllusion && !!this.summonData?.illusion) {
|
||||
return !this.isFusion(false)
|
||||
? this.summonData?.illusion.basePokemon!.variant
|
||||
: Math.max(this.variant, this.fusionVariant) as Variant;
|
||||
} else {
|
||||
return !this.isFusion(true)
|
||||
? this.variant
|
||||
: Math.max(this.variant, this.fusionVariant) as Variant;
|
||||
}
|
||||
}
|
||||
|
||||
getBaseVariant(doubleShiny: boolean): Variant {
|
||||
if (doubleShiny) {
|
||||
return !!this.summonData?.illusion
|
||||
? this.summonData?.illusion.basePokemon!.variant
|
||||
: this.variant;
|
||||
} else {
|
||||
return this.getVariant();
|
||||
}
|
||||
}
|
||||
|
||||
getLuck(): number {
|
||||
return this.luck + (this.isFusion() ? this.fusionLuck : 0);
|
||||
}
|
||||
|
||||
isFusion(): boolean {
|
||||
return !!this.fusionSpecies;
|
||||
isFusion(useIllusion: boolean = false): boolean {
|
||||
if (useIllusion && !!this.summonData?.illusion) {
|
||||
return !!this.summonData?.illusion.fusionSpecies;
|
||||
} else {
|
||||
return !!this.fusionSpecies;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} useIllusion - Whether we want the fake name or the real name of the Pokemon (for Illusion ability).
|
||||
*/
|
||||
getName(useIllusion: boolean = false): string {
|
||||
return (!useIllusion && !!this.summonData?.illusion && this.summonData?.illusion.basePokemon)
|
||||
? this.summonData?.illusion.basePokemon.name
|
||||
: this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1796,12 +1991,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
* @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`
|
||||
* @param useIllusion - `true` to return the types of the illusion instead of the actual types; "AUTO" will depend on forDefend param; Default: "AUTO"
|
||||
* @returns array of {@linkcode PokemonType}
|
||||
*/
|
||||
public getTypes(
|
||||
includeTeraType = false,
|
||||
forDefend = false,
|
||||
ignoreOverride = false,
|
||||
includeTeraType = false,
|
||||
forDefend: boolean = false,
|
||||
ignoreOverride?: boolean,
|
||||
useIllusion: boolean | "AUTO" = "AUTO"
|
||||
): PokemonType[] {
|
||||
const types: PokemonType[] = [];
|
||||
|
||||
|
@ -1815,17 +2012,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!types.length || !includeTeraType) {
|
||||
|
||||
const doIllusion: boolean = (useIllusion === "AUTO") ? !forDefend : useIllusion;
|
||||
if (
|
||||
!ignoreOverride &&
|
||||
this.summonData?.types &&
|
||||
this.summonData.types.length > 0
|
||||
!ignoreOverride &&
|
||||
this.summonData?.types &&
|
||||
this.summonData.types.length > 0 &&
|
||||
(!this.summonData?.illusion || !doIllusion)
|
||||
) {
|
||||
this.summonData.types.forEach(t => types.push(t));
|
||||
} else {
|
||||
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
||||
const speciesForm = this.getSpeciesForm(ignoreOverride, doIllusion);
|
||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride, doIllusion);
|
||||
const customTypes = this.customPokemonData.types?.length > 0;
|
||||
|
||||
// First type, checking for "permanently changed" types from ME
|
||||
|
@ -2378,6 +2577,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
* @param ignoreAbility Whether to ignore abilities that might affect type effectiveness or immunity (defaults to `false`).
|
||||
* @param simulated Whether to apply abilities via simulated calls (defaults to `true`)
|
||||
* @param cancelled {@linkcode BooleanHolder} Stores whether the move was cancelled by a non-type-based immunity.
|
||||
* @param useIllusion - Whether we want the attack move effectiveness on the illusion or not
|
||||
* Currently only used by {@linkcode Pokemon.apply} to determine whether a "No effect" message should be shown.
|
||||
* @returns The type damage multiplier, indicating the effectiveness of the move
|
||||
*/
|
||||
|
@ -2387,6 +2587,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
ignoreAbility = false,
|
||||
simulated = true,
|
||||
cancelled?: BooleanHolder,
|
||||
useIllusion: boolean = false
|
||||
): TypeDamageMultiplier {
|
||||
if (!isNullOrUndefined(this.turnData?.moveEffectiveness)) {
|
||||
return this.turnData?.moveEffectiveness;
|
||||
|
@ -2398,17 +2599,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
const moveType = source.getMoveType(move);
|
||||
|
||||
const typeMultiplier = new NumberHolder(
|
||||
move.category !== MoveCategory.STATUS ||
|
||||
move.hasAttr(RespectAttackTypeImmunityAttr)
|
||||
? this.getAttackTypeEffectiveness(
|
||||
moveType,
|
||||
source,
|
||||
false,
|
||||
simulated,
|
||||
move,
|
||||
)
|
||||
: 1,
|
||||
);
|
||||
move.category !== MoveCategory.STATUS ||
|
||||
move.hasAttr(RespectAttackTypeImmunityAttr)
|
||||
? this.getAttackTypeEffectiveness(
|
||||
moveType,
|
||||
source,
|
||||
false,
|
||||
simulated,
|
||||
move,
|
||||
useIllusion
|
||||
)
|
||||
: 1);
|
||||
|
||||
applyMoveAttrs(
|
||||
VariableMoveTypeMultiplierAttr,
|
||||
|
@ -2512,19 +2713,21 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
* @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks)
|
||||
* @param simulated tag to only apply the strong winds effect message when the move is used
|
||||
* @param move (optional) the move whose type effectiveness is to be checked. Used for applying {@linkcode VariableMoveTypeChartAttr}
|
||||
* @param useIllusion - Whether we want the attack type effectiveness on the illusion or not
|
||||
* @returns a multiplier for the type effectiveness
|
||||
*/
|
||||
getAttackTypeEffectiveness(
|
||||
moveType: PokemonType,
|
||||
source?: Pokemon,
|
||||
ignoreStrongWinds = false,
|
||||
simulated = true,
|
||||
move?: Move,
|
||||
moveType: PokemonType,
|
||||
source?: Pokemon,
|
||||
ignoreStrongWinds: boolean = false,
|
||||
simulated: boolean = true,
|
||||
move?: Move,
|
||||
useIllusion: boolean = false
|
||||
): TypeDamageMultiplier {
|
||||
if (moveType === PokemonType.STELLAR) {
|
||||
return this.isTerastallized ? 2 : 1;
|
||||
}
|
||||
const types = this.getTypes(true, true);
|
||||
const types = this.getTypes(true, true, undefined, useIllusion);
|
||||
const arena = globalScene.arena;
|
||||
|
||||
// Handle flying v ground type immunity without removing flying type so effective types are still effective
|
||||
|
@ -2623,7 +2826,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
*/
|
||||
getMatchupScore(opponent: Pokemon): number {
|
||||
const types = this.getTypes(true);
|
||||
const enemyTypes = opponent.getTypes(true, true);
|
||||
|
||||
const enemyTypes = opponent.getTypes(true, true, false, true);
|
||||
/** Is this Pokemon faster than the opponent? */
|
||||
const outspeed =
|
||||
(this.isActive(true)
|
||||
|
@ -2634,9 +2838,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
* Based on how effective this Pokemon's types are offensively against the opponent's types.
|
||||
* This score is increased by 25 percent if this Pokemon is faster than the opponent.
|
||||
*/
|
||||
let atkScore =
|
||||
opponent.getAttackTypeEffectiveness(types[0], this) *
|
||||
(outspeed ? 1.25 : 1);
|
||||
let atkScore = opponent.getAttackTypeEffectiveness(types[0], this, false, true, undefined, true) * (outspeed ? 1.25 : 1);
|
||||
/**
|
||||
* Based on how effectively this Pokemon defends against the opponent's types.
|
||||
* This score cannot be higher than 4.
|
||||
|
@ -2648,12 +2850,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
atkScore *= opponent.getAttackTypeEffectiveness(types[1], this);
|
||||
}
|
||||
if (enemyTypes.length > 1) {
|
||||
defScore *=
|
||||
1 /
|
||||
Math.max(
|
||||
this.getAttackTypeEffectiveness(enemyTypes[1], opponent),
|
||||
0.25,
|
||||
);
|
||||
defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent, false, false, undefined, true), 0.25));
|
||||
}
|
||||
/**
|
||||
* Based on this Pokemon's HP ratio compared to that of the opponent.
|
||||
|
@ -5538,7 +5735,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
this.summonDataPrimer = summonDataPrimer;
|
||||
}
|
||||
|
||||
// For PreSummonAbAttr to get access to summonData
|
||||
initSummondata(): void {
|
||||
this.summonData = this.summonData ?? this.summonDataPrimer ?? new PokemonSummonData()
|
||||
}
|
||||
|
||||
resetSummonData(): void {
|
||||
const illusion: IllusionData | null = this.summonData?.illusion;
|
||||
if (this.summonData?.speciesForm) {
|
||||
this.summonData.speciesForm = null;
|
||||
this.updateFusionPalette();
|
||||
|
@ -5574,6 +5777,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
}
|
||||
this.summonDataPrimer = null;
|
||||
}
|
||||
this.summonData.illusion = illusion
|
||||
this.updateInfo();
|
||||
}
|
||||
|
||||
|
@ -7146,15 +7350,11 @@ export class EnemyPokemon extends Pokemon {
|
|||
) {
|
||||
targetScore = -20;
|
||||
} else if (move instanceof AttackMove) {
|
||||
/**
|
||||
* Attack moves are given extra multipliers to their base benefit score based on
|
||||
* the move's type effectiveness against the target and whether the move is a STAB move.
|
||||
*/
|
||||
const effectiveness = target.getMoveEffectiveness(
|
||||
this,
|
||||
move,
|
||||
!target.battleData?.abilityRevealed,
|
||||
);
|
||||
/**
|
||||
* Attack moves are given extra multipliers to their base benefit score based on
|
||||
* the move's type effectiveness against the target and whether the move is a STAB move.
|
||||
*/
|
||||
const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed, undefined, undefined, true);
|
||||
if (target.isPlayer() !== this.isPlayer()) {
|
||||
targetScore *= effectiveness;
|
||||
if (this.isOfType(move.type)) {
|
||||
|
@ -7543,6 +7743,42 @@ export class EnemyPokemon extends Pokemon {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Illusion property
|
||||
*/
|
||||
interface IllusionData {
|
||||
basePokemon: {
|
||||
/** The actual name of the Pokemon */
|
||||
name: string;
|
||||
/** The actual nickname of the Pokemon */
|
||||
nickname: string;
|
||||
/** Whether the base pokemon is shiny or not */
|
||||
shiny: boolean;
|
||||
/** The shiny variant of the base pokemon */
|
||||
variant: Variant;
|
||||
/** Whether the fusion species of the base pokemon is shiny or not */
|
||||
fusionShiny: boolean;
|
||||
/** The variant of the fusion species of the base pokemon */
|
||||
fusionVariant: Variant;
|
||||
};
|
||||
/** The species of the illusion */
|
||||
species: Species;
|
||||
/** The formIndex of the illusion */
|
||||
formIndex: number;
|
||||
/** The gender of the illusion */
|
||||
gender: Gender;
|
||||
/** The pokeball of the illusion */
|
||||
pokeball: PokeballType;
|
||||
/** The fusion species of the illusion if it's a fusion */
|
||||
fusionSpecies?: PokemonSpecies;
|
||||
/** The fusionFormIndex of the illusion */
|
||||
fusionFormIndex?: number;
|
||||
/** The fusionGender of the illusion if it's a fusion */
|
||||
fusionGender?: Gender;
|
||||
/** The level of the illusion (not used currently) */
|
||||
level?: number
|
||||
}
|
||||
|
||||
export interface TurnMove {
|
||||
move: Moves;
|
||||
targets: BattlerIndex[];
|
||||
|
@ -7576,9 +7812,12 @@ export class PokemonSummonData {
|
|||
public fusionGender: Gender;
|
||||
public stats: number[] = [0, 0, 0, 0, 0, 0];
|
||||
public moveset: PokemonMove[];
|
||||
public illusionBroken: boolean = false;
|
||||
|
||||
// If not initialized this value will not be populated from save data.
|
||||
public types: PokemonType[] = [];
|
||||
public addedType: PokemonType | null = null;
|
||||
public illusion: IllusionData | null = null;
|
||||
}
|
||||
|
||||
export class PokemonBattleData {
|
||||
|
@ -7589,7 +7828,7 @@ export class PokemonBattleData {
|
|||
public endured = false;
|
||||
public berriesEaten: BerryType[] = [];
|
||||
public abilitiesApplied: Abilities[] = [];
|
||||
public abilityRevealed = false;
|
||||
public abilityRevealed: boolean = false;
|
||||
}
|
||||
|
||||
export class PokemonBattleSummonData {
|
||||
|
|
|
@ -6,9 +6,10 @@ import i18next from "i18next";
|
|||
/**
|
||||
* Retrieves the Pokemon's name, potentially with an affix indicating its role (wild or foe) in the current battle context, translated
|
||||
* @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance
|
||||
* @param {boolean} useIllusion - Whether we want the name of the illusion or not. Default value : true
|
||||
* @returns {string} ex: "Wild Gengar", "Ectoplasma sauvage"
|
||||
*/
|
||||
export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string {
|
||||
export function getPokemonNameWithAffix(pokemon: Pokemon | undefined, useIllusion = true): string {
|
||||
if (!pokemon) {
|
||||
return "Missigno";
|
||||
}
|
||||
|
@ -18,19 +19,17 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string {
|
|||
return !pokemon.isPlayer()
|
||||
? pokemon.hasTrainer()
|
||||
? i18next.t("battle:foePokemonWithAffix", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
pokemonName: pokemon.getNameToRender(useIllusion),
|
||||
})
|
||||
: i18next.t("battle:wildPokemonWithAffix", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
pokemonName: pokemon.getNameToRender(useIllusion),
|
||||
})
|
||||
: pokemon.getNameToRender();
|
||||
: pokemon.getNameToRender(useIllusion);
|
||||
case BattleSpec.FINAL_BOSS:
|
||||
return !pokemon.isPlayer()
|
||||
? i18next.t("battle:foePokemonWithAffix", {
|
||||
pokemonName: pokemon.getNameToRender(),
|
||||
})
|
||||
: pokemon.getNameToRender();
|
||||
? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) })
|
||||
: pokemon.getNameToRender(useIllusion);
|
||||
default:
|
||||
return pokemon.getNameToRender();
|
||||
return pokemon.getNameToRender(useIllusion);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { BattlerIndex, BattleType } from "#app/battle";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
import { applyAbAttrs, SyncEncounterNatureAbAttr } from "#app/data/ability";
|
||||
import { applyAbAttrs, SyncEncounterNatureAbAttr, applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/ability";
|
||||
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
||||
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
@ -259,6 +259,9 @@ export class EncounterPhase extends BattlePhase {
|
|||
}
|
||||
if (e < (battle.double ? 2 : 1)) {
|
||||
if (battle.battleType === BattleType.WILD) {
|
||||
for (const pokemon of globalScene.getField()) {
|
||||
applyPreSummonAbAttrs(PreSummonAbAttr, pokemon, []);
|
||||
}
|
||||
globalScene.field.add(enemyPokemon);
|
||||
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
|
||||
const playerPokemon = globalScene.getPlayerPokemon();
|
||||
|
@ -545,7 +548,7 @@ export class EncounterPhase extends BattlePhase {
|
|||
const enemyField = globalScene.getEnemyField();
|
||||
|
||||
enemyField.forEach((enemyPokemon, e) => {
|
||||
if (enemyPokemon.isShiny()) {
|
||||
if (enemyPokemon.isShiny(true)) {
|
||||
globalScene.unshiftPhase(new ShinySparklePhase(BattlerIndex.ENEMY + e));
|
||||
}
|
||||
/** This sets Eternatus' held item to be untransferrable, preventing it from being stolen */
|
||||
|
|
|
@ -71,6 +71,7 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase {
|
|||
if (!this.pokemon.pauseEvolutions) {
|
||||
const evolution = this.pokemon.getEvolution();
|
||||
if (evolution) {
|
||||
this.pokemon.breakIllusion()
|
||||
globalScene.unshiftPhase(new EvolutionPhase(this.pokemon, evolution, this.lastLevel));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { PostSummonPhase } from "./post-summon-phase";
|
|||
import { GameOverPhase } from "./game-over-phase";
|
||||
import { ShinySparklePhase } from "./shiny-sparkle-phase";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/ability";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
|
||||
export class SummonPhase extends PartyMemberPokemonPhase {
|
||||
|
@ -27,6 +28,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||
start() {
|
||||
super.start();
|
||||
|
||||
applyPreSummonAbAttrs(PreSummonAbAttr, this.getPokemon());
|
||||
this.preSummon();
|
||||
}
|
||||
|
||||
|
@ -126,7 +128,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||
this.player ? 36 : 248,
|
||||
this.player ? 80 : 44,
|
||||
"pb",
|
||||
getPokeballAtlasKey(pokemon.pokeball),
|
||||
getPokeballAtlasKey(pokemon.getPokeball(true)),
|
||||
);
|
||||
pokeball.setVisible(false);
|
||||
pokeball.setOrigin(0.5, 0.625);
|
||||
|
@ -175,7 +177,11 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||
}
|
||||
globalScene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id);
|
||||
}
|
||||
addPokeballOpenParticles(pokemon.x, pokemon.y - 16, pokemon.pokeball);
|
||||
addPokeballOpenParticles(
|
||||
pokemon.x,
|
||||
pokemon.y - 16,
|
||||
pokemon.getPokeball(true),
|
||||
);
|
||||
globalScene.updateModifiers(this.player);
|
||||
globalScene.updateFieldScale();
|
||||
pokemon.showInfo();
|
||||
|
@ -183,7 +189,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||
pokemon.setVisible(true);
|
||||
pokemon.getSprite().setVisible(true);
|
||||
pokemon.setScale(0.5);
|
||||
pokemon.tint(getPokeballTintColor(pokemon.pokeball));
|
||||
pokemon.tint(getPokeballTintColor(pokemon.getPokeball(true)));
|
||||
pokemon.untint(250, "Sine.easeIn");
|
||||
globalScene.updateFieldScale();
|
||||
globalScene.tweens.add({
|
||||
|
@ -270,7 +276,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||
onEnd(): void {
|
||||
const pokemon = this.getPokemon();
|
||||
|
||||
if (pokemon.isShiny()) {
|
||||
if (pokemon.isShiny(true)) {
|
||||
globalScene.unshiftPhase(new ShinySparklePhase(pokemon.getBattlerIndex()));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { globalScene } from "#app/global-scene";
|
||||
import { applyPreSwitchOutAbAttrs, PostDamageForceSwitchAbAttr, PreSwitchOutAbAttr } from "#app/data/ability";
|
||||
import {
|
||||
applyPreSummonAbAttrs,
|
||||
applyPreSwitchOutAbAttrs,
|
||||
PostDamageForceSwitchAbAttr,
|
||||
PreSummonAbAttr,
|
||||
PreSwitchOutAbAttr,
|
||||
} from "#app/data/ability";
|
||||
import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move";
|
||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||
|
@ -99,7 +105,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||
);
|
||||
globalScene.playSound("se/pb_rel");
|
||||
pokemon.hideInfo();
|
||||
pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn");
|
||||
pokemon.tint(getPokeballTintColor(pokemon.getPokeball(true)), 1, 250, "Sine.easeIn");
|
||||
globalScene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 250,
|
||||
|
@ -116,6 +122,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||
const party = this.player ? this.getParty() : globalScene.getEnemyParty();
|
||||
const switchedInPokemon = party[this.slotIndex];
|
||||
this.lastPokemon = this.getPokemon();
|
||||
applyPreSummonAbAttrs(PreSummonAbAttr, switchedInPokemon);
|
||||
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
||||
if (this.switchType === SwitchType.BATON_PASS && switchedInPokemon) {
|
||||
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon =>
|
||||
|
|
|
@ -1426,12 +1426,11 @@ export class GameData {
|
|||
),
|
||||
) // TODO: is this bang correct?
|
||||
: this.getSessionSaveData();
|
||||
|
||||
const maxIntAttrValue = 0x80000000;
|
||||
const systemData = useCachedSystem
|
||||
? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser?.username}`)!, bypassLogin))
|
||||
: this.getSystemSaveData(); // TODO: is this bang correct?
|
||||
|
||||
|
||||
const request = {
|
||||
system: systemData,
|
||||
session: sessionData,
|
||||
|
@ -1448,7 +1447,6 @@ export class GameData {
|
|||
bypassLogin,
|
||||
),
|
||||
);
|
||||
|
||||
localStorage.setItem(
|
||||
`sessionData${globalScene.sessionSlotId ? globalScene.sessionSlotId : ""}_${loggedInUser?.username}`,
|
||||
encrypt(JSON.stringify(sessionData), bypassLogin),
|
||||
|
|
|
@ -79,12 +79,14 @@ export default class PokemonData {
|
|||
this.id = source.id;
|
||||
this.player = sourcePokemon ? sourcePokemon.isPlayer() : source.player;
|
||||
this.species = sourcePokemon ? sourcePokemon.species.speciesId : source.species;
|
||||
this.nickname = sourcePokemon ? sourcePokemon.nickname : source.nickname;
|
||||
this.nickname = sourcePokemon
|
||||
? (!!sourcePokemon.summonData?.illusion ? sourcePokemon.summonData.illusion.basePokemon.nickname : sourcePokemon.nickname)
|
||||
: source.nickname;
|
||||
this.formIndex = Math.max(Math.min(source.formIndex, getPokemonSpecies(this.species).forms.length - 1), 0);
|
||||
this.abilityIndex = source.abilityIndex;
|
||||
this.passive = source.passive;
|
||||
this.shiny = source.shiny;
|
||||
this.variant = source.variant;
|
||||
this.shiny = sourcePokemon ? sourcePokemon.isShiny() : source.shiny;
|
||||
this.variant = sourcePokemon ? sourcePokemon.getVariant() : source.variant;
|
||||
this.pokeball = source.pokeball;
|
||||
this.level = source.level;
|
||||
this.exp = source.exp;
|
||||
|
@ -117,8 +119,12 @@ export default class PokemonData {
|
|||
this.fusionSpecies = sourcePokemon ? sourcePokemon.fusionSpecies?.speciesId : source.fusionSpecies;
|
||||
this.fusionFormIndex = source.fusionFormIndex;
|
||||
this.fusionAbilityIndex = source.fusionAbilityIndex;
|
||||
this.fusionShiny = source.fusionShiny;
|
||||
this.fusionVariant = source.fusionVariant;
|
||||
this.fusionShiny = sourcePokemon
|
||||
? (!!sourcePokemon.summonData?.illusion ? sourcePokemon.summonData.illusion.basePokemon.fusionShiny : sourcePokemon.fusionShiny)
|
||||
: source.fusionShiny;
|
||||
this.fusionVariant = sourcePokemon
|
||||
? (!!sourcePokemon.summonData?.illusion ? sourcePokemon.summonData.illusion.basePokemon.fusionVariant : sourcePokemon.fusionVariant)
|
||||
: source.fusionVariant;
|
||||
this.fusionGender = source.fusionGender;
|
||||
this.fusionLuck =
|
||||
source.fusionLuck !== undefined ? source.fusionLuck : source.fusionShiny ? source.fusionVariant + 1 : 0;
|
||||
|
@ -174,6 +180,7 @@ export default class PokemonData {
|
|||
this.summonData.types = source.summonData.types;
|
||||
this.summonData.speciesForm = source.summonData.speciesForm;
|
||||
this.summonDataSpeciesFormIndex = source.summonDataSpeciesFormIndex;
|
||||
this.summonData.illusionBroken = source.summonData.illusionBroken;
|
||||
|
||||
if (source.summonData.tags) {
|
||||
this.summonData.tags = source.summonData.tags?.map(t => loadBattlerTag(t));
|
||||
|
@ -219,6 +226,7 @@ export default class PokemonData {
|
|||
if (this.summonData) {
|
||||
// when loading from saved session, recover summonData.speciesFrom and form index species object
|
||||
// used to stay transformed on reload session
|
||||
|
||||
if (this.summonData.speciesForm) {
|
||||
this.summonData.speciesForm = getPokemonSpeciesForm(
|
||||
this.summonData.speciesForm.speciesId,
|
||||
|
|
|
@ -356,7 +356,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
});
|
||||
this.teraIcon.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||
|
||||
const isFusion = pokemon.isFusion();
|
||||
const isFusion = pokemon.isFusion(true);
|
||||
|
||||
this.splicedIcon.setPositionRelative(
|
||||
this.nameText,
|
||||
|
@ -375,7 +375,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny;
|
||||
const baseVariant = !doubleShiny ? pokemon.getVariant() : pokemon.variant;
|
||||
const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant;
|
||||
|
||||
this.shinyIcon.setPositionRelative(
|
||||
this.nameText,
|
||||
|
@ -617,6 +617,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
return resolve();
|
||||
}
|
||||
|
||||
const gender: Gender = !!pokemon.summonData?.illusion ? pokemon.summonData?.illusion.gender : pokemon.gender;
|
||||
|
||||
this.genderText.setText(getGenderSymbol(gender));
|
||||
this.genderText.setColor(getGenderColor(gender));
|
||||
|
||||
const nameUpdated = this.lastName !== pokemon.getNameToRender();
|
||||
|
||||
if (nameUpdated) {
|
||||
|
@ -638,8 +643,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
this.lastTeraType = teraType;
|
||||
}
|
||||
|
||||
const isFusion = pokemon.isFusion(true);
|
||||
|
||||
if (nameUpdated || teraTypeUpdated) {
|
||||
this.splicedIcon.setVisible(!!pokemon.fusionSpecies);
|
||||
this.splicedIcon.setVisible(isFusion);
|
||||
|
||||
this.teraIcon.setPositionRelative(
|
||||
this.nameText,
|
||||
|
@ -764,7 +771,17 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
this.lastStats = statsStr;
|
||||
}
|
||||
|
||||
this.shinyIcon.setVisible(pokemon.isShiny());
|
||||
this.shinyIcon.setVisible(pokemon.isShiny(true));
|
||||
|
||||
const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny;
|
||||
const baseVariant = !doubleShiny ? pokemon.getVariant(true) : pokemon.variant;
|
||||
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
||||
|
||||
this.fusionShinyIcon.setVisible(doubleShiny);
|
||||
if (isFusion) {
|
||||
this.fusionShinyIcon.setTint(getVariantTint(pokemon.fusionVariant));
|
||||
}
|
||||
this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
@ -777,10 +794,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
|
|||
const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.BATTLE_INFO);
|
||||
nameTextWidth = nameSizeTest.displayWidth;
|
||||
|
||||
const gender: Gender = !!pokemon.summonData?.illusion ? pokemon.summonData?.illusion.gender : pokemon.gender;
|
||||
while (
|
||||
nameTextWidth >
|
||||
(this.player || !this.boss ? 60 : 98) -
|
||||
((pokemon.gender !== Gender.GENDERLESS ? 6 : 0) +
|
||||
((gender !== Gender.GENDERLESS ? 6 : 0) +
|
||||
(pokemon.fusionSpecies ? 8 : 0) +
|
||||
(pokemon.isShiny() ? 8 : 0) +
|
||||
(Math.min(pokemon.level.toString().length, 3) - 3) * 8)
|
||||
|
|
|
@ -306,6 +306,9 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
|
|||
pokemon,
|
||||
pokemonMove.getMove(),
|
||||
!opponent.battleData?.abilityRevealed,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
if (effectiveness === undefined) {
|
||||
return undefined;
|
||||
|
@ -350,7 +353,7 @@ export default class FightUiHandler extends UiHandler implements InfoToggle {
|
|||
|
||||
const moveColors = opponents
|
||||
.map(opponent =>
|
||||
opponent.getMoveEffectiveness(pokemon, pokemonMove.getMove(), !opponent.battleData.abilityRevealed),
|
||||
opponent.getMoveEffectiveness(pokemon, pokemonMove.getMove(), !opponent.battleData.abilityRevealed, undefined, undefined, true),
|
||||
)
|
||||
.sort((a, b) => b - a)
|
||||
.map(effectiveness => getTypeDamageMultiplierColor(effectiveness ?? 0, "offense"));
|
||||
|
|
|
@ -193,18 +193,14 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
|
||||
public static FilterNonFainted = (pokemon: PlayerPokemon) => {
|
||||
if (pokemon.isFainted()) {
|
||||
return i18next.t("partyUiHandler:noEnergy", {
|
||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
return i18next.t("partyUiHandler:noEnergy", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
public static FilterFainted = (pokemon: PlayerPokemon) => {
|
||||
if (!pokemon.isFainted()) {
|
||||
return i18next.t("partyUiHandler:hasEnergy", {
|
||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
return i18next.t("partyUiHandler:hasEnergy", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
@ -218,9 +214,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
const challengeAllowed = new BooleanHolder(true);
|
||||
applyChallenges(ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed);
|
||||
if (!challengeAllowed.value) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", {
|
||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
@ -232,9 +226,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier),
|
||||
) as PokemonHeldItemModifier;
|
||||
if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount()) {
|
||||
return i18next.t("partyUiHandler:tooManyItems", {
|
||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||
});
|
||||
return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
@ -583,7 +575,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
this.showText(
|
||||
i18next.t(
|
||||
pokemon.pauseEvolutions ? "partyUiHandler:pausedEvolutions" : "partyUiHandler:unpausedEvolutions",
|
||||
{ pokemonName: getPokemonNameWithAffix(pokemon) },
|
||||
{ pokemonName: getPokemonNameWithAffix(pokemon, false) },
|
||||
),
|
||||
undefined,
|
||||
() => this.showText("", 0),
|
||||
|
@ -596,14 +588,14 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
this.showText(
|
||||
i18next.t("partyUiHandler:unspliceConfirmation", {
|
||||
fusionName: pokemon.fusionSpecies?.name,
|
||||
pokemonName: pokemon.name,
|
||||
pokemonName: pokemon.getName(),
|
||||
}),
|
||||
null,
|
||||
() => {
|
||||
ui.setModeWithoutClear(
|
||||
Mode.CONFIRM,
|
||||
() => {
|
||||
const fusionName = pokemon.name;
|
||||
const fusionName = pokemon.getName();
|
||||
pokemon.unfuse().then(() => {
|
||||
this.clearPartySlots();
|
||||
this.populatePartySlots();
|
||||
|
@ -611,7 +603,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
this.showText(
|
||||
i18next.t("partyUiHandler:wasReverted", {
|
||||
fusionName: fusionName,
|
||||
pokemonName: pokemon.name,
|
||||
pokemonName: pokemon.getName(false),
|
||||
}),
|
||||
undefined,
|
||||
() => {
|
||||
|
@ -637,7 +629,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
this.blockInput = true;
|
||||
this.showText(
|
||||
i18next.t("partyUiHandler:releaseConfirmation", {
|
||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
||||
pokemonName: getPokemonNameWithAffix(pokemon, false),
|
||||
}),
|
||||
null,
|
||||
() => {
|
||||
|
@ -1285,7 +1277,7 @@ export default class PartyUiHandler extends MessageUiHandler {
|
|||
|
||||
doRelease(slotIndex: number): void {
|
||||
this.showText(
|
||||
this.getReleaseMessage(getPokemonNameWithAffix(globalScene.getPlayerParty()[slotIndex])),
|
||||
this.getReleaseMessage(getPokemonNameWithAffix(globalScene.getPlayerParty()[slotIndex], false)),
|
||||
null,
|
||||
() => {
|
||||
this.clearPartySlots();
|
||||
|
@ -1495,7 +1487,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||
const slotInfoContainer = globalScene.add.container(0, 0);
|
||||
this.add(slotInfoContainer);
|
||||
|
||||
let displayName = this.pokemon.getNameToRender();
|
||||
let displayName = this.pokemon.getNameToRender(false);
|
||||
let nameTextWidth: number;
|
||||
|
||||
const nameSizeTest = addTextObject(0, 0, displayName, TextStyle.PARTY);
|
||||
|
@ -1575,12 +1567,12 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||
}
|
||||
|
||||
if (this.pokemon.isShiny()) {
|
||||
const doubleShiny = this.pokemon.isFusion() && this.pokemon.shiny && this.pokemon.fusionShiny;
|
||||
const doubleShiny = this.pokemon.isDoubleShiny(false);
|
||||
|
||||
const shinyStar = globalScene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`);
|
||||
shinyStar.setOrigin(0, 0);
|
||||
shinyStar.setPositionRelative(this.slotName, -9, 3);
|
||||
shinyStar.setTint(getVariantTint(!doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant));
|
||||
shinyStar.setTint(getVariantTint(this.pokemon.getBaseVariant(doubleShiny)));
|
||||
|
||||
slotInfoContainer.add(shinyStar);
|
||||
|
||||
|
@ -1588,7 +1580,9 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||
const fusionShinyStar = globalScene.add.image(0, 0, "shiny_star_small_2");
|
||||
fusionShinyStar.setOrigin(0, 0);
|
||||
fusionShinyStar.setPosition(shinyStar.x, shinyStar.y);
|
||||
fusionShinyStar.setTint(getVariantTint(this.pokemon.fusionVariant));
|
||||
fusionShinyStar.setTint(
|
||||
getVariantTint(this.pokemon.summonData?.illusion?.basePokemon.fusionVariant ?? this.pokemon.fusionVariant),
|
||||
);
|
||||
|
||||
slotInfoContainer.add(fusionShinyStar);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ export default class RenameFormUiHandler extends FormModalUiHandler {
|
|||
if (super.show(args)) {
|
||||
const config = args[0] as ModalConfig;
|
||||
if (args[1] && typeof (args[1] as PlayerPokemon).getNameToRender === "function") {
|
||||
this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender();
|
||||
this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender(false);
|
||||
} else {
|
||||
this.inputs[0].text = args[1];
|
||||
}
|
||||
|
|
|
@ -357,8 +357,14 @@ export default class SummaryUiHandler extends UiHandler {
|
|||
this.pokemonSprite.setPipelineData("isTerastallized", this.pokemon.isTerastallized);
|
||||
this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
|
||||
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
||||
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
|
||||
this.pokemonSprite.setPipelineData("variant", this.pokemon.variant);
|
||||
this.pokemonSprite.setPipelineData(
|
||||
"shiny",
|
||||
this.pokemon.summonData?.illusion?.basePokemon.shiny ?? this.pokemon.shiny,
|
||||
);
|
||||
this.pokemonSprite.setPipelineData(
|
||||
"variant",
|
||||
this.pokemon.summonData?.illusion?.basePokemon.variant ?? this.pokemon.variant,
|
||||
);
|
||||
["spriteColors", "fusionSpriteColors"].map(k => {
|
||||
delete this.pokemonSprite.pipelineData[`${k}Base`];
|
||||
if (this.pokemon?.summonData?.speciesForm) {
|
||||
|
@ -368,7 +374,7 @@ export default class SummaryUiHandler extends UiHandler {
|
|||
});
|
||||
this.pokemon.cry();
|
||||
|
||||
this.nameText.setText(this.pokemon.getNameToRender());
|
||||
this.nameText.setText(this.pokemon.getNameToRender(false));
|
||||
|
||||
const isFusion = this.pokemon.isFusion();
|
||||
|
||||
|
@ -426,8 +432,8 @@ export default class SummaryUiHandler extends UiHandler {
|
|||
|
||||
this.friendshipShadow.setCrop(0, 0, 16, 16 - 16 * ((this.pokemon?.friendship || 0) / 255));
|
||||
|
||||
const doubleShiny = isFusion && this.pokemon.shiny && this.pokemon.fusionShiny;
|
||||
const baseVariant = !doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant;
|
||||
const doubleShiny = this.pokemon.isDoubleShiny(false);
|
||||
const baseVariant = this.pokemon.getBaseVariant(doubleShiny);
|
||||
|
||||
this.shinyIcon.setPositionRelative(
|
||||
this.nameText,
|
||||
|
@ -435,7 +441,7 @@ export default class SummaryUiHandler extends UiHandler {
|
|||
3,
|
||||
);
|
||||
this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`);
|
||||
this.shinyIcon.setVisible(this.pokemon.isShiny());
|
||||
this.shinyIcon.setVisible(this.pokemon.isShiny(false));
|
||||
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
||||
if (this.shinyIcon.visible) {
|
||||
const shinyDescriptor =
|
||||
|
@ -455,7 +461,9 @@ export default class SummaryUiHandler extends UiHandler {
|
|||
this.fusionShinyIcon.setPosition(this.shinyIcon.x, this.shinyIcon.y);
|
||||
this.fusionShinyIcon.setVisible(doubleShiny);
|
||||
if (isFusion) {
|
||||
this.fusionShinyIcon.setTint(getVariantTint(this.pokemon.fusionVariant));
|
||||
this.fusionShinyIcon.setTint(
|
||||
getVariantTint(this.pokemon.summonData?.illusion?.basePokemon.fusionVariant ?? this.pokemon.fusionVariant),
|
||||
);
|
||||
}
|
||||
|
||||
this.pokeball.setFrame(getPokeballAtlasKey(this.pokemon.pokeball));
|
||||
|
@ -838,7 +846,7 @@ export default class SummaryUiHandler extends UiHandler {
|
|||
return typeIcon;
|
||||
};
|
||||
|
||||
const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct?
|
||||
const types = this.pokemon?.getTypes(false, false, true, false)!; // TODO: is this bang correct?
|
||||
profileContainer.add(getTypeIcon(0, types[0]));
|
||||
if (types.length > 1) {
|
||||
profileContainer.add(getTypeIcon(1, types[1]));
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import { Species } from "#enums/species";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { PokeballType } from "#app/enums/pokeball";
|
||||
import { Gender } from "#app/data/gender";
|
||||
|
||||
describe("Abilities - Illusion", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single");
|
||||
game.override.enemySpecies(Species.ZORUA);
|
||||
game.override.enemyAbility(Abilities.ILLUSION);
|
||||
game.override.enemyMoveset(Moves.TACKLE);
|
||||
game.override.enemyHeldItems([{ name: "WIDE_LENS", count: 3 }]);
|
||||
|
||||
game.override.moveset([Moves.WORRY_SEED, Moves.SOAK, Moves.TACKLE]);
|
||||
game.override.startingHeldItems([{ name: "WIDE_LENS", count: 3 }]);
|
||||
});
|
||||
|
||||
it("creates illusion at the start", async () => {
|
||||
await game.classicMode.startBattle([Species.ZOROARK, Species.AXEW]);
|
||||
const zoroark = game.scene.getPlayerPokemon()!;
|
||||
const zorua = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(!!zoroark.summonData?.illusion).equals(true);
|
||||
expect(!!zorua.summonData?.illusion).equals(true);
|
||||
});
|
||||
|
||||
it("break after receiving damaging move", async () => {
|
||||
await game.classicMode.startBattle([Species.AXEW]);
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
const zorua = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(!!zorua.summonData?.illusion).equals(false);
|
||||
expect(zorua.name).equals("Zorua");
|
||||
});
|
||||
|
||||
it("break after getting ability changed", async () => {
|
||||
await game.classicMode.startBattle([Species.AXEW]);
|
||||
game.move.select(Moves.WORRY_SEED);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
const zorua = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(!!zorua.summonData?.illusion).equals(false);
|
||||
});
|
||||
|
||||
it("break if the ability is suppressed", async () => {
|
||||
game.override.enemyAbility(Abilities.NEUTRALIZING_GAS);
|
||||
await game.classicMode.startBattle([Species.KOFFING]);
|
||||
|
||||
const zorua = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(!!zorua.summonData?.illusion).equals(false);
|
||||
});
|
||||
|
||||
it("causes enemy AI to consider the illusion's type instead of the actual type when considering move effectiveness", async () => {
|
||||
game.override.enemyMoveset([Moves.FLAMETHROWER, Moves.PSYCHIC, Moves.TACKLE]);
|
||||
await game.classicMode.startBattle([Species.ZOROARK, Species.AXEW]);
|
||||
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
const zoroark = game.scene.getPlayerPokemon()!;
|
||||
|
||||
const flameThrower = enemy.getMoveset()[0]!.getMove();
|
||||
const psychic = enemy.getMoveset()[1]!.getMove();
|
||||
const flameThrowerEffectiveness = zoroark.getAttackTypeEffectiveness(
|
||||
flameThrower.type,
|
||||
enemy,
|
||||
undefined,
|
||||
undefined,
|
||||
flameThrower,
|
||||
true,
|
||||
);
|
||||
const psychicEffectiveness = zoroark.getAttackTypeEffectiveness(
|
||||
psychic.type,
|
||||
enemy,
|
||||
undefined,
|
||||
undefined,
|
||||
psychic,
|
||||
true,
|
||||
);
|
||||
expect(psychicEffectiveness).above(flameThrowerEffectiveness);
|
||||
});
|
||||
|
||||
it("does not break from indirect damage", async () => {
|
||||
game.override.enemySpecies(Species.GIGALITH);
|
||||
game.override.enemyAbility(Abilities.SAND_STREAM);
|
||||
game.override.enemyMoveset(Moves.WILL_O_WISP);
|
||||
game.override.moveset([Moves.FLARE_BLITZ]);
|
||||
|
||||
await game.classicMode.startBattle([Species.ZOROARK, Species.AZUMARILL]);
|
||||
|
||||
game.move.select(Moves.FLARE_BLITZ);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
const zoroark = game.scene.getPlayerPokemon()!;
|
||||
|
||||
expect(!!zoroark.summonData?.illusion).equals(true);
|
||||
});
|
||||
|
||||
it("copies the the name, nickname, gender, shininess, and pokeball from the illusion source", async () => {
|
||||
game.override.enemyMoveset(Moves.SPLASH);
|
||||
await game.classicMode.startBattle([Species.ABRA, Species.ZOROARK, Species.AXEW]);
|
||||
const axew = game.scene.getPlayerParty().at(2)!;
|
||||
axew.shiny = true;
|
||||
axew.nickname = btoa(unescape(encodeURIComponent("axew nickname")));
|
||||
axew.gender = Gender.FEMALE;
|
||||
axew.pokeball = PokeballType.GREAT_BALL;
|
||||
|
||||
game.doSwitchPokemon(1);
|
||||
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
|
||||
const zoroark = game.scene.getPlayerPokemon()!;
|
||||
|
||||
expect(zoroark.name).equals("Axew");
|
||||
expect(zoroark.getNameToRender()).equals("axew nickname");
|
||||
expect(zoroark.getGender(false, true)).equals(Gender.FEMALE);
|
||||
expect(zoroark.isShiny(true)).equals(true);
|
||||
expect(zoroark.getPokeball(true)).equals(PokeballType.GREAT_BALL);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue