[Sprite][Bug][ME] Fix ME Intro visuals for shinies and other shiny related fixes (#4827)
* [ME] Fix GTS Wonder Trade shiny not giving luck * [ME] Shiny Magikarp from Pokemon Salesman can have any variant * [ME] Shiny lock MEs with custom or special sprites * [ME] GTS shows shiny sparkle for received Pokemon * [ME] Shiny lock 'Slumbering Snorlax' and 'The Strong Stuff' * [ME] Dancing Lessson: show shiny sparkle for Oricorio in intro * [ME] Show shiny sparkles for Pokemon in ME intro * fix tests * Ensure shiny sparkle animation is initialized before playing it (Fixes #3924) * make loading variant assets cleaner * cleanup EnemyPokemon shiny initialization * test fixes and final cleanup * Make 'getSpeciesFilterRandomPartyMemberFunc' more readable --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
80555be22c
commit
38d7a26053
|
@ -47,7 +47,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container";
|
||||||
import { biomeDepths, getBiomeName } from "#app/data/balance/biomes";
|
import { biomeDepths, getBiomeName } from "#app/data/balance/biomes";
|
||||||
import { SceneBase } from "#app/scene-base";
|
import { SceneBase } from "#app/scene-base";
|
||||||
import CandyBar from "#app/ui/candy-bar";
|
import CandyBar from "#app/ui/candy-bar";
|
||||||
import { Variant, variantData } from "#app/data/variant";
|
import { Variant, variantColorCache, variantData, VariantSet } from "#app/data/variant";
|
||||||
import { Localizable } from "#app/interfaces/locales";
|
import { Localizable } from "#app/interfaces/locales";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { InputsController } from "#app/inputs-controller";
|
import { InputsController } from "#app/inputs-controller";
|
||||||
|
@ -345,6 +345,33 @@ export default class BattleScene extends SceneBase {
|
||||||
this.load.atlas(key, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`);
|
this.load.atlas(key, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache}
|
||||||
|
*/
|
||||||
|
loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant) {
|
||||||
|
const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey);
|
||||||
|
if (useExpSprite) {
|
||||||
|
fileRoot = `exp/${fileRoot}`;
|
||||||
|
}
|
||||||
|
let variantConfig = variantData;
|
||||||
|
fileRoot.split("/").map(p => variantConfig ? variantConfig = variantConfig[p] : null);
|
||||||
|
const variantSet = variantConfig as VariantSet;
|
||||||
|
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
|
||||||
|
const populateVariantColors = (key: string): Promise<void> => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (variantColorCache.hasOwnProperty(key)) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`).then(res => res.json()).then(c => {
|
||||||
|
variantColorCache[key] = c;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
populateVariantColors(spriteKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async preload() {
|
async preload() {
|
||||||
if (DEBUG_RNG) {
|
if (DEBUG_RNG) {
|
||||||
const scene = this;
|
const scene = this;
|
||||||
|
@ -891,7 +918,7 @@ export default class BattleScene extends SceneBase {
|
||||||
return pokemon;
|
return pokemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
|
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, shinyLock: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
|
||||||
if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
|
if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
|
||||||
level = Overrides.OPP_LEVEL_OVERRIDE;
|
level = Overrides.OPP_LEVEL_OVERRIDE;
|
||||||
}
|
}
|
||||||
|
@ -901,7 +928,7 @@ export default class BattleScene extends SceneBase {
|
||||||
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
|
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, dataSource);
|
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, shinyLock, dataSource);
|
||||||
if (Overrides.OPP_FUSION_OVERRIDE) {
|
if (Overrides.OPP_FUSION_OVERRIDE) {
|
||||||
pokemon.generateFusionSpecies();
|
pokemon.generateFusionSpecies();
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,6 +216,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||||
species: getPokemonSpecies(Species.GREEDENT),
|
species: getPokemonSpecies(Species.GREEDENT),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 3,
|
bossSegments: 3,
|
||||||
|
shiny: false, // Shiny lock because of consistency issues between the different options
|
||||||
moveSet: [ Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH ],
|
moveSet: [ Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH ],
|
||||||
modifierConfigs: bossModifierConfigs,
|
modifierConfigs: bossModifierConfigs,
|
||||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||||
|
@ -353,9 +354,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||||
})
|
})
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
// Let it have the food
|
// Let it have the food
|
||||||
// Greedent joins the team, level equal to 2 below highest party member
|
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
|
||||||
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
|
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
|
||||||
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false);
|
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, true);
|
||||||
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
|
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
|
||||||
greedent.passive = true;
|
greedent.passive = true;
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,9 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||||
tint: 0.25,
|
tint: 0.25,
|
||||||
x: -5,
|
x: -5,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: bossPokemon.shiny,
|
||||||
|
variant: bossPokemon.variant
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -92,9 +92,13 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||||
.withCatchAllowed(true)
|
.withCatchAllowed(true)
|
||||||
.withFleeAllowed(false)
|
.withFleeAllowed(false)
|
||||||
.withOnVisualsStart((scene: BattleScene) => {
|
.withOnVisualsStart((scene: BattleScene) => {
|
||||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon()!);
|
const oricorio = scene.getEnemyPokemon()!;
|
||||||
danceAnim.play(scene);
|
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, scene.getPlayerPokemon()!);
|
||||||
|
danceAnim.play(scene, false, () => {
|
||||||
|
if (oricorio.shiny) {
|
||||||
|
oricorio.sparkle();
|
||||||
|
}
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
|
@ -136,7 +140,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||||
}
|
}
|
||||||
|
|
||||||
const oricorioData = new PokemonData(enemyPokemon);
|
const oricorioData = new PokemonData(enemyPokemon);
|
||||||
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, oricorioData);
|
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
|
||||||
|
|
||||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||||
scene.getEnemyParty().forEach(enemyPokemon => {
|
scene.getEnemyParty().forEach(enemyPokemon => {
|
||||||
|
|
|
@ -114,7 +114,9 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
||||||
tint: 0.25,
|
tint: 0.25,
|
||||||
x: -5,
|
x: -5,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: bossPokemon.shiny,
|
||||||
|
variant: bossPokemon.variant
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -194,10 +194,10 @@ async function summonPlayerPokemon(scene: BattleScene) {
|
||||||
playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon);
|
playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also loads Wobbuffet data
|
// Also loads Wobbuffet data (cannot be shiny)
|
||||||
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
|
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
|
||||||
scene.currentBattle.enemyParty = [];
|
scene.currentBattle.enemyParty = [];
|
||||||
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false);
|
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true);
|
||||||
wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ];
|
wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ];
|
||||||
wobbuffet.setNature(Nature.MILD);
|
wobbuffet.setNature(Nature.MILD);
|
||||||
wobbuffet.setAlpha(0);
|
wobbuffet.setAlpha(0);
|
||||||
|
|
|
@ -12,8 +12,7 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon
|
||||||
import { getTypeRgb } from "#app/data/type";
|
import { getTypeRgb } from "#app/data/type";
|
||||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import * as Utils from "#app/utils";
|
import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||||
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
|
||||||
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
||||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
|
@ -27,6 +26,7 @@ import { trainerNamePools } from "#app/data/trainer-names";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import type { PokeballType } from "#enums/pokeball";
|
import type { PokeballType } from "#enums/pokeball";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/globalTradeSystem";
|
const namespace = "mysteryEncounters/globalTradeSystem";
|
||||||
|
@ -230,7 +230,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||||
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
||||||
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
||||||
if (!tradePokemon.shiny) {
|
if (!tradePokemon.shiny) {
|
||||||
const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE);
|
const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE);
|
||||||
if (scene.eventManager.isEventActive()) {
|
if (scene.eventManager.isEventActive()) {
|
||||||
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
|
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||||
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
|
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
|
||||||
if (tradePokemon.species.abilityHidden) {
|
if (tradePokemon.species.abilityHidden) {
|
||||||
if (tradePokemon.abilityIndex < hiddenIndex) {
|
if (tradePokemon.abilityIndex < hiddenIndex) {
|
||||||
const hiddenAbilityChance = new IntegerHolder(64);
|
const hiddenAbilityChance = new NumberHolder(64);
|
||||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||||
|
|
||||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||||
|
@ -797,6 +797,14 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
|
||||||
receivedPokeballSprite.x = tradeBaseBg.displayWidth / 2;
|
receivedPokeballSprite.x = tradeBaseBg.displayWidth / 2;
|
||||||
receivedPokeballSprite.y = tradeBaseBg.displayHeight / 2 - 100;
|
receivedPokeballSprite.y = tradeBaseBg.displayHeight / 2 - 100;
|
||||||
|
|
||||||
|
// Received pokemon sparkles
|
||||||
|
let pokemonShinySparkle: Phaser.GameObjects.Sprite;
|
||||||
|
if (receivedPokemon.shiny) {
|
||||||
|
pokemonShinySparkle = scene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny");
|
||||||
|
pokemonShinySparkle.setVisible(false);
|
||||||
|
tradeContainer.add(pokemonShinySparkle);
|
||||||
|
}
|
||||||
|
|
||||||
const BASE_ANIM_DURATION = 1000;
|
const BASE_ANIM_DURATION = 1000;
|
||||||
|
|
||||||
// Pokeball falls to the screen
|
// Pokeball falls to the screen
|
||||||
|
@ -835,6 +843,11 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
|
||||||
scale: 1,
|
scale: 1,
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
|
if (receivedPokemon.shiny) {
|
||||||
|
scene.time.delayedCall(500, () => {
|
||||||
|
doShinySparkleAnim(scene, pokemonShinySparkle, receivedPokemon.variant);
|
||||||
|
});
|
||||||
|
}
|
||||||
receivedPokeballSprite.destroy();
|
receivedPokeballSprite.destroy();
|
||||||
scene.time.delayedCall(2000, () => resolve());
|
scene.time.delayedCall(2000, () => resolve());
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
||||||
const pokemonConfig: EnemyPokemonConfig = {
|
const pokemonConfig: EnemyPokemonConfig = {
|
||||||
species: bossSpecies,
|
species: bossSpecies,
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
|
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||||
status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves
|
status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves
|
||||||
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ],
|
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ],
|
||||||
modifierConfigs: [
|
modifierConfigs: [
|
||||||
|
|
|
@ -72,13 +72,11 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||||
|
|
||||||
let pokemon: PlayerPokemon;
|
let pokemon: PlayerPokemon;
|
||||||
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
|
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
|
||||||
// If no HA mon found or you roll 1%, give shiny Magikarp
|
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
|
||||||
species = getPokemonSpecies(Species.MAGIKARP);
|
species = getPokemonSpecies(Species.MAGIKARP);
|
||||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex, undefined, true);
|
||||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true, 0);
|
|
||||||
} else {
|
} else {
|
||||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex);
|
||||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
|
|
||||||
}
|
}
|
||||||
pokemon.generateAndPopulateMoveset();
|
pokemon.generateAndPopulateMoveset();
|
||||||
|
|
||||||
|
@ -88,7 +86,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||||
fileRoot: fileRoot,
|
fileRoot: fileRoot,
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: pokemon.shiny,
|
||||||
|
variant: pokemon.variant
|
||||||
});
|
});
|
||||||
|
|
||||||
const starterTier = speciesStarterCosts[species.speciesId];
|
const starterTier = speciesStarterCosts[species.speciesId];
|
||||||
|
|
|
@ -79,6 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
|
||||||
species: getPokemonSpecies(Species.SHUCKLE),
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 5,
|
bossSegments: 5,
|
||||||
|
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||||
nature: Nature.BOLD,
|
nature: Nature.BOLD,
|
||||||
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
||||||
|
|
|
@ -61,11 +61,12 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
||||||
.withOnInit((scene: BattleScene) => {
|
.withOnInit((scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
|
|
||||||
// Calculate boss mon
|
// Calculate boss mon (shiny locked)
|
||||||
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
||||||
const pokemonConfig: EnemyPokemonConfig = {
|
const pokemonConfig: EnemyPokemonConfig = {
|
||||||
species: bossSpecies,
|
species: bossSpecies,
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
|
shiny: false, // Shiny lock because of custom intro sprite
|
||||||
formIndex: 1, // Gmax
|
formIndex: 1, // Gmax
|
||||||
bossSegmentModifier: 1, // +1 Segment from normal
|
bossSegmentModifier: 1, // +1 Segment from normal
|
||||||
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
|
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
|
||||||
|
|
|
@ -100,7 +100,9 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: -5,
|
x: -5,
|
||||||
repeat: true,
|
repeat: true,
|
||||||
isPokemon: true
|
isPokemon: true,
|
||||||
|
isShiny: pokemon.shiny,
|
||||||
|
variant: pokemon.variant
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -113,13 +115,15 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
const pokemonSprite = encounter.introVisuals!.getSprites();
|
const pokemonSprite = encounter.introVisuals!.getSprites();
|
||||||
|
|
||||||
scene.tweens.add({ // Bounce at the end
|
// Bounce at the end, then shiny sparkle if the Pokemon is shiny
|
||||||
|
scene.tweens.add({
|
||||||
targets: pokemonSprite,
|
targets: pokemonSprite,
|
||||||
duration: 300,
|
duration: 300,
|
||||||
ease: "Cubic.easeOut",
|
ease: "Cubic.easeOut",
|
||||||
yoyo: true,
|
yoyo: true,
|
||||||
y: "-=20",
|
y: "-=20",
|
||||||
loop: 1,
|
loop: 1,
|
||||||
|
onComplete: () => encounter.introVisuals?.playShinySparkles()
|
||||||
});
|
});
|
||||||
|
|
||||||
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
|
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
|
||||||
|
|
|
@ -184,7 +184,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||||
dataSource = config.dataSource;
|
dataSource = config.dataSource;
|
||||||
enemySpecies = config.species;
|
enemySpecies = config.species;
|
||||||
isBoss = config.isBoss;
|
isBoss = config.isBoss;
|
||||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, dataSource);
|
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource);
|
||||||
} else {
|
} else {
|
||||||
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||||
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true);
|
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, dataSource);
|
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPr
|
||||||
import { Type } from "#enums/type";
|
import { Type } from "#enums/type";
|
||||||
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
|
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { Variant, VariantSet, variantColorCache, variantData } from "#app/data/variant";
|
import { Variant, VariantSet, variantData } from "#app/data/variant";
|
||||||
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
|
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
|
|
||||||
|
@ -511,29 +511,8 @@ export abstract class PokemonSpeciesForm {
|
||||||
} else {
|
} else {
|
||||||
scene.anims.get(spriteKey).frameRate = 10;
|
scene.anims.get(spriteKey).frameRate = 10;
|
||||||
}
|
}
|
||||||
let spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
|
const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
|
||||||
const useExpSprite = scene.experimentalSprites && scene.hasExpSprite(spriteKey);
|
scene.loadPokemonVariantAssets(spriteKey, spritePath, variant);
|
||||||
if (useExpSprite) {
|
|
||||||
spritePath = `exp/${spritePath}`;
|
|
||||||
}
|
|
||||||
let config = variantData;
|
|
||||||
spritePath.split("/").map(p => config ? config = config[p] : null);
|
|
||||||
const variantSet = config as VariantSet;
|
|
||||||
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
|
|
||||||
const populateVariantColors = (key: string): Promise<void> => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (variantColorCache.hasOwnProperty(key)) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
scene.cachedFetch(`./images/pokemon/variant/${spritePath}.json`).then(res => res.json()).then(c => {
|
|
||||||
variantColorCache[key] = c;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
populateVariantColors(spriteKey).then(() => resolve());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
if (startLoad) {
|
if (startLoad) {
|
||||||
|
|
|
@ -1173,16 +1173,28 @@ export function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: Tr
|
||||||
if (!ignoreEvolution) {
|
if (!ignoreEvolution) {
|
||||||
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex);
|
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex);
|
||||||
}
|
}
|
||||||
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess);
|
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, false, undefined, postProcess);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilter, trainerSlot: TrainerSlot = TrainerSlot.TRAINER, allowLegendaries?: boolean, postProcess?: (EnemyPokemon: EnemyPokemon) => void): PartyMemberFunc {
|
function getSpeciesFilterRandomPartyMemberFunc(
|
||||||
const originalSpeciesFilter = speciesFilter;
|
originalSpeciesFilter: PokemonSpeciesFilter,
|
||||||
speciesFilter = (species: PokemonSpecies) => (allowLegendaries || (!species.legendary && !species.subLegendary && !species.mythical)) && !species.isTrainerForbidden() && originalSpeciesFilter(species);
|
trainerSlot: TrainerSlot = TrainerSlot.TRAINER,
|
||||||
return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => {
|
allowLegendaries?: boolean,
|
||||||
const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex)), level, trainerSlot, undefined, undefined, postProcess);
|
postProcess?: (EnemyPokemon: EnemyPokemon) => void
|
||||||
return ret;
|
): PartyMemberFunc {
|
||||||
|
|
||||||
|
const speciesFilter = (species: PokemonSpecies): boolean => {
|
||||||
|
const notLegendary = !species.legendary && !species.subLegendary && !species.mythical;
|
||||||
|
return (allowLegendaries || notLegendary) && !species.isTrainerForbidden() && originalSpeciesFilter(species);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (scene: BattleScene, level: number, strength: PartyMemberStrength) => {
|
||||||
|
const waveIndex = scene.currentBattle.waveIndex;
|
||||||
|
const species = getPokemonSpecies(scene.randomSpecies(waveIndex, level, false, speciesFilter)
|
||||||
|
.getTrainerSpeciesForLevel(level, true, strength, waveIndex));
|
||||||
|
|
||||||
|
return scene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import BattleScene from "../battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
import * as Utils from "../utils";
|
import { Variant } from "#app/data/variant";
|
||||||
|
import { getFrameMs, randGauss } from "#app/utils";
|
||||||
|
|
||||||
export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void {
|
export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void {
|
||||||
switch (pokeballType) {
|
switch (pokeballType) {
|
||||||
|
@ -127,7 +128,7 @@ function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y:
|
||||||
|
|
||||||
const particleTimer = scene.tweens.addCounter({
|
const particleTimer = scene.tweens.addCounter({
|
||||||
repeat: -1,
|
repeat: -1,
|
||||||
duration: Utils.getFrameMs(1),
|
duration: getFrameMs(1),
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
updateParticle();
|
updateParticle();
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,7 @@ export function addPokeballCaptureStars(scene: BattleScene, pokeball: Phaser.Gam
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const dist = Utils.randGauss(25);
|
const dist = randGauss(25);
|
||||||
scene.tweens.add({
|
scene.tweens.add({
|
||||||
targets: particle,
|
targets: particle,
|
||||||
x: pokeball.x + dist,
|
x: pokeball.x + dist,
|
||||||
|
@ -185,3 +186,31 @@ export function sin(index: integer, amplitude: integer): number {
|
||||||
export function cos(index: integer, amplitude: integer): number {
|
export function cos(index: integer, amplitude: integer): number {
|
||||||
return amplitude * Math.cos(index * (Math.PI / 128));
|
return amplitude * Math.cos(index * (Math.PI / 128));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the shiny sparkle animation and sound effect for the given sprite
|
||||||
|
* First ensures that the animation has been properly initialized
|
||||||
|
* @param sparkleSprite the Sprite to play the animation on
|
||||||
|
* @param variant which shiny {@linkcode variant} to play the animation for
|
||||||
|
*/
|
||||||
|
export function doShinySparkleAnim(scene: BattleScene, sparkleSprite: Phaser.GameObjects.Sprite, variant: Variant) {
|
||||||
|
const keySuffix = variant ? `_${variant + 1}` : "";
|
||||||
|
const spriteKey = `shiny${keySuffix}`;
|
||||||
|
const animationKey = `sparkle${keySuffix}`;
|
||||||
|
|
||||||
|
// Make sure the animation exists, and create it if not
|
||||||
|
if (!scene.anims.exists(animationKey)) {
|
||||||
|
const frameNames = scene.anims.generateFrameNames(spriteKey, { suffix: ".png", end: 34 });
|
||||||
|
scene.anims.create({
|
||||||
|
key: `sparkle${keySuffix}`,
|
||||||
|
frames: frameNames,
|
||||||
|
frameRate: 32,
|
||||||
|
showOnStart: true,
|
||||||
|
hideOnComplete: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play the animation
|
||||||
|
sparkleSprite.play(animationKey);
|
||||||
|
scene.playSound("se/sparkle");
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { GameObjects } from "phaser";
|
import { GameObjects } from "phaser";
|
||||||
import BattleScene from "../battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import MysteryEncounter from "../data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig;
|
import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig;
|
||||||
|
import { Variant } from "#app/data/variant";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
type KnownFileRoot =
|
type KnownFileRoot =
|
||||||
| "arenas"
|
| "arenas"
|
||||||
|
@ -59,6 +61,10 @@ export class MysteryEncounterSpriteConfig {
|
||||||
scale?: number;
|
scale?: number;
|
||||||
/** If you are using a Pokemon sprite, set to `true`. This will ensure variant, form, gender, shiny sprites are loaded properly */
|
/** If you are using a Pokemon sprite, set to `true`. This will ensure variant, form, gender, shiny sprites are loaded properly */
|
||||||
isPokemon?: boolean;
|
isPokemon?: boolean;
|
||||||
|
/** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */
|
||||||
|
isShiny?: boolean;
|
||||||
|
/** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */
|
||||||
|
variant?: Variant;
|
||||||
/** If you are using an item sprite, set to `true` */
|
/** If you are using an item sprite, set to `true` */
|
||||||
isItem?: boolean;
|
isItem?: boolean;
|
||||||
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
||||||
|
@ -74,6 +80,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
public encounter: MysteryEncounter;
|
public encounter: MysteryEncounter;
|
||||||
public spriteConfigs: MysteryEncounterSpriteConfig[];
|
public spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||||
public enterFromRight: boolean;
|
public enterFromRight: boolean;
|
||||||
|
private shinySparkleSprites: { sprite: Phaser.GameObjects.Sprite, variant: Variant }[];
|
||||||
|
|
||||||
constructor(scene: BattleScene, encounter: MysteryEncounter) {
|
constructor(scene: BattleScene, encounter: MysteryEncounter) {
|
||||||
super(scene, -72, 76);
|
super(scene, -72, 76);
|
||||||
|
@ -86,7 +93,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isNullOrUndefined(result.species)) {
|
if (!isNullOrUndefined(result.species)) {
|
||||||
const keys = getSpriteKeysFromSpecies(result.species);
|
const keys = getSpriteKeysFromSpecies(result.species, undefined, undefined, result.isShiny, result.variant);
|
||||||
result.spriteKey = keys.spriteKey;
|
result.spriteKey = keys.spriteKey;
|
||||||
result.fileRoot = keys.fileRoot;
|
result.fileRoot = keys.fileRoot;
|
||||||
result.isPokemon = true;
|
result.isPokemon = true;
|
||||||
|
@ -120,18 +127,36 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
// Sprites with custom X or Y defined will not count for normal spacing requirements
|
// Sprites with custom X or Y defined will not count for normal spacing requirements
|
||||||
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
||||||
|
|
||||||
|
this.shinySparkleSprites = [];
|
||||||
|
const shinySparkleSprites = scene.add.container(0, 0);
|
||||||
this.spriteConfigs?.forEach((config) => {
|
this.spriteConfigs?.forEach((config) => {
|
||||||
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha } = config;
|
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha, isPokemon, isShiny, variant } = config;
|
||||||
|
|
||||||
let sprite: GameObjects.Sprite;
|
let sprite: GameObjects.Sprite;
|
||||||
let tintSprite: GameObjects.Sprite;
|
let tintSprite: GameObjects.Sprite;
|
||||||
|
let pokemonShinySparkle: Phaser.GameObjects.Sprite | undefined;
|
||||||
|
|
||||||
if (!isItem) {
|
if (isItem) {
|
||||||
sprite = getSprite(spriteKey, hasShadow, yShadow);
|
|
||||||
tintSprite = getSprite(spriteKey);
|
|
||||||
} else {
|
|
||||||
sprite = getItemSprite(spriteKey, hasShadow, yShadow);
|
sprite = getItemSprite(spriteKey, hasShadow, yShadow);
|
||||||
tintSprite = getItemSprite(spriteKey);
|
tintSprite = getItemSprite(spriteKey);
|
||||||
|
} else {
|
||||||
|
sprite = getSprite(spriteKey, hasShadow, yShadow);
|
||||||
|
tintSprite = getSprite(spriteKey);
|
||||||
|
if (isPokemon && isShiny) {
|
||||||
|
// Set Pipeline for shiny variant
|
||||||
|
sprite.setPipelineData("spriteKey", spriteKey);
|
||||||
|
tintSprite.setPipelineData("spriteKey", spriteKey);
|
||||||
|
sprite.setPipelineData("shiny", true);
|
||||||
|
sprite.setPipelineData("variant", variant);
|
||||||
|
tintSprite.setPipelineData("shiny", true);
|
||||||
|
tintSprite.setPipelineData("variant", variant);
|
||||||
|
// Create Sprite for shiny Sparkle
|
||||||
|
pokemonShinySparkle = scene.add.sprite(sprite.x, sprite.y, "shiny");
|
||||||
|
pokemonShinySparkle.setOrigin(0.5, 1);
|
||||||
|
pokemonShinySparkle.setVisible(false);
|
||||||
|
this.shinySparkleSprites.push({ sprite: pokemonShinySparkle, variant: variant ?? 0 });
|
||||||
|
shinySparkleSprites.add(pokemonShinySparkle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sprite.setVisible(!config.hidden);
|
sprite.setVisible(!config.hidden);
|
||||||
|
@ -165,6 +190,11 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isNullOrUndefined(pokemonShinySparkle)) {
|
||||||
|
// Offset the sparkle to match the Pokemon's position
|
||||||
|
pokemonShinySparkle.setPosition(sprite.x, sprite.y);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isNullOrUndefined(alpha)) {
|
if (!isNullOrUndefined(alpha)) {
|
||||||
sprite.setAlpha(alpha);
|
sprite.setAlpha(alpha);
|
||||||
tintSprite.setAlpha(alpha);
|
tintSprite.setAlpha(alpha);
|
||||||
|
@ -173,6 +203,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
this.add(sprite);
|
this.add(sprite);
|
||||||
this.add(tintSprite);
|
this.add(tintSprite);
|
||||||
});
|
});
|
||||||
|
this.add(shinySparkleSprites);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -187,6 +218,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
this.spriteConfigs.forEach((config) => {
|
this.spriteConfigs.forEach((config) => {
|
||||||
if (config.isPokemon) {
|
if (config.isPokemon) {
|
||||||
this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
|
this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
|
||||||
|
if (config.isShiny) {
|
||||||
|
this.scene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant);
|
||||||
|
}
|
||||||
} else if (config.isItem) {
|
} else if (config.isItem) {
|
||||||
this.scene.loadAtlas("items", "");
|
this.scene.loadAtlas("items", "");
|
||||||
} else {
|
} else {
|
||||||
|
@ -240,11 +274,21 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
this.getSprites().map((sprite, i) => {
|
this.getSprites().map((sprite, i) => {
|
||||||
if (!this.spriteConfigs[i].isItem) {
|
if (!this.spriteConfigs[i].isItem) {
|
||||||
sprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
sprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
||||||
|
if (sprite.texture.frameTotal > 1) {
|
||||||
|
// Show the first animation frame for a smooth transition when the animation starts.
|
||||||
|
const firstFrame = sprite.texture.frames["0001.png"];
|
||||||
|
sprite.setFrame(firstFrame ?? 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.getTintSprites().map((tintSprite, i) => {
|
this.getTintSprites().map((tintSprite, i) => {
|
||||||
if (!this.spriteConfigs[i].isItem) {
|
if (!this.spriteConfigs[i].isItem) {
|
||||||
tintSprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
tintSprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
||||||
|
if (tintSprite.texture.frameTotal > 1) {
|
||||||
|
// Show the first frame for a smooth transition when the animation starts.
|
||||||
|
const firstFrame = tintSprite.texture.frames["0001.png"];
|
||||||
|
tintSprite.setFrame(firstFrame ?? 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -288,6 +332,17 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play shiny sparkle animations if there are shiny Pokemon
|
||||||
|
*/
|
||||||
|
playShinySparkles() {
|
||||||
|
for (const sparkleConfig of this.shinySparkleSprites) {
|
||||||
|
this.scene.time.delayedCall(500, () => {
|
||||||
|
doShinySparkleAnim(this.scene, sparkleConfig.sprite, sparkleConfig.variant);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For sprites with animation and that do not have animation disabled, will begin frame animation
|
* For sprites with animation and that do not have animation disabled, will begin frame animation
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -69,6 +69,7 @@ import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates";
|
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
export enum FieldPosition {
|
export enum FieldPosition {
|
||||||
CENTER,
|
CENTER,
|
||||||
|
@ -673,21 +674,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
initShinySparkle(): void {
|
initShinySparkle(): void {
|
||||||
const keySuffix = this.variant ? `_${this.variant + 1}` : "";
|
const shinySparkle = this.scene.addFieldSprite(0, 0, "shiny");
|
||||||
const key = `shiny${keySuffix}`;
|
|
||||||
const shinySparkle = this.scene.addFieldSprite(0, 0, key);
|
|
||||||
shinySparkle.setVisible(false);
|
shinySparkle.setVisible(false);
|
||||||
shinySparkle.setOrigin(0.5, 1);
|
shinySparkle.setOrigin(0.5, 1);
|
||||||
const frameNames = this.scene.anims.generateFrameNames(key, { suffix: ".png", end: 34 });
|
|
||||||
if (!(this.scene.anims.exists(`sparkle${keySuffix}`))) {
|
|
||||||
this.scene.anims.create({
|
|
||||||
key: `sparkle${keySuffix}`,
|
|
||||||
frames: frameNames,
|
|
||||||
frameRate: 32,
|
|
||||||
showOnStart: true,
|
|
||||||
hideOnComplete: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.add(shinySparkle);
|
this.add(shinySparkle);
|
||||||
|
|
||||||
this.shinySparkle = shinySparkle;
|
this.shinySparkle = shinySparkle;
|
||||||
|
@ -1976,6 +1965,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
/**
|
/**
|
||||||
* Function that tries to set a Pokemon shiny based on seed.
|
* Function that tries to set a Pokemon shiny based on seed.
|
||||||
* For manual use only, usually to roll a Pokemon's shiny chance a second time.
|
* For manual use only, usually to roll a Pokemon's shiny chance a second time.
|
||||||
|
* If it rolls shiny, also sets a random variant and give the Pokemon the associated luck.
|
||||||
*
|
*
|
||||||
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
|
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
|
||||||
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
||||||
|
@ -2001,6 +1991,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||||
|
|
||||||
if (this.shiny) {
|
if (this.shiny) {
|
||||||
|
this.variant = this.generateShinyVariant();
|
||||||
|
this.luck = this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0);
|
||||||
this.initShinySparkle();
|
this.initShinySparkle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3802,8 +3794,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
|
|
||||||
sparkle(): void {
|
sparkle(): void {
|
||||||
if (this.shinySparkle) {
|
if (this.shinySparkle) {
|
||||||
this.shinySparkle.play(`sparkle${this.variant ? `_${this.variant + 1}` : ""}`);
|
doShinySparkleAnim(this.scene, this.shinySparkle, this.variant);
|
||||||
this.scene.playSound("se/sparkle");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4646,12 +4637,13 @@ export class EnemyPokemon extends Pokemon {
|
||||||
public aiType: AiType;
|
public aiType: AiType;
|
||||||
public bossSegments: integer;
|
public bossSegments: integer;
|
||||||
public bossSegmentIndex: integer;
|
public bossSegmentIndex: integer;
|
||||||
/** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */
|
/** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */
|
||||||
public readonly isPopulatedFromDataSource: boolean;
|
public readonly isPopulatedFromDataSource: boolean;
|
||||||
|
|
||||||
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource?: PokemonData) {
|
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) {
|
||||||
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex,
|
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender,
|
||||||
dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource);
|
(!shinyLock && dataSource) ? dataSource.shiny : false, (!shinyLock && dataSource) ? dataSource.variant : undefined,
|
||||||
|
undefined, dataSource ? dataSource.nature : undefined, dataSource);
|
||||||
|
|
||||||
this.trainerSlot = trainerSlot;
|
this.trainerSlot = trainerSlot;
|
||||||
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
|
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
|
||||||
|
@ -4680,12 +4672,15 @@ export class EnemyPokemon extends Pokemon {
|
||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
this.generateAndPopulateMoveset();
|
this.generateAndPopulateMoveset();
|
||||||
|
|
||||||
this.trySetShiny();
|
if (shinyLock || Overrides.OPP_SHINY_OVERRIDE === false) {
|
||||||
if (Overrides.OPP_SHINY_OVERRIDE) {
|
this.shiny = false;
|
||||||
|
} else {
|
||||||
|
this.trySetShiny();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.shiny && Overrides.OPP_SHINY_OVERRIDE) {
|
||||||
this.shiny = true;
|
this.shiny = true;
|
||||||
this.initShinySparkle();
|
this.initShinySparkle();
|
||||||
} else if (Overrides.OPP_SHINY_OVERRIDE === false) {
|
|
||||||
this.shiny = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.shiny) {
|
if (this.shiny) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||||
import * as Utils from "#app/utils";
|
import * as Utils from "#app/utils";
|
||||||
import { EggLapsePhase } from "./egg-lapse-phase";
|
import { EggLapsePhase } from "./egg-lapse-phase";
|
||||||
import { EggHatchData } from "#app/data/egg-hatch-data";
|
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
|
import { doShinySparkleAnim } from "#app/field/anims";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -341,8 +342,7 @@ export class EggHatchPhase extends Phase {
|
||||||
this.pokemon.cry();
|
this.pokemon.cry();
|
||||||
if (isShiny) {
|
if (isShiny) {
|
||||||
this.scene.time.delayedCall(Utils.fixedInt(500), () => {
|
this.scene.time.delayedCall(Utils.fixedInt(500), () => {
|
||||||
this.pokemonShinySparkle.play(`sparkle${this.pokemon.variant ? `_${this.pokemon.variant + 1}` : ""}`);
|
doShinySparkleAnim(this.scene, this.pokemonShinySparkle, this.pokemon.variant);
|
||||||
this.scene.playSound("se/sparkle");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => {
|
this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => {
|
||||||
|
|
|
@ -379,6 +379,9 @@ export class EncounterPhase extends BattlePhase {
|
||||||
|
|
||||||
if (encounter.onVisualsStart) {
|
if (encounter.onVisualsStart) {
|
||||||
encounter.onVisualsStart(this.scene);
|
encounter.onVisualsStart(this.scene);
|
||||||
|
} else if (encounter.spriteConfigs && introVisuals) {
|
||||||
|
// If the encounter doesn't have any special visual intro, show sparkle for shiny Pokemon
|
||||||
|
introVisuals.playShinySparkles();
|
||||||
}
|
}
|
||||||
|
|
||||||
const doEncounter = () => {
|
const doEncounter = () => {
|
||||||
|
|
|
@ -171,7 +171,7 @@ export default class PokemonData {
|
||||||
playerPokemon.nickname = this.nickname;
|
playerPokemon.nickname = this.nickname;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
: scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, this);
|
: scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, false, this);
|
||||||
if (this.summonData) {
|
if (this.summonData) {
|
||||||
ret.primeSummonData(this.summonData);
|
ret.primeSummonData(this.summonData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
|
|
||||||
const namespace = "mysteryEncounters/globalTradeSystem";
|
const namespace = "mysteryEncounters/globalTradeSystem";
|
||||||
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||||
|
@ -176,6 +177,23 @@ describe("Global Trade System - Mystery Encounter", () => {
|
||||||
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
|
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should roll for shiny twice, with random variant and associated luck", async () => {
|
||||||
|
// This ensures that the first shiny roll gets ignored, to test the ME rerolling for shiny
|
||||||
|
game.override.enemyShiny(false);
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||||
|
|
||||||
|
vi.spyOn(Utils, "randSeedInt").mockReturnValue(1); // force shiny on reroll
|
||||||
|
|
||||||
|
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
|
||||||
|
|
||||||
|
const receivedPokemon = scene.getPlayerParty().at(-1)!;
|
||||||
|
|
||||||
|
expect(receivedPokemon.shiny).toBeTruthy();
|
||||||
|
expect(receivedPokemon.variant).toBeDefined();
|
||||||
|
expect(receivedPokemon.luck).toBe(receivedPokemon.variant + 1);
|
||||||
|
});
|
||||||
|
|
||||||
it("should leave encounter without battle", async () => {
|
it("should leave encounter without battle", async () => {
|
||||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should update the player's money properly", async () => {
|
it("should update the player's money properly", async () => {
|
||||||
const initialMoney = 20000;
|
const initialMoney = 20000;
|
||||||
scene.money = initialMoney;
|
scene.money = initialMoney;
|
||||||
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||||
|
@ -137,7 +137,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||||
expect(scene.money).toBe(initialMoney - price);
|
expect(scene.money).toBe(initialMoney - price);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should add the Pokemon to the party", async () => {
|
it("should add the Pokemon to the party", async () => {
|
||||||
scene.money = 20000;
|
scene.money = 20000;
|
||||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
||||||
|
|
||||||
|
@ -153,6 +153,18 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||||
expect(newlyPurchasedPokemon!.moveset.length > 0).toBeTruthy();
|
expect(newlyPurchasedPokemon!.moveset.length > 0).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should give the purchased Pokemon its HA or make it shiny", async () => {
|
||||||
|
scene.money = 20000;
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 1);
|
||||||
|
|
||||||
|
const newlyPurchasedPokemon = scene.getPlayerParty()[scene.getPlayerParty().length - 1];
|
||||||
|
const isshiny = newlyPurchasedPokemon.shiny;
|
||||||
|
const hasHA = newlyPurchasedPokemon.abilityIndex === 2;
|
||||||
|
expect(isshiny || hasHA).toBeTruthy();
|
||||||
|
expect(isshiny && hasHA).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
it("should be disabled if player does not have enough money", async () => {
|
it("should be disabled if player does not have enough money", async () => {
|
||||||
scene.money = 0;
|
scene.money = 0;
|
||||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
||||||
|
|
|
@ -109,6 +109,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||||
species: getPokemonSpecies(Species.SHUCKLE),
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 5,
|
bossSegments: 5,
|
||||||
|
shiny: false,
|
||||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||||
nature: Nature.BOLD,
|
nature: Nature.BOLD,
|
||||||
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
||||||
|
|
|
@ -92,6 +92,7 @@ describe("Trash to Treasure - Mystery Encounter", () => {
|
||||||
{
|
{
|
||||||
species: getPokemonSpecies(Species.GARBODOR),
|
species: getPokemonSpecies(Species.GARBODOR),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
|
shiny: false,
|
||||||
formIndex: 1,
|
formIndex: 1,
|
||||||
bossSegmentModifier: 1,
|
bossSegmentModifier: 1,
|
||||||
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ],
|
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ],
|
||||||
|
|
Loading…
Reference in New Issue