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); container.add(icon);
if (pokemon.isFusion()) { if (pokemon.isFusion(true)) {
const fusionIcon = this.add.sprite(0, 0, pokemon.getFusionIconAtlasKey(ignoreOverride)); const fusionIcon = this.add.sprite(0, 0, pokemon.getFusionIconAtlasKey(ignoreOverride));
fusionIcon.setName("sprite-fusion-icon"); fusionIcon.setName("sprite-fusion-icon");
fusionIcon.setOrigin(0.5, 0); 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; return true;
} }
} }
export class PostSummonMessageAbAttr extends PostSummonAbAttr { export class PostSummonMessageAbAttr extends PostSummonAbAttr {
private messageFunc: (pokemon: Pokemon) => string; private messageFunc: (pokemon: Pokemon) => string;
@ -1898,6 +1897,11 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr {
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean { applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
pokemon.scene.queueMessage(this.messageFunc(pokemon)); 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; return true;
} }
} }
@ -2262,6 +2266,10 @@ export class PostSummonTransformAbAttr extends PostSummonAbAttr {
target = targets[0]; target = targets[0];
} }
if (target.illusion.active) {
return false;
}
pokemon.summonData.speciesForm = target.getSpeciesForm(); pokemon.summonData.speciesForm = target.getSpeciesForm();
pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm(); pokemon.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
pokemon.summonData.ability = target.getAbility().id; pokemon.summonData.ability = target.getAbility().id;
@ -3554,8 +3562,8 @@ export class MaxMultiHitAbAttr extends AbAttr {
} }
export class PostBattleAbAttr extends AbAttr { export class PostBattleAbAttr extends AbAttr {
constructor() { constructor(showAbility: boolean = true) {
super(true); super(showAbility);
} }
applyPostBattle(pokemon: Pokemon, passive: boolean, args: any[]): boolean { 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). * 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); 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>, export function applyPreSwitchOutAbAttrs(attrType: Constructor<PreSwitchOutAbAttr>,
pokemon: Pokemon, ...args: any[]): Promise<void> { pokemon: Pokemon, ...args: any[]): Promise<void> {
return applyAbAttrsInternal<PreSwitchOutAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, args), args, true); 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) new Ability(Abilities.ILLUSION, 5)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .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) new Ability(Abilities.IMPOSTER, 5)
.attr(PostSummonTransformAbAttr) .attr(PostSummonTransformAbAttr)
.attr(UncopiableAbilityAbAttr), .attr(UncopiableAbilityAbAttr),

View File

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

View File

@ -63,6 +63,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public name: string; public name: string;
public nickname: string; public nickname: string;
public species: PokemonSpecies; public species: PokemonSpecies;
public illusion: Illusion;
public formIndex: integer; public formIndex: integer;
public abilityIndex: integer; public abilityIndex: integer;
public passive: boolean; public passive: boolean;
@ -126,6 +127,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const randAbilityIndex = Utils.randSeedInt(2); const randAbilityIndex = Utils.randSeedInt(2);
this.species = species; this.species = species;
this.illusion = {active: false, available: true};
this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL; this.pokeball = dataSource?.pokeball || PokeballType.POKEBALL;
this.level = level; this.level = level;
// Determine the ability index // 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 { try {
if (this.nickname) { if (nickname) {
return decodeURIComponent(escape(atob(this.nickname))); return decodeURIComponent(escape(atob(nickname)));
} }
return this.name; return name;
} catch (err) { } catch (err) {
console.error(`Failed to decode nickname for ${this.name}`, err); console.error(`Failed to decode nickname for ${name}`, err);
return this.name; return name;
} }
} }
@ -276,7 +281,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.addAt(sprite, 0); this.addAt(sprite, 0);
this.addAt(tintSprite, 1); this.addAt(tintSprite, 1);
if (this.isShiny() && !this.shinySparkle) { if (this.isShiny(true) && !this.shinySparkle) {
this.initShinySparkle(); 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 isPlayer(): boolean;
abstract hasTrainer(): boolean; abstract hasTrainer(): boolean;
@ -345,18 +457,25 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
abstract getBattlerIndex(): BattlerIndex; 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 => { return new Promise(resolve => {
const moveIds = this.getMoveset().map(m => m.getMove().id); const moveIds = this.getMoveset().map(m => m.getMove().id);
Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m))) Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m)))
.then(() => { .then(() => {
loadMoveAnimAssets(this.scene, moveIds); loadMoveAnimAssets(this.scene, moveIds);
this.getSpeciesForm().loadAssets(this.scene, this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant); const formIndex = this.illusion.active && useIllusion ? this.illusion.formIndex : this.formIndex;
if (this.isPlayer() || this.getFusionSpeciesForm()) { 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)); this.scene.loadPokemonAtlas(this.getBattleSpriteKey(true, ignoreOverride), this.getBattleSpriteAtlasPath(true, ignoreOverride));
} }
if (this.getFusionSpeciesForm()) { if (this.getFusionSpeciesForm(false, useIllusion)) {
this.getFusionSpeciesForm().loadAssets(this.scene, this.getFusionGender() === Gender.FEMALE, this.fusionFormIndex, this.fusionShiny, this.fusionVariant); 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.loadPokemonAtlas(this.getFusionBattleSpriteKey(true, ignoreOverride), this.getFusionBattleSpriteAtlasPath(true, ignoreOverride));
} }
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => { 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 { 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 { getBattleSpriteId(back?: boolean, ignoreOverride?: boolean): string {
if (back === undefined) { if (back === undefined) {
back = this.isPlayer(); 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 { 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 { getBattleSpriteKey(back?: boolean, ignoreOverride?: boolean): string {
@ -475,14 +603,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getFusionSpriteId(ignoreOverride?: boolean): string { 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 { getFusionBattleSpriteId(back?: boolean, ignoreOverride?: boolean): string {
if (back === undefined) { if (back === undefined) {
back = this.isPlayer(); 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 { getFusionBattleSpriteKey(back?: boolean, ignoreOverride?: boolean): string {
@ -494,39 +626,56 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
getIconAtlasKey(ignoreOverride?: boolean): string { 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 { 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 { 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 { 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) { if (!ignoreOverride && this.summonData?.speciesForm) {
return this.summonData.speciesForm; return this.summonData.speciesForm;
} }
if (!this.species.forms?.length) {
return this.species; if (!species.forms?.length || formIndex === undefined) {
} return species;
return this.species.forms[this.formIndex];
} }
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) { if (!ignoreOverride && this.summonData?.speciesForm) {
return this.summonData.fusionSpeciesForm; return this.summonData.fusionSpeciesForm;
} }
if (!this.fusionSpecies?.forms?.length || this.fusionFormIndex >= this.fusionSpecies?.forms.length) { if (!fusionSpecies?.forms?.length || fusionFormIndex >= fusionSpecies?.forms.length) {
return this.fusionSpecies; return fusionSpecies;
} }
return this.fusionSpecies?.forms[this.fusionFormIndex]; return fusionSpecies?.forms[fusionFormIndex];
} }
getSprite(): Phaser.GameObjects.Sprite { 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.summonData.gender;
} }
return this.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.summonData.fusionGender;
} }
return this.fusionGender; return this.fusionGender;
} }
isShiny(): boolean { isShiny(useIllusion: boolean = false): boolean {
return this.shiny || (this.isFusion() && this.fusionShiny); 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 { isDoubleShiny(useIllusion: boolean = false): boolean {
return !this.isFusion() ? this.variant : Math.max(this.variant, this.fusionVariant) as Variant; 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 { getLuck(): integer {
return this.luck + (this.isFusion() ? this.fusionLuck : 0); 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; return !!this.fusionSpecies;
} }
}
getName(illusion: boolean = false): string {
return (!illusion && this.illusion.active) ? this.illusion.name : this.name;
}
abstract isBoss(): boolean; abstract isBoss(): boolean;
@ -894,30 +1086,29 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** /**
* Gets the types of a pokemon * Gets the types of a pokemon
* @param includeTeraType boolean to include tera-formed type, default false * @param {boolean} includeTeraType to include tera-formed type, default false
* @param forDefend boolean if the pokemon is defending from an attack * @param {boolean} forDefend if the pokemon is defending from an attack
* @param ignoreOverride boolean if true, ignore ability changing effects * @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} * @returns array of {@linkcode Type}
*/ */
getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean): Type[] { getTypes(includeTeraType = false, forDefend: boolean = false, ignoreOverride?: boolean, useIllusion: boolean | "AUTO" = "AUTO"): Type[] {
const types = []; const types: Type[] = [];
if (includeTeraType) { if (includeTeraType) {
const teraType = this.getTeraType(); const teraType = this.getTeraType();
if (teraType !== Type.UNKNOWN) { if (teraType !== Type.UNKNOWN) {
types.push(teraType); types.push(teraType);
} }
} }
if (!types.length || !includeTeraType) { 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)); this.summonData.types.forEach(t => types.push(t));
} else { } else {
const speciesForm = this.getSpeciesForm(ignoreOverride); const speciesForm = this.getSpeciesForm(ignoreOverride, doIllusion);
types.push(speciesForm.type1); types.push(speciesForm.type1);
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride, doIllusion);
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
if (fusionSpeciesForm) { if (fusionSpeciesForm) {
if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) { if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) {
types.push(fusionSpeciesForm.type2); types.push(fusionSpeciesForm.type2);
@ -925,7 +1116,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
types.push(fusionSpeciesForm.type1); types.push(fusionSpeciesForm.type1);
} }
} }
if (types.length === 1 && speciesForm.type2 !== null) { if (types.length === 1 && speciesForm.type2 !== null) {
types.push(speciesForm.type2); types.push(speciesForm.type2);
} }
@ -951,7 +1141,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
types.splice(index, 1); types.splice(index, 1);
} }
} }
return types; return types;
} }
@ -1179,12 +1368,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param source - The attacking Pokémon. * @param source - The attacking Pokémon.
* @param pokemonMove - The move being used by 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 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 * @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 move = pokemonMove.getMove();
const typeless = move.hasAttr(TypelessAttr); 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); const cancelled = new Utils.BooleanHolder(false);
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
if (!typeless && !ignoreAbility) { if (!typeless && !ignoreAbility) {
@ -1203,9 +1393,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param source the Pokemon using the move * @param source the Pokemon using the move
* @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks) * @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 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 * @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) const move = (moveOrType instanceof Move)
? moveOrType ? moveOrType
: undefined; : undefined;
@ -1216,7 +1407,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (moveType === Type.STELLAR) { if (moveType === Type.STELLAR) {
return this.isTerastallized() ? 2 : 1; return this.isTerastallized() ? 2 : 1;
} }
const types = this.getTypes(true, true); const types = this.getTypes(true, true, undefined, useIllusion);
let multiplier = types.map(defType => { let multiplier = types.map(defType => {
if (source) { if (source) {
@ -1253,12 +1444,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getMatchupScore(pokemon: Pokemon): number { getMatchupScore(pokemon: Pokemon): number {
const types = this.getTypes(true); 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); 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); let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], pokemon), 0.25);
if (types.length > 1) { if (types.length > 1) {
atkScore *= pokemon.getAttackTypeEffectiveness(types[1], this); atkScore *= pokemon.getAttackTypeEffectiveness(types[1], this, false, false, true);
} }
if (enemyTypes.length > 1) { if (enemyTypes.length > 1) {
defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], pokemon), 0.25)); 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)) { if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id)) {
targetScore = -20; targetScore = -20;
} else if (move instanceof AttackMove) { } else if (move instanceof AttackMove) {
const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove); const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove, false, true);
if (target.isPlayer() !== this.isPlayer()) { if (target.isPlayer() !== this.isPlayer()) {
targetScore *= effectiveness; targetScore *= effectiveness;
if (this.isOfType(move.type)) { 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 { export interface TurnMove {
move: Moves; move: Moves;
targets?: BattlerIndex[]; targets?: BattlerIndex[];

View File

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

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!", "trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!", "windPowerCharged": "Being hit by {{moveName}} charged {{pokemonName}} with power!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!", "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!", "blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!", "typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!", "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}}!", "windPowerCharged": "¡{{pokemonName}} se ha cargado de electricidad gracias a {{moveName}}!",
"quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!", "quickDraw": "{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!", "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!", "typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!",
"postDefendDisguise": "{{pokemonNameWithAffix}}'s disguise was busted!", "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 !", "poisonHeal": "{{abilityName}} de {{pokemonName}}\nrestaure un peu ses PV !",
"trace": "{{pokemonName}} copie le talent {{abilityName}}\nde {{targetName}} !", "trace": "{{pokemonName}} copie le talent {{abilityName}}\nde {{targetName}} !",
"windPowerCharged": "{{pokemonName}} a été touché par la capacité {{moveName}} et se charge en électricité !", "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 !", "quickDraw": "Tir Vif permet à {{pokemonName}}\ndagir plus vite que dhabitude !",
"blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nempêche son objet dêtre volé !", "blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nempêche son objet dêtre volé !",
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaure un peu ses PV !", "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!", "trace": "L'abilità {{abilityName}} di {{targetName}}\nviene copiata da {{pokemonName}} con Traccia!",
"windPowerCharged": "Venire colpito da {{moveName}} ha caricato {{pokemonName}}!", "windPowerCharged": "Venire colpito da {{moveName}} ha caricato {{pokemonName}}!",
"quickDraw":"{{pokemonName}} agisce più rapidamente del normale grazie a Colpolesto!", "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!", "blockItemTheft": "{{abilityName}} di {{pokemonNameWithAffix}}\nlo rende immune ai furti!",
"typeImmunityHeal": "{{pokemonName}} recupera alcuni PS\ncon {{abilityName}}!", "typeImmunityHeal": "{{pokemonName}} recupera alcuni PS\ncon {{abilityName}}!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evita il colpo\ncon {{abilityName}}!", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evita il colpo\ncon {{abilityName}}!",

View File

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

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} copiou {{abilityName}}\nde {{targetName}}!", "trace": "{{pokemonName}} copiou {{abilityName}}\nde {{targetName}}!",
"windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!", "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!", "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!", "blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nprevine o roubo de itens!",
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaurou um pouco de PS!", "typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaurou um pouco de PS!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evitou dano\ncom {{abilityName}}!", "nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} evitou dano\ncom {{abilityName}}!",

View File

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

View File

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

View File

@ -10,6 +10,7 @@ export const abilityTriggers: SimpleTranslationEntries = {
"trace": "{{pokemonName}} 複製了 {{targetName}} 的\n{{abilityName}}!", "trace": "{{pokemonName}} 複製了 {{targetName}} 的\n{{abilityName}}!",
"windPowerCharged": "受 {{moveName}} 的影響, {{pokemonName}} 提升了能力!", "windPowerCharged": "受 {{moveName}} 的影響, {{pokemonName}} 提升了能力!",
"quickDraw":"{{pokemonName}} can act faster than normal, thanks to its Quick Draw!", "quickDraw":"{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"illusionBreak": "{{pokemonName}}造成的\n幻覺解除了",
"blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!", "blockItemTheft": "{{pokemonNameWithAffix}}'s {{abilityName}}\nprevents item theft!",
"typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!", "typeImmunityHeal": "{{pokemonNameWithAffix}}'s {{abilityName}}\nrestored its HP a little!",
"nonSuperEffectiveImmunity": "{{pokemonNameWithAffix}} avoided damage\nwith {{abilityName}}!", "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 * @param pokemon {@linkcode Pokemon} name and battle context will be retrieved from this instance
* @returns {string} ex: "Wild Gengar", "Ectoplasma sauvage" * @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) { switch (pokemon.scene.currentBattle.battleSpec) {
case BattleSpec.DEFAULT: case BattleSpec.DEFAULT:
return !pokemon.isPlayer() return !pokemon.isPlayer()
? pokemon.hasTrainer() ? pokemon.hasTrainer()
? i18next.t("battle:foePokemonWithAffix", { ? i18next.t("battle:foePokemonWithAffix", {
pokemonName: pokemon.getNameToRender(), pokemonName: pokemon.getNameToRender(useIllusion),
}) })
: i18next.t("battle:wildPokemonWithAffix", { : i18next.t("battle:wildPokemonWithAffix", {
pokemonName: pokemon.getNameToRender(), pokemonName: pokemon.getNameToRender(useIllusion),
}) })
: pokemon.getNameToRender(); : pokemon.getNameToRender(useIllusion);
case BattleSpec.FINAL_BOSS: case BattleSpec.FINAL_BOSS:
return !pokemon.isPlayer() return !pokemon.isPlayer()
? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender(useIllusion) })
: pokemon.getNameToRender(); : pokemon.getNameToRender(useIllusion);
default: 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 { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; 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 { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena"; import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -842,6 +845,7 @@ export class EncounterPhase extends BattlePhase {
}); });
} }
} }
const enemyPokemon = this.scene.getEnemyParty()[e]; const enemyPokemon = this.scene.getEnemyParty()[e];
if (e < (battle.double ? 2 : 1)) { if (e < (battle.double ? 2 : 1)) {
enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]); enemyPokemon.setX(-66 + enemyPokemon.getFieldPositionOffset()[0]);
@ -895,6 +899,8 @@ export class EncounterPhase extends BattlePhase {
battle.enemyParty.forEach((enemyPokemon, e) => { battle.enemyParty.forEach((enemyPokemon, e) => {
if (e < (battle.double ? 2 : 1)) { if (e < (battle.double ? 2 : 1)) {
if (battle.battleType === BattleType.WILD) { if (battle.battleType === BattleType.WILD) {
applyPreSummonAbAttrs(PreSummonAbAttr, enemyPokemon, []);
this.scene.field.add(enemyPokemon); this.scene.field.add(enemyPokemon);
battle.seenEnemyPartyMemberIds.add(enemyPokemon.id); battle.seenEnemyPartyMemberIds.add(enemyPokemon.id);
const playerPokemon = this.scene.getPlayerPokemon(); const playerPokemon = this.scene.getPlayerPokemon();
@ -1058,7 +1064,7 @@ export class EncounterPhase extends BattlePhase {
const enemyField = this.scene.getEnemyField(); const enemyField = this.scene.getEnemyField();
enemyField.forEach((enemyPokemon, e) => { enemyField.forEach((enemyPokemon, e) => {
if (enemyPokemon.isShiny()) { if (enemyPokemon.isShiny(true)) {
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e)); this.scene.unshiftPhase(new ShinySparklePhase(this.scene, BattlerIndex.ENEMY + e));
} }
}); });
@ -1378,7 +1384,8 @@ export class SummonPhase extends PartyMemberPokemonPhase {
start() { start() {
super.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 { summon(): void {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.illusion.pokeball ?? pokemon.pokeball));
const pokeball = this.scene.addFieldSprite(this.player ? 36 : 248, this.player ? 80 : 44, "pb", getPokeballAtlasKey(pokemon.pokeball));
pokeball.setVisible(false); pokeball.setVisible(false);
pokeball.setOrigin(0.5, 0.625); pokeball.setOrigin(0.5, 0.625);
this.scene.field.add(pokeball); this.scene.field.add(pokeball);
@ -1494,7 +1500,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
} }
this.scene.currentBattle.seenEnemyPartyMemberIds.add(pokemon.id); 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.updateModifiers(this.player);
this.scene.updateFieldScale(); this.scene.updateFieldScale();
pokemon.showInfo(); pokemon.showInfo();
@ -1502,7 +1508,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
pokemon.setVisible(true); pokemon.setVisible(true);
pokemon.getSprite().setVisible(true); pokemon.getSprite().setVisible(true);
pokemon.setScale(0.5); pokemon.setScale(0.5);
pokemon.tint(getPokeballTintColor(pokemon.pokeball)); pokemon.tint(getPokeballTintColor(pokemon.illusion.pokeball ?? pokemon.pokeball));
pokemon.untint(250, "Sine.easeIn"); pokemon.untint(250, "Sine.easeIn");
this.scene.updateFieldScale(); this.scene.updateFieldScale();
this.scene.tweens.add({ this.scene.tweens.add({
@ -1526,7 +1532,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
onEnd(): void { onEnd(): void {
const pokemon = this.getPokemon(); const pokemon = this.getPokemon();
if (pokemon.isShiny()) { if (pokemon.isShiny(true)) {
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, pokemon.getBattlerIndex())); 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()); 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.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getParty() : this.scene.getEnemyParty())[this.slotIndex])) {
if (this.player) { if (this.player) {
return this.switchAndSummon(); return this.switchAndSummon();
@ -1597,8 +1606,6 @@ export class SwitchSummonPhase extends SummonPhase {
} }
} }
const pokemon = this.getPokemon();
if (!this.batonPass) { if (!this.batonPass) {
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); (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"); this.scene.playSound("pb_rel");
pokemon.hideInfo(); 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({ this.scene.tweens.add({
targets: pokemon, targets: pokemon,
duration: 250, duration: 250,
@ -1631,6 +1638,8 @@ export class SwitchSummonPhase extends SummonPhase {
const party = this.player ? this.getParty() : this.scene.getEnemyParty(); const party = this.player ? this.getParty() : this.scene.getEnemyParty();
const switchedPokemon = party[this.slotIndex]; const switchedPokemon = party[this.slotIndex];
this.lastPokemon = this.getPokemon(); this.lastPokemon = this.getPokemon();
applyPreSummonAbAttrs(PreSummonAbAttr, switchedPokemon);
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon); applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
if (this.batonPass && switchedPokemon) { if (this.batonPass && switchedPokemon) {
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedPokemon.id)); (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()); this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
}, PartyUiHandler.FilterNonFainted); }, 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()); 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.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1 + (this.teraIcon.visible ? this.teraIcon.displayWidth + 1 : 0), 2.5);
this.splicedIcon.setVisible(isFusion); this.splicedIcon.setVisible(isFusion);
@ -308,7 +308,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
} }
const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; 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.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" : ""}`); this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`);
@ -510,6 +510,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
return resolve(); 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(); const nameUpdated = this.lastName !== pokemon.getNameToRender();
if (nameUpdated) { if (nameUpdated) {
@ -527,8 +532,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.lastTeraType = teraType; this.lastTeraType = teraType;
} }
const isFusion = pokemon.isFusion(true);
if (nameUpdated || teraTypeUpdated) { 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.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); 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.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(); resolve();
}); });
@ -643,7 +660,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.BATTLE_INFO); const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.BATTLE_INFO);
nameTextWidth = nameSizeTest.displayWidth; 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()}.`; displayName = `${displayName.slice(0, displayName.endsWith(".") ? -2 : -1).trimEnd()}.`;
nameSizeTest.setText(displayName); nameSizeTest.setText(displayName);
nameTextWidth = nameSizeTest.displayWidth; nameTextWidth = nameSizeTest.displayWidth;

View File

@ -117,14 +117,14 @@ export default class PartyUiHandler extends MessageUiHandler {
public static FilterNonFainted = (pokemon: PlayerPokemon) => { public static FilterNonFainted = (pokemon: PlayerPokemon) => {
if (pokemon.isFainted()) { if (pokemon.isFainted()) {
return i18next.t("partyUiHandler:noEnergy", { pokemonName: getPokemonNameWithAffix(pokemon) }); return i18next.t("partyUiHandler:noEnergy", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
} }
return null; return null;
}; };
public static FilterFainted = (pokemon: PlayerPokemon) => { public static FilterFainted = (pokemon: PlayerPokemon) => {
if (!pokemon.isFainted()) { if (!pokemon.isFainted()) {
return i18next.t("partyUiHandler:hasEnergy", { pokemonName: getPokemonNameWithAffix(pokemon) }); return i18next.t("partyUiHandler:hasEnergy", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
} }
return null; return null;
}; };
@ -138,7 +138,7 @@ export default class PartyUiHandler extends MessageUiHandler {
const challengeAllowed = new Utils.BooleanHolder(true); const challengeAllowed = new Utils.BooleanHolder(true);
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed);
if (!challengeAllowed.value) { if (!challengeAllowed.value) {
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: getPokemonNameWithAffix(pokemon) }); return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: getPokemonNameWithAffix(pokemon, false) });
} }
return null; return null;
}; };
@ -148,7 +148,7 @@ export default class PartyUiHandler extends MessageUiHandler {
public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { 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; 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)) { 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; return null;
}; };
@ -380,18 +380,18 @@ export default class PartyUiHandler extends MessageUiHandler {
this.clearOptions(); this.clearOptions();
ui.playSelect(); ui.playSelect();
pokemon.pauseEvolutions = false; 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) { } else if (option === PartyOption.UNSPLICE) {
this.clearOptions(); this.clearOptions();
ui.playSelect(); 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, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => {
const fusionName = pokemon.name; const fusionName = pokemon.getName(false);
pokemon.unfuse().then(() => { pokemon.unfuse().then(() => {
this.clearPartySlots(); this.clearPartySlots();
this.populatePartySlots(); this.populatePartySlots();
ui.setMode(Mode.PARTY); 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); ui.setMode(Mode.PARTY);
this.showText(null, 0); this.showText(null, 0);
}, null, true); }, null, true);
@ -405,7 +405,7 @@ export default class PartyUiHandler extends MessageUiHandler {
this.clearOptions(); this.clearOptions();
ui.playSelect(); ui.playSelect();
if (this.cursor >= this.scene.currentBattle.getBattlerCount() || !pokemon.isAllowedInBattle()) { 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.setModeWithoutClear(Mode.CONFIRM, () => {
ui.setMode(Mode.PARTY); ui.setMode(Mode.PARTY);
this.doRelease(this.cursor); this.doRelease(this.cursor);
@ -950,7 +950,7 @@ export default class PartyUiHandler extends MessageUiHandler {
} }
doRelease(slotIndex: integer): void { 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.clearPartySlots();
this.scene.removePartyMemberModifiers(slotIndex); this.scene.removePartyMemberModifiers(slotIndex);
const releasedPokemon = this.scene.getParty().splice(slotIndex, 1)[0]; 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); const slotInfoContainer = this.scene.add.container(0, 0);
this.add(slotInfoContainer); this.add(slotInfoContainer);
let displayName = this.pokemon.getNameToRender(); let displayName = this.pokemon.getNameToRender(false);
let nameTextWidth: number; let nameTextWidth: number;
const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY); const nameSizeTest = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY);
@ -1148,12 +1148,12 @@ class PartySlot extends Phaser.GameObjects.Container {
} }
if (this.pokemon.isShiny()) { 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" : ""}`); const shinyStar = this.scene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`);
shinyStar.setOrigin(0, 0); shinyStar.setOrigin(0, 0);
shinyStar.setPositionRelative(slotName, -9, 3); 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); 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"); const fusionShinyStar = this.scene.add.image(0, 0, "shiny_star_small_2");
fusionShinyStar.setOrigin(0, 0); fusionShinyStar.setOrigin(0, 0);
fusionShinyStar.setPosition(shinyStar.x, shinyStar.y); 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); slotInfoContainer.add(fusionShinyStar);
} }

View File

@ -36,7 +36,7 @@ export default class RenameFormUiHandler extends FormModalUiHandler {
show(args: any[]): boolean { show(args: any[]): boolean {
if (super.show(args)) { if (super.show(args)) {
const config = args[0] as ModalConfig; 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.submitAction = (_) => {
this.sanitizeInputs(); 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("teraColor", getTypeRgb(this.pokemon.getTeraType()));
this.pokemonSprite.setPipelineData("ignoreTimeTint", true); this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); this.pokemonSprite.setPipelineData("shiny", this.pokemon.illusion.shiny ?? this.pokemon.shiny);
this.pokemonSprite.setPipelineData("variant", this.pokemon.variant); this.pokemonSprite.setPipelineData("variant", this.pokemon.illusion.variant ?? this.pokemon.variant);
[ "spriteColors", "fusionSpriteColors" ].map(k => { [ "spriteColors", "fusionSpriteColors" ].map(k => {
delete this.pokemonSprite.pipelineData[`${k}Base`]; delete this.pokemonSprite.pipelineData[`${k}Base`];
if (this.pokemon.summonData?.speciesForm) { if (this.pokemon.summonData?.speciesForm) {
@ -312,7 +312,7 @@ export default class SummaryUiHandler extends UiHandler {
}); });
this.pokemon.cry(); this.pokemon.cry();
this.nameText.setText(this.pokemon.getNameToRender()); this.nameText.setText(this.pokemon.getNameToRender(false));
const isFusion = this.pokemon.isFusion(); const isFusion = this.pokemon.isFusion();
@ -346,12 +346,12 @@ export default class SummaryUiHandler extends UiHandler {
this.candyShadow.setCrop(0,0,16, candyCropY); this.candyShadow.setCrop(0,0,16, candyCropY);
const doubleShiny = isFusion && this.pokemon.shiny && this.pokemon.fusionShiny; const doubleShiny = this.pokemon.isDoubleShiny(false);
const baseVariant = !doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant; 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.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.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`);
this.shinyIcon.setVisible(this.pokemon.isShiny()); this.shinyIcon.setVisible(this.pokemon.isShiny(false));
this.shinyIcon.setTint(getVariantTint(baseVariant)); this.shinyIcon.setTint(getVariantTint(baseVariant));
if (this.shinyIcon.visible) { if (this.shinyIcon.visible) {
const shinyDescriptor = doubleShiny || baseVariant ? 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.setPosition(this.shinyIcon.x, this.shinyIcon.y);
this.fusionShinyIcon.setVisible(doubleShiny); this.fusionShinyIcon.setVisible(doubleShiny);
if (isFusion) { 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)); this.pokeball.setFrame(getPokeballAtlasKey(this.pokemon.pokeball));
@ -727,7 +727,7 @@ export default class SummaryUiHandler extends UiHandler {
return typeIcon; 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])); profileContainer.add(getTypeIcon(0, types[0]));
if (types.length > 1) { if (types.length > 1) {
profileContainer.add(getTypeIcon(1, types[1])); profileContainer.add(getTypeIcon(1, types[1]));