implement illusion ability with unit test and localizations

This commit is contained in:
Lylian 2024-07-31 22:32:20 +02:00
parent bfc44ea35e
commit 9f26ea983b
23 changed files with 674 additions and 118 deletions

View File

@ -851,7 +851,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);

120
src/data/ability.ts Normal file → Executable file
View File

@ -1885,7 +1885,6 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr {
return true;
}
}
export class PostSummonMessageAbAttr extends PostSummonAbAttr {
private messageFunc: (pokemon: Pokemon) => string;
@ -1898,6 +1897,11 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr {
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
pokemon.scene.queueMessage(this.messageFunc(pokemon));
pokemon.scene.getField(true).map(pokemon => {
if (pokemon.breakIllusion()) {
pokemon.scene.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(pokemon) }));
}
});
return true;
}
}
@ -2262,6 +2266,10 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
target = targets[0];
}
if (target.illusion.active) {
return false;
}
pokemon.summonData.speciesForm = target.getSpeciesForm();
pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
pokemon.summonData.ability = target.getAbility().id;
@ -3554,8 +3562,8 @@ export class MaxMultiHitAbAttr extends AbAttr {
}
export class PostBattleAbAttr extends AbAttr {
constructor() {
super(true);
constructor(showAbility: boolean = true) {
super(showAbility);
}
applyPostBattle(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
@ -4041,6 +4049,97 @@ export class IceFaceBlockPhysicalAbAttr extends ReceivedMoveDamageMultiplierAbAt
}
}
export class PreSummonAbAttr extends AbAttr {
applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
return false;
}
}
export class IllusionPreSummonAbAttr extends PreSummonAbAttr {
/**
* Apply a new illusion when summoning Zoroark if the illusion is available
*
* @param {Pokemon} pokemon - The Pokémon with the Illusion ability.
* @param {boolean} passive - N/A
* @param {...any} args - N/A
* @returns {boolean} - Whether the illusion was applied.
*/
applyPreSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
let suppressed = false;
pokemon.scene.getField(true).filter(p => p !== pokemon).map(p => {
if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) {
suppressed = true;
}
if (p.getPassiveAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility(true)) {
suppressed = true;
}
});
if (pokemon.illusion.available && !suppressed) {
return pokemon.generateIllusion();
} else {
return false;
}
}
}
export class IllusionBreakAbAttr extends PostDefendAbAttr {
/**
* Destroy illusion if attack move deals damage to zoroark
*
* @param {Pokemon} pokemon - The Pokémon with the Illusion ability.
* @param {boolean} passive - N/A
* @param {Pokemon} attacker - The attacking Pokémon.
* @param {PokemonMove} move - The move being used.
* @param {PokemonMove} hitResult - The type of hitResult the pokemon got
* @param {...any} args - N/A
* @returns {boolean} - Whether the illusion was destroyed.
*/
applyPostDefend(pokemon: Pokemon, passive: 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];
if (!breakIllusion.includes(hitResult)) {
return false;
}
pokemon.breakIllusion();
pokemon.scene.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(pokemon) }));
return true;
}
}
export class IllusionAfterBattle extends PostBattleAbAttr {
/**
* Illusion will be available again after a battle and apply the illusion of the pokemon is already on field
*
* @param {Pokemon} pokemon - The Pokémon with the Illusion ability.
* @param {boolean} passive - N/A
* @param {...any} args - N/A
* @returns {boolean} - Whether the illusion was applied.
*/
applyPostBattle(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
pokemon.breakIllusion();
pokemon.illusion.available = true;
return true;
}
}
export class IllusionDisableAbAttr extends PostSummonAbAttr {
/**
* Illusion will be disabled if the pokemon is summoned with an illusion.
* So the pokemon can use 1 illusion per battle.
*
* @param {Pokemon} pokemon - The Pokémon with the Illusion ability.
* @param {boolean} passive - N/A
* @param {...any} args - N/A
* @returns {boolean}
*/
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
pokemon.illusion.available = false;
return true;
}
}
/**
* 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).
*
@ -4216,6 +4315,11 @@ export function applyPostSummonAbAttrs(attrType: Constructor<PostSummonAbAttr>,
return applyAbAttrsInternal<PostSummonAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSummon(pokemon, passive, args), args);
}
export function applyPreSummonAbAttrs(attrType: Constructor<PreSummonAbAttr>,
pokemon: Pokemon, ...args: any[]) {
return applyAbAttrsInternal<PreSummonAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSummon(pokemon, passive, args), args);
}
export function applyPreSwitchOutAbAttrs(attrType: Constructor<PreSwitchOutAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreSwitchOutAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, args), args, true);
@ -4759,7 +4863,15 @@ export function initAbilities() {
new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.unimplemented(),
//The pokemon generate an illusion if it's available
.conditionalAttr((pokemon) => pokemon.illusion.available, IllusionPreSummonAbAttr, false)
//The pokemon loses his illusion when he is damaged by a move
.conditionalAttr((pokemon) => pokemon.illusion.active, IllusionBreakAbAttr, true)
//Illusion is available again after a battle
.conditionalAttr((pokemon) => pokemon.isAllowedInBattle(), IllusionAfterBattle, false)
//Illusion is not available after summon
.attr(IllusionDisableAbAttr, false)
.bypassFaint(),
new Ability(Abilities.IMPOSTER, 5)
.attr(PostSummonTransformAbAttr)
.attr(UncopiableAbilityAbAttr),

View File

@ -5486,10 +5486,12 @@ export class AbilityChangeAttr extends MoveEffectAttr {
return false;
}
(this.selfTarget ? user : target).summonData.ability = this.ability;
user.scene.queueMessage(i18next.t("moveTriggers:acquiredAbility", {pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name}));
const pokemon: Pokemon = this.selfTarget ? user : target;
pokemon.summonData.ability = this.ability;
if (pokemon.breakIllusion()) {
pokemon.scene.queueMessage(i18next.t("abilityTriggers:illusionBreak", { pokemonName: getPokemonNameWithAffix(pokemon) }));
}
user.scene.queueMessage(i18next.t("moveTriggers:acquiredAbility", {pokemonName: getPokemonNameWithAffix(pokemon), abilityName: allAbilities[this.ability].name}));
return true;
}
@ -5641,7 +5643,8 @@ export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr {
export class TransformAttr extends MoveEffectAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return new Promise(resolve => {
if (!super.apply(user, target, move, args)) {
if (!super.apply(user, target, move, args) || target.illusion.active || user.illusion.active) {
user.scene.queueMessage(i18next.t("battle:attackFailed"));
return resolve(false);
}

View File

@ -63,6 +63,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public name: string;
public nickname: string;
public species: PokemonSpecies;
public illusion: Illusion;
public formIndex: integer;
public abilityIndex: integer;
public passive: boolean;
@ -126,6 +127,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const randAbilityIndex = Utils.randSeedInt(2);
this.species = species;
this.illusion = {active: false, available: true};
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level;
// Determine the ability index
@ -240,15 +243,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
getNameToRender() {
getNameToRender(useIllusion: boolean = true) {
const name: string = (!useIllusion && this.illusion.active) ? this.illusion.name : this.name;
const nickname: string = (!useIllusion && this.illusion.active) ? this.illusion.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;
}
}
@ -276,7 +281,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();
}
}
@ -337,6 +342,113 @@ 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.
*
* @param {Pokemon} pokemon - The Pokemon that will create an illusion.
* @param {Pokemon[]} party - The party of the trainer's pokemon.
*/
generateIllusion(): boolean {
if (this.hasTrainer()) {
const party: Pokemon[] = (this.isPlayer() ? this.scene.getParty() : this.scene.getEnemyParty()).filter(p => p.isAllowedInBattle());
const lastPokemon: Pokemon = party.at(-1);
const speciesId = lastPokemon.species.speciesId;
if ( lastPokemon === this || this.illusion.active ||
((speciesId === Species.OGERPON || speciesId === Species.TERAPAGOS) && (lastPokemon.isTerastallized() || this.isTerastallized()))) {
return false;
}
this.illusion = {
active: true,
available: true,
name: this.name,
nickname: this.nickname,
species: getPokemonSpecies(speciesId),
formIndex: lastPokemon.formIndex,
shiny: this.shiny,
variant: this.variant,
shinySparkle: this.shinySparkle,
gender: lastPokemon.gender,
pokeball: lastPokemon.pokeball,
fusionFormIndex: lastPokemon.fusionFormIndex,
fusionSpecies: lastPokemon.fusionSpecies,
fusionVariant: this.fusionVariant,
fusionShiny: this.fusionShiny,
fusionGender: lastPokemon.fusionGender
};
this.name = lastPokemon.name;
this.nickname = lastPokemon.nickname;
this.shiny = lastPokemon.shiny;
this.variant = lastPokemon.variant;
this.fusionVariant = lastPokemon.fusionVariant;
this.fusionShiny = lastPokemon.fusionShiny;
if (this.shiny) {
this.initShinySparkle();
}
this.loadAssets(false, true).then(() => this.playAnim());
} else {
let availables: Species[] = [];
if (this.isBoss()) {
availables = [Species.ENTEI, Species.RAIKOU, Species.SUICUNE];
} else {
const area: Species[] = [
Species.HOUNDOUR, Species.SABLEYE, Species.PURRLOIN, Species.PAWNIARD, Species.NICKIT,
Species.IMPIDIMP, Species.MASCHIFF, Species.ABSOL, Species.SPIRITOMB, Species.DEINO,
];
for (let species of area) {
for (const evolutionLevel of getPokemonSpecies(species).getEvolutionLevels()) {
if (evolutionLevel[1] && evolutionLevel[1] <= this.level) {
species = evolutionLevel[0];
}
}
availables.push(species);
}
availables.push(this.species.name === getPokemonSpecies(Species.ZORUA).name ? Species.MURKROW : Species.HONCHKROW);
}
const randomIllusion: PokemonSpecies = getPokemonSpecies(availables[this.randSeedInt(availables.length)]);
this.illusion = {
active: true,
available: true,
species: randomIllusion,
name: this.name,
shiny: this.shiny,
variant: this.variant,
shinySparkle: this.shinySparkle,
gender: this.gender
};
this.name = randomIllusion.name;
this.loadAssets(false, true).then(() => this.playAnim());
}
return true;
}
breakIllusion(): boolean {
if (!this.illusion.active) {
return false;
}
this.name = this.illusion.name;
this.nickname = this.illusion.nickname;
this.shiny = this.illusion.shiny;
this.variant = this.illusion.variant;
this.fusionVariant = this.illusion.fusionVariant;
this.fusionShiny = this.illusion.fusionShiny;
this.illusion = {active: false, available: false};
if (this.isOnField) {
this.scene.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;
@ -345,18 +457,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
abstract getBattlerIndex(): BattlerIndex;
loadAssets(ignoreOverride: boolean = true): Promise<void> {
/**
* @param {boolean} useIllusion - Whether we want the illusion or not.
*/
loadAssets(ignoreOverride: boolean = true, useIllusion: boolean = false): Promise<void> {
return new Promise(resolve => {
const moveIds = this.getMoveset().map(m => m.getMove().id);
Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m)))
.then(() => {
loadMoveAnimAssets(this.scene, moveIds);
this.getSpeciesForm().loadAssets(this.scene, this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant);
if (this.isPlayer() || this.getFusionSpeciesForm()) {
const formIndex = this.illusion.active && useIllusion ? this.illusion.formIndex : this.formIndex;
this.getSpeciesForm(false, useIllusion).loadAssets(this.scene, this.getGender(useIllusion) === Gender.FEMALE, formIndex, this.isShiny(useIllusion), this.getVariant(useIllusion));
if (this.isPlayer() || this.getFusionSpeciesForm(false, useIllusion)) {
this.scene.loadPokemonAtlas(this.getBattleSpriteKey(true, ignoreOverride), this.getBattleSpriteAtlasPath(true, ignoreOverride));
}
if (this.getFusionSpeciesForm()) {
this.getFusionSpeciesForm().loadAssets(this.scene, this.getFusionGender() === Gender.FEMALE, this.fusionFormIndex, this.fusionShiny, this.fusionVariant);
if (this.getFusionSpeciesForm(false, useIllusion)) {
const fusionFormIndex = this.illusion.active && useIllusion ? this.illusion.fusionFormIndex : this.fusionFormIndex;
const fusionShiny = this.illusion.active && !useIllusion ? this.illusion.fusionShiny : this.fusionShiny;
const fusionVariant = this.illusion.active && !useIllusion ? this.illusion.fusionVariant : this.fusionVariant;
this.getFusionSpeciesForm(false, useIllusion).loadAssets(this.scene, this.getFusionGender(false, useIllusion) === Gender.FEMALE, fusionFormIndex, fusionShiny, fusionVariant);
this.scene.loadPokemonAtlas(this.getFusionBattleSpriteKey(true, ignoreOverride), this.getFusionBattleSpriteAtlasPath(true, ignoreOverride));
}
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
@ -456,18 +575,27 @@ 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.illusion.active ? this.illusion.formIndex : this.formIndex;
return this.getSpeciesForm(ignoreOverride, true).getSpriteId(this.getGender(ignoreOverride, true) === Gender.FEMALE, formIndex, this.shiny, this.variant);
}
getBattleSpriteId(back?: boolean, ignoreOverride?: boolean): string {
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.illusion.active ? this.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(this.getGender(ignoreOverride) === Gender.FEMALE, this.formIndex, this.shiny, this.variant);
return this.getSpeciesForm(ignoreOverride, false).getSpriteKey(
this.getGender(ignoreOverride) === Gender.FEMALE,
this.formIndex,
this.illusion.shiny ?? this.shiny,
this.illusion.variant ?? this.variant
);
}
getBattleSpriteKey(back?: boolean, ignoreOverride?: boolean): string {
@ -475,14 +603,18 @@ 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.illusion.active ? this.illusion.fusionFormIndex : this.fusionFormIndex;
return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId(this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, fusionFormIndex, this.fusionShiny, this.fusionVariant);
}
getFusionBattleSpriteId(back?: boolean, ignoreOverride?: boolean): string {
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.illusion.active ? this.illusion.fusionFormIndex : this.fusionFormIndex;
return this.getFusionSpeciesForm(ignoreOverride, true).getSpriteId(this.getFusionGender(ignoreOverride, true) === Gender.FEMALE, fusionFormIndex, this.fusionShiny, this.fusionVariant, back);
}
getFusionBattleSpriteKey(back?: boolean, ignoreOverride?: boolean): string {
@ -494,39 +626,56 @@ 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.illusion.active ? this.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.illusion.active ? this.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.illusion.active ? this.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.illusion.active ? this.illusion.species : this.species;
const formIndex: integer = useIllusion && this.illusion.active ? this.illusion.formIndex : this.formIndex;
if (!ignoreOverride && this.summonData?.speciesForm) {
return this.summonData.speciesForm;
}
if (!this.species.forms?.length) {
return this.species;
}
return this.species.forms[this.formIndex];
if (!species.forms?.length || formIndex === undefined) {
return species;
}
getFusionSpeciesForm(ignoreOverride?: boolean): PokemonSpeciesForm {
return species.forms[formIndex];
}
/**
* @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.illusion.active ? this.illusion.fusionSpecies : this.fusionSpecies;
const fusionFormIndex: integer = useIllusion && this.illusion.active ? this.illusion.fusionFormIndex : this.fusionFormIndex;
if (!ignoreOverride && this.summonData?.speciesForm) {
return this.summonData.fusionSpeciesForm;
}
if (!this.fusionSpecies?.forms?.length || this.fusionFormIndex >= this.fusionSpecies?.forms.length) {
return this.fusionSpecies;
if (!fusionSpecies?.forms?.length || fusionFormIndex >= fusionSpecies?.forms.length) {
return fusionSpecies;
}
return this.fusionSpecies?.forms[this.fusionFormIndex];
return fusionSpecies?.forms[fusionFormIndex];
}
getSprite(): Phaser.GameObjects.Sprite {
@ -835,35 +984,78 @@ 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 gender of the illusion or not.
*/
getGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender {
if (useIllusion && this.illusion.active) {
return this.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 fusionGender of the illusion or not.
*/
getFusionGender(ignoreOverride?: boolean, useIllusion: boolean = false): Gender {
if (useIllusion && this.illusion.active) {
return this.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);
isShiny(useIllusion: boolean = false): boolean {
if (!useIllusion && this.illusion.active) {
return this.illusion.shiny || (!!this.illusion.fusionSpecies && this.illusion.fusionShiny);
} else {
return this.shiny || (this.isFusion(useIllusion) && this.fusionShiny);
}
}
getVariant(): Variant {
return !this.isFusion() ? this.variant : Math.max(this.variant, this.fusionVariant) as Variant;
isDoubleShiny(useIllusion: boolean = false): boolean {
if (!useIllusion && this.illusion.active) {
return this.isFusion(false) && this.illusion.shiny && this.illusion.fusionShiny;
} else {
return this.isFusion(useIllusion) && this.shiny && this.fusionShiny;
}
}
getVariant(useIllusion: boolean = false): Variant {
if (!useIllusion && this.illusion.active) {
return !this.isFusion(false) ? this.illusion.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.illusion.active ? this.illusion.variant : this.variant;
} else {
return this.getVariant();
}
}
getLuck(): integer {
return this.luck + (this.isFusion() ? this.fusionLuck : 0);
}
isFusion(): boolean {
isFusion(useIllusion: boolean = false): boolean {
if (useIllusion && this.illusion.active) {
return !!this.illusion.fusionSpecies;
} else {
return !!this.fusionSpecies;
}
}
getName(illusion: boolean = false): string {
return (!illusion && this.illusion.active) ? this.illusion.name : this.name;
}
abstract isBoss(): boolean;
@ -894,30 +1086,29 @@ 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 {boolean} includeTeraType to include tera-formed type, default false
* @param {boolean} forDefend if the pokemon is defending from an attack
* @param {boolean} ignoreOverride if true, ignore ability changing effects
* @param {boolean} useIllusion whether we want he types of the illusion or not
* @returns array of {@linkcode Type}
*/
getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] {
const types = [];
getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean, useIllusion: boolean | "AUTO" = "AUTO"): Type[] {
const types: Type[] = [];
if (includeTeraType) {
const teraType = this.getTeraType();
if (teraType !== Type.UNKNOWN) {
types.push(teraType);
}
}
if (!types.length || !includeTeraType) {
if (!ignoreOverride && this.summonData?.types) {
const doIllusion: boolean = useIllusion === "AUTO" ? !forDefend : useIllusion;
if (!ignoreOverride && this.summonData?.types && (!this.illusion.active || !doIllusion)) {
this.summonData.types.forEach(t => types.push(t));
} else {
const speciesForm = this.getSpeciesForm(ignoreOverride);
const speciesForm = this.getSpeciesForm(ignoreOverride, doIllusion);
types.push(speciesForm.type1);
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride, doIllusion);
if (fusionSpeciesForm) {
if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) {
types.push(fusionSpeciesForm.type2);
@ -925,7 +1116,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
types.push(fusionSpeciesForm.type1);
}
}
if (types.length === 1 && speciesForm.type2 !== null) {
types.push(speciesForm.type2);
}
@ -951,7 +1141,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
types.splice(index, 1);
}
}
return types;
}
@ -1179,12 +1368,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param source - The attacking Pokémon.
* @param pokemonMove - The move being used by the attacking Pokémon.
* @param ignoreAbility - Whether to check for abilities that might affect type effectiveness or immunity.
* @param {boolean} useIllusion - Whether we want the attack move effectiveness on the illusion or not
* @returns The type damage multiplier, indicating the effectiveness of the move
*/
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove, ignoreAbility: boolean = false): TypeDamageMultiplier {
getAttackMoveEffectiveness(source: Pokemon, pokemonMove: PokemonMove, ignoreAbility: boolean = false, useIllusion: boolean = false): TypeDamageMultiplier {
const move = pokemonMove.getMove();
const typeless = move.hasAttr(TypelessAttr);
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move, source));
const typeMultiplier = new Utils.NumberHolder(this.getAttackTypeEffectiveness(move, source, undefined, undefined, useIllusion));
const cancelled = new Utils.BooleanHolder(false);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (!typeless && !ignoreAbility) {
@ -1203,9 +1393,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param source the Pokemon using the move
* @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 {boolean} useIllusion - Whether we want the attack type effectiveness on the illusion or not
* @returns a multiplier for the type effectiveness
*/
getAttackTypeEffectiveness(moveOrType: Move | Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier {
getAttackTypeEffectiveness(moveOrType: Move | Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true, useIllusion: boolean = false): TypeDamageMultiplier {
const move = (moveOrType instanceof Move)
? moveOrType
: undefined;
@ -1216,7 +1407,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (moveType === Type.STELLAR) {
return this.isTerastallized() ? 2 : 1;
}
const types = this.getTypes(true, true);
const types = this.getTypes(true, true, undefined, useIllusion);
let multiplier = types.map(defType => {
if (source) {
@ -1253,12 +1444,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getMatchupScore(pokemon: Pokemon): number {
const types = this.getTypes(true);
const enemyTypes = pokemon.getTypes(true, true);
const enemyTypes = pokemon.getTypes(true, true, false, true);
const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, pokemon) : this.getStat(Stat.SPD)) <= pokemon.getBattleStat(Stat.SPD, this);
let atkScore = pokemon.getAttackTypeEffectiveness(types[0], this) * (outspeed ? 1.25 : 1);
let atkScore = pokemon.getAttackTypeEffectiveness(types[0], this, false, false, true) * (outspeed ? 1.25 : 1);
let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], pokemon), 0.25);
if (types.length > 1) {
atkScore *= pokemon.getAttackTypeEffectiveness(types[1], this);
atkScore *= pokemon.getAttackTypeEffectiveness(types[1], this, false, false, true);
}
if (enemyTypes.length > 1) {
defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], pokemon), 0.25));
@ -3720,7 +3911,7 @@ export class EnemyPokemon extends Pokemon {
if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id)) {
targetScore = -20;
} else if (move instanceof AttackMove) {
const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove);
const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove, false, true);
if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness;
if (this.isOfType(move.type)) {
@ -4017,6 +4208,91 @@ export class EnemyPokemon extends Pokemon {
}
}
/**
* Illusion property
*/
interface Illusion {
/**
* Whether the illusion is active or not.
* @type {boolean}
*/
active: boolean;
/**
* Whether the pokemon can generate an illusion or not.
* @type {boolean}
*/
available: boolean;
/**
* The stored name of the pokemon.
* @type {string}
*/
name?: string;
/**
* The stored nickname of the pokemon.
* @type {string}
*/
nickname?: string;
/**
* The species of the illusion.
* @type {PokemonSpecies}
*/
species?: PokemonSpecies;
/**
* The formIndex of the illusion
* @type {integer}
*/
formIndex?: integer;
/**
* Store whether the base pokemon is shiny or not.
* @type {boolean}
*/
shiny?: boolean;
/**
* The shiny variant of the base pokemon.
* @type {Variant}
*/
variant?: Variant;
/**
* The shinysparkles of the base pokemon.
* @type {Phaser.GameObjects.Sprite}
*/
shinySparkle?: Phaser.GameObjects.Sprite;
/**
* The gender of the illusion
* @type {Gender}
*/
gender?: Gender;
/**
* The pokeball of the illusion.
*/
pokeball?: PokeballType;
/**
* The fusionned species of the illusion if it's a fusion.
* @type {PokemonSpecies}
*/
fusionSpecies?: PokemonSpecies;
/**
* The fusionFormIndex of the illusion
* @type {integer}
*/
fusionFormIndex?: integer;
/**
* Whether the fusionned species of the base pokemon is shiny or not.
* @type {PokemonSpecies}
*/
fusionShiny?: boolean;
/**
* The variant of the fusionned species of the base pokemon.
* @type {Variant}
*/
fusionVariant?: Variant;
/**
* The fusionGender of the illusion if it's a fusion
* @type {Gender}
*/
fusionGender?: Gender;
}
export interface TurnMove {
move: Moves;
targets?: BattlerIndex[];

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} kopiert {{abilityName}} von {{targetName}}!",
"windPowerCharged": "Der Treffer durch {{moveName}} läd die Stärke von {{pokemonName}} auf!",
"quickDraw": "Durch Schnellschuss kann {{pokemonName}} schneller handeln als sonst!",
"illusionBreak": "Das Trugbild von {{pokemonName}} verschwindet!",
"blockItemTheft": "{{abilityName}} von {{pokemonNameWithAffix}} verhindert Item-Diebstahl!",
"typeImmunityHeal": "{{abilityName}} von {{pokemonNameWithAffix}} füllte einige KP auf!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} vermeidet Schaden mit {{abilityName}}!",

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"illusionBreak": "{{pokemonName}}'s illusion wore off!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",

View File

@ -11,6 +11,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"illusionBreak": "¡La ilusión de {{pokemonName}} se ha desvanecido!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
"postDefendDisguise": "{{pokemonNameWithAffix}}'s disguise was busted!",

View File

@ -9,6 +9,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"poisonHeal": "{{abilityName}} de {{pokemonName}}\nrestaure un peu ses PV !",
"trace": "{{pokemonName}} copie le talent {{abilityName}}\nde {{targetName}} !",
"windPowerCharged": "{{pokemonName}} a été touché par la capacité {{moveName}} et se charge en électricité !",
"illusionBreak": "Lillusion de {{pokemonName}} se brise !",
"quickDraw": "Tir Vif permet à {{pokemonName}}\ndagir plus vite que dhabitude !",
"blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nempêche son objet dêtre volé !",
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaure un peu ses PV !",

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "L'abilità {{abilityName}} di {{targetName}}\nviene copiata da {{pokemonName}} con Traccia!",
"windPowerCharged": "Venire colpito da {{moveName}} ha caricato {{pokemonName}}!",
"quickDraw":"{{pokemonName}} agisce più rapidamente del normale grazie a Colpolesto!",
"illusionBreak": "L'illusione di {{pokemonName}} si dissolve!",
"blockItemTheft": "{{abilityName}} di {{pokemonNameWithAffix}}\nlo rende immune ai furti!",
"typeImmunityHeal": "{{pokemonName}} recupera alcuni PS\ncon {{abilityName}}!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evita il colpo\ncon {{abilityName}}!",

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}}[[는]] 상대 {{targetName}}의 \n{{abilityName}}[[를]] 트레이스했다!",
"windPowerCharged": "{{pokemonName}}[[는]]\n{{moveName}}에 맞아 충전되었다!",
"quickDraw": "{{pokemonName}}[[는]]\n퀵드로에 의해 행동이 빨라졌다!",
"illusionBreak": "{{pokemonName}}의\n일루전이 풀렸다!",
"blockItemTheft": "{{pokemonNameWithAffix}}의 {{abilityName}}에 의해\n도구를 빼앗기지 않는다!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}[[는]]\n{{abilityName}}[[로]] 체력이 회복되었다!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}[[는]] {{abilityName}} 때문에\n데미지를 입지 않는다!",

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} copiou {{abilityName}}\nde {{targetName}}!",
"windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!",
"quickDraw": "{{pokemonName}} pode agir mais rápido que o normal\ngraças ao seu Quick Draw!",
"illusionBreak": "A ilusão de {{pokemonName}} acabou!",
"blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nprevine o roubo de itens!",
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaurou um pouco de PS!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evitou dano\ncom {{abilityName}}!",

View File

@ -1,5 +1,6 @@
import { ability } from "./ability";
import { abilityTriggers } from "./ability-trigger";
import { arenaFlyout } from "./arena-flyout";
import { PGFachv, PGMachv } from "./achv";
import { arenaFlyout } from "./arena-flyout";
import { arenaTag } from "./arena-tag";

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}}复制了{{targetName}}的\n{{abilityName}}",
"windPowerCharged": "受{{moveName}}的影响,{{pokemonName}}提升了能力!",
"quickDraw":"因为速击效果发动,\n{{pokemonName}}比平常出招更快了!",
"illusionBreak": "{{pokemonName}}造成的\n幻觉被解除了",
"blockItemTheft": "{{pokemonNameWithAffix}}的{{abilityName}}\n阻止了对方夺取道具",
"typeImmunityHeal": "{{pokemonNameWithAffix}}因{{abilityName}}\n回复了少许HP",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}}因{{abilityName}}\n避免了伤害",

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} 複製了 {{targetName}} 的\n{{abilityName}}!",
"windPowerCharged": "受 {{moveName}} 的影響, {{pokemonName}} 提升了能力!",
"quickDraw":"{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"illusionBreak": "{{pokemonName}}造成的\n幻覺解除了",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",

View File

@ -18,23 +18,23 @@ export function getPokemonMessage(pokemon: Pokemon, content: string): string {
* @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance
* @returns {string} ex: "Wild Gengar", "Ectoplasma sauvage"
*/
export function getPokemonNameWithAffix(pokemon: Pokemon): string {
export function getPokemonNameWithAffix(pokemon: Pokemon, useIllusion: boolean = true): string {
switch (pokemon.scene.currentBattle.battleSpec) {
case BattleSpec.DEFAULT:
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);
}
}

View File

@ -25,7 +25,10 @@ import { Starter } from "./ui/starter-select-ui-handler";
import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { CheckTrappedAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability";
import {
CheckTrappedAbAttr, PreSummonAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr,
applyAbAttrs, applyCheckTrappedAbAttrs, applyPreSummonAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr
} from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -842,6 +845,7 @@ export class EncounterPhase extends BattlePhase {
});
}
}
const enemyPokemon = this.scene.getEnemyParty()[e];
if (e < (battle.double ? 2 : 1)) {
enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]);
@ -895,6 +899,8 @@ export class EncounterPhase extends BattlePhase {
battle.enemyParty.forEach((enemyPokemon, e) => {
if (e < (battle.double ? 2 : 1)) {
if (battle.battleType === BattleType.WILD) {
applyPreSummonAbAttrs(PreSummonAbAttr, enemyPokemon, []);
this.scene.field.add(enemyPokemon);
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
const playerPokemon = this.scene.getPlayerPokemon();
@ -1058,7 +1064,7 @@ export class EncounterPhase extends BattlePhase {
const enemyField = this.scene.getEnemyField();
enemyField.forEach((enemyPokemon, e) => {
if (enemyPokemon.isShiny()) {
if (enemyPokemon.isShiny(true)) {
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e));
}
});
@ -1378,7 +1384,8 @@ export class SummonPhase extends PartyMemberPokemonPhase {
start() {
super.start();
this.preSummon();
const pokemon = this.getPokemon();
applyPreSummonAbAttrs(PreSummonAbAttr, pokemon).then(() => this.preSummon());
}
/**
@ -1447,8 +1454,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
summon(): void {
const pokemon = this.getPokemon();
const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball));
const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.illusion.pokeball ?? pokemon.pokeball));
pokeball.setVisible(false);
pokeball.setOrigin(0.5, 0.625);
this.scene.field.add(pokeball);
@ -1494,7 +1500,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
}
this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id);
}
addPokeballOpenParticles(this.scene, pokemon.x, pokemon.y - 16, pokemon.pokeball);
addPokeballOpenParticles(this.scene, pokemon.x, pokemon.y - 16, pokemon.illusion.pokeball ?? pokemon.pokeball);
this.scene.updateModifiers(this.player);
this.scene.updateFieldScale();
pokemon.showInfo();
@ -1502,7 +1508,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.illusion.pokeball ?? pokemon.pokeball));
pokemon.untint(250, "Sine.easeIn");
this.scene.updateFieldScale();
this.scene.tweens.add({
@ -1526,7 +1532,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
onEnd(): void {
const pokemon = this.getPokemon();
if (pokemon.isShiny()) {
if (pokemon.isShiny(true)) {
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, pokemon.getBattlerIndex()));
}
@ -1587,7 +1593,10 @@ export class SwitchSummonPhase extends SummonPhase {
this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty());
}
}
const pokemon: Pokemon = this.getPokemon();
// if doReturn === False OR slotIndex !== -1 (slotIndex is valid) and the pokemon doesn't exist/is false
// then switchAndSummon(), manually pick pokemon to switch into
if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getParty() : this.scene.getEnemyParty())[this.slotIndex])) {
if (this.player) {
return this.switchAndSummon();
@ -1597,8 +1606,6 @@ export class SwitchSummonPhase extends SummonPhase {
}
}
const pokemon = this.getPokemon();
if (!this.batonPass) {
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
}
@ -1612,7 +1619,7 @@ export class SwitchSummonPhase extends SummonPhase {
);
this.scene.playSound("pb_rel");
pokemon.hideInfo();
pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn");
pokemon.tint(getPokeballTintColor(pokemon.illusion.pokeball ?? pokemon.pokeball), 1, 250, "Sine.easeIn");
this.scene.tweens.add({
targets: pokemon,
duration: 250,
@ -1631,6 +1638,8 @@ export class SwitchSummonPhase extends SummonPhase {
const party = this.player ? this.getParty() : this.scene.getEnemyParty();
const switchedPokemon = party[this.slotIndex];
this.lastPokemon = this.getPokemon();
applyPreSummonAbAttrs(PreSummonAbAttr, switchedPokemon);
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
if (this.batonPass && switchedPokemon) {
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedPokemon.id));
@ -4470,6 +4479,7 @@ export class SwitchPhase extends BattlePhase {
}
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
}, PartyUiHandler.FilterNonFainted);
}
}

View File

@ -0,0 +1,127 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Gender } from "../../data/gender";
import { PokeballType } from "../../data/pokeball";
import {
TurnEndPhase,
} from "#app/phases";
import { Moves } from "#enums/moves";
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
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);
vi.spyOn(overrides, "BATTLE_TYPE_OVERRIDE", "get").mockReturnValue("single");
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ZORUA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ILLUSION);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
vi.spyOn(overrides, "OPP_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "WIDE_LENS", count: 3}]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WORRY_SEED, Moves.SOAK, Moves.TACKLE, Moves.TACKLE]);
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "WIDE_LENS", count: 3}]);
});
it("create illusion at the start", async () => {
await game.startBattle([Species.ZOROARK, Species.AXEW]);
const zoroark = game.scene.getPlayerPokemon();
const zorua = game.scene.getEnemyPokemon();
expect(zoroark.illusion.active).equals(true);
expect(zorua.illusion.active).equals(true);
expect(zoroark.illusion.available).equals(false);
});
it("disappear after receiving damaging move and changing ability move", async () => {
await game.startBattle([Species.ZOROARK, Species.AXEW]);
game.doAttack(getMovePosition(game.scene, 0, Moves.WORRY_SEED));
await game.phaseInterceptor.to(TurnEndPhase);
const zoroark = game.scene.getPlayerPokemon();
const zorua = game.scene.getEnemyPokemon();
expect(zorua.illusion.active).equals(false);
expect(zoroark.illusion.active).equals(false);
});
it("disappear if the ability is suppressed", async () => {
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NEUTRALIZING_GAS);
await game.startBattle([Species.KOFFING]);
const zorua = game.scene.getEnemyPokemon();
expect(zorua.illusion.active).equals(false);
});
it("trick the enemy AI", async () => {
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.FLAMETHROWER, Moves.PSYCHIC, Moves.TACKLE, Moves.TACKLE]);
await game.startBattle([Species.ZOROARK, Species.AXEW]);
const enemy = game.scene.getEnemyPokemon();
const zoroark = game.scene.getPlayerPokemon();
const flameThwowerEffectiveness = zoroark.getAttackMoveEffectiveness(enemy, enemy.getMoveset()[0], false, true);
const psychicEffectiveness = zoroark.getAttackMoveEffectiveness(enemy, enemy.getMoveset()[1], false, true);
expect(psychicEffectiveness).above(flameThwowerEffectiveness);
});
it("do not disappear if the pokemon takes indirect damages", async () => {
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GIGALITH);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SAND_STREAM);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WILL_O_WISP, Moves.WILL_O_WISP, Moves.WILL_O_WISP, Moves.WILL_O_WISP]);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.FLARE_BLITZ]);
await game.startBattle([Species.ZOROARK, Species.AZUMARILL]);
game.doAttack(getMovePosition(game.scene, 0, Moves.FLARE_BLITZ));
await game.phaseInterceptor.to(TurnEndPhase);
const zoroark = game.scene.getPlayerPokemon();
expect(zoroark.illusion.active).equals(true);
});
it("copy the the name, the nickname, the gender, the shininess and the pokeball of the pokemon", async () => {
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SCARY_FACE, Moves.SCARY_FACE, Moves.SCARY_FACE, Moves.SCARY_FACE]);
await game.startBattle([Species.ABRA, Species.ZOROARK, Species.AXEW]);
const axew = game.scene.getParty().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.illusion.pokeball).equals(PokeballType.GREAT_BALL);
});
});

View File

@ -298,7 +298,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
});
this.teraIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip());
const isFusion = pokemon.isFusion();
const isFusion = pokemon.isFusion(true);
this.splicedIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), 2.5);
this.splicedIcon.setVisible(isFusion);
@ -308,7 +308,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, nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0) + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0), 2.5);
this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`);
@ -510,6 +510,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
return resolve();
}
const gender: Gender = pokemon.illusion.active ? pokemon.illusion.gender : pokemon.gender;
this.genderText.setText(getGenderSymbol(gender));
this.genderText.setColor(getGenderColor(gender));
const nameUpdated = this.lastName !== pokemon.getNameToRender();
if (nameUpdated) {
@ -527,8 +532,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, this.nameText.displayWidth + this.genderText.displayWidth + 1, 2);
this.splicedIcon.setPositionRelative(this.nameText, this.nameText.displayWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), 1.5);
@ -630,7 +637,17 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.lastBattleStats = battleStatsStr;
}
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();
});
@ -643,7 +660,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.BATTLE_INFO);
nameTextWidth = nameSizeTest.displayWidth;
while (nameTextWidth > (this.player || !this.boss ? 60 : 98) - ((pokemon.gender !== Gender.GENDERLESS ? 6 : 0) + (pokemon.fusionSpecies ? 8 : 0) + (pokemon.isShiny() ? 8 : 0) + (Math.min(pokemon.level.toString().length, 3) - 3) * 8)) {
const gender: Gender = pokemon.illusion.active ? pokemon.illusion.gender : pokemon.gender;
while (nameTextWidth > (this.player || !this.boss ? 60 : 98) - ((gender !== Gender.GENDERLESS ? 6 : 0) + (pokemon.fusionSpecies ? 8 : 0) + (pokemon.isShiny() ? 8 : 0) + (Math.min(pokemon.level.toString().length, 3) - 3) * 8)) {
displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`;
nameSizeTest.setText(displayName);
nameTextWidth = nameSizeTest.displayWidth;

View File

@ -117,14 +117,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;
};
@ -138,7 +138,7 @@ export default class PartyUiHandler extends MessageUiHandler {
const challengeAllowed = new Utils.BooleanHolder(true);
applyChallenges(this.scene.gameMode, 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;
};
@ -148,7 +148,7 @@ export default class PartyUiHandler extends MessageUiHandler {
public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => {
const matchingModifier = pokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id && m.matchType(modifier)) as PokemonHeldItemModifier;
if (matchingModifier && matchingModifier.stackCount === matchingModifier.getMaxStackCount(pokemon.scene)) {
return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon) });
return i18next.t("partyUiHandler:tooManyItems", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
}
return null;
};
@ -380,18 +380,18 @@ export default class PartyUiHandler extends MessageUiHandler {
this.clearOptions();
ui.playSelect();
pokemon.pauseEvolutions = false;
this.showText(i18next.t("partyUiHandler:unpausedEvolutions", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => this.showText(null, 0), null, true);
this.showText(i18next.t("partyUiHandler:unpausedEvolutions", { pokemonName: getPokemonNameWithAffix(pokemon, false) }), null, () => this.showText(null, 0), null, true);
} else if (option === PartyOption.UNSPLICE) {
this.clearOptions();
ui.playSelect();
this.showText(i18next.t("partyUiHandler:unspliceConfirmation", { fusionName: pokemon.fusionSpecies.name, pokemonName: pokemon.name }), null, () => {
this.showText(i18next.t("partyUiHandler:unspliceConfirmation", { fusionName: pokemon.fusionSpecies.name, pokemonName: pokemon.getName() }), null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => {
const fusionName = pokemon.name;
const fusionName = pokemon.getName(false);
pokemon.unfuse().then(() => {
this.clearPartySlots();
this.populatePartySlots();
ui.setMode(Mode.PARTY);
this.showText(i18next.t("partyUiHandler:wasReverted", { fusionName: fusionName, pokemonName: pokemon.name }), null, () => {
this.showText(i18next.t("partyUiHandler:wasReverted", { fusionName: fusionName, pokemonName: pokemon.getName() }), null, () => {
ui.setMode(Mode.PARTY);
this.showText(null, 0);
}, null, true);
@ -405,7 +405,7 @@ export default class PartyUiHandler extends MessageUiHandler {
this.clearOptions();
ui.playSelect();
if (this.cursor >= this.scene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) {
this.showText(i18next.t("partyUiHandler:releaseConfirmation", { pokemonName: getPokemonNameWithAffix(pokemon) }), null, () => {
this.showText(i18next.t("partyUiHandler:releaseConfirmation", { pokemonName: getPokemonNameWithAffix(pokemon, false) }), null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => {
ui.setMode(Mode.PARTY);
this.doRelease(this.cursor);
@ -950,7 +950,7 @@ export default class PartyUiHandler extends MessageUiHandler {
}
doRelease(slotIndex: integer): void {
this.showText(this.getReleaseMessage(getPokemonNameWithAffix(this.scene.getParty()[slotIndex])), null, () => {
this.showText(this.getReleaseMessage(getPokemonNameWithAffix(this.scene.getParty()[slotIndex], false)), null, () => {
this.clearPartySlots();
this.scene.removePartyMemberModifiers(slotIndex);
const releasedPokemon = this.scene.getParty().splice(slotIndex, 1)[0];
@ -1081,7 +1081,7 @@ class PartySlot extends Phaser.GameObjects.Container {
const slotInfoContainer = this.scene.add.container(0, 0);
this.add(slotInfoContainer);
let displayName = this.pokemon.getNameToRender();
let displayName = this.pokemon.getNameToRender(false);
let nameTextWidth: number;
const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY);
@ -1148,12 +1148,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 = this.scene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`);
shinyStar.setOrigin(0, 0);
shinyStar.setPositionRelative(slotName, -9, 3);
shinyStar.setTint(getVariantTint(!doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant));
shinyStar.setTint(getVariantTint(this.pokemon.getBaseVariant(doubleShiny)));
slotInfoContainer.add(shinyStar);
@ -1161,7 +1161,7 @@ class PartySlot extends Phaser.GameObjects.Container {
const fusionShinyStar = this.scene.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.illusion.fusionVariant ?? this.pokemon.fusionVariant));
slotInfoContainer.add(fusionShinyStar);
}

View File

@ -36,7 +36,7 @@ export default class RenameFormUiHandler extends FormModalUiHandler {
show(args: any[]): boolean {
if (super.show(args)) {
const config = args[0] as ModalConfig;
this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender();
this.inputs[0].text = (args[1] as PlayerPokemon).getNameToRender(false);
this.submitAction = (_) => {
this.sanitizeInputs();

View File

@ -301,8 +301,8 @@ export default class SummaryUiHandler extends UiHandler {
this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType()));
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.illusion.shiny ?? this.pokemon.shiny);
this.pokemonSprite.setPipelineData("variant", this.pokemon.illusion.variant ?? this.pokemon.variant);
[ "spriteColors", "fusionSpriteColors" ].map(k => {
delete this.pokemonSprite.pipelineData[`${k}Base`];
if (this.pokemon.summonData?.speciesForm) {
@ -312,7 +312,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();
@ -346,12 +346,12 @@ export default class SummaryUiHandler extends UiHandler {
this.candyShadow.setCrop(0,0,16, candyCropY);
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, this.nameText.displayWidth + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0) + 1, 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 = doubleShiny || baseVariant ?
@ -364,7 +364,7 @@ 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.illusion.fusionVariant ?? this.pokemon.fusionVariant));
}
this.pokeball.setFrame(getPokeballAtlasKey(this.pokemon.pokeball));
@ -727,7 +727,7 @@ export default class SummaryUiHandler extends UiHandler {
return typeIcon;
};
const types = this.pokemon.getTypes(false, false, true);
const types = this.pokemon.getTypes(false, false, true, false);
profileContainer.add(getTypeIcon(0, types[0]));
if (types.length > 1) {
profileContainer.add(getTypeIcon(1, types[1]));