mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-25 05:28:27 +00:00
implement illusion ability with unit test and localizations
This commit is contained in:
parent
bfc44ea35e
commit
9f26ea983b
@ -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
120
src/data/ability.ts
Normal file → Executable 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),
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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[];
|
||||
|
@ -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}}!",
|
||||
|
@ -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}}!",
|
||||
|
@ -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!",
|
||||
|
@ -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": "L’illusion de {{pokemonName}} se brise !",
|
||||
"quickDraw": "Tir Vif permet à {{pokemonName}}\nd’agir plus vite que d’habitude !",
|
||||
"blockItemTheft": "{{abilityName}} de {{pokemonNameWithAffix}}\nempêche son objet d’être volé !",
|
||||
"typeImmunityHeal": "{{abilityName}} de {{pokemonNameWithAffix}}\nrestaure un peu ses PV !",
|
||||
|
@ -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}}!",
|
||||
|
@ -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데미지를 입지 않는다!",
|
||||
|
@ -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}}!",
|
||||
|
@ -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";
|
||||
|
@ -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避免了伤害!",
|
||||
|
@ -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}}!",
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
127
src/test/abilities/illusion.test.ts
Normal file
127
src/test/abilities/illusion.test.ts
Normal 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);
|
||||
});
|
||||
});
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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]));
|
||||
|
Loading…
x
Reference in New Issue
Block a user