This commit is contained in:
Fuad Ali 2025-06-20 00:00:13 -04:00 committed by GitHub
commit da41e94451
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 985 additions and 22 deletions

BIN
public/images/heavy_fog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

View File

@ -8450,6 +8450,27 @@
"w": 16,
"h": 16
}
},
{
"filename": "micle_berry",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 20,
"h": 20
},
"spriteSourceSize": {
"x": 8,
"y": 8,
"w": 20,
"h": 20
},
"frame": {
"x": 400,
"y": 386,
"w": 20,
"h": 20
}
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

View File

@ -8118,7 +8118,7 @@ export function initAbilities() {
.unreplaceable()
.attr(NoFusionAbilityAbAttr)
.attr(PostSummonFormChangeByWeatherAbAttr, AbilityId.FORECAST)
.attr(PostWeatherChangeFormChangeAbAttr, AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]),
.attr(PostWeatherChangeFormChangeAbAttr, AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HEAVY_FOG ]),
new Ability(AbilityId.STICKY_HOLD, 3)
.attr(BlockItemTheftAbAttr)
.bypassFaint()
@ -8306,7 +8306,7 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.SPDEF, 1.5)
.attr(NoFusionAbilityAbAttr)
.attr(PostSummonFormChangeByWeatherAbAttr, AbilityId.FLOWER_GIFT)
.attr(PostWeatherChangeFormChangeAbAttr, AbilityId.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
.attr(PostWeatherChangeFormChangeAbAttr, AbilityId.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HEAVY_FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
.uncopiable()
.unreplaceable()
.ignorable(),
@ -9018,7 +9018,7 @@ export function initAbilities() {
.unreplaceable()
.ignorable(),
new Ability(AbilityId.TERAFORM_ZERO, 9)
.attr(ClearWeatherAbAttr, [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN, WeatherType.STRONG_WINDS ])
.attr(ClearWeatherAbAttr, [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN, WeatherType.STRONG_WINDS ])
.attr(ClearTerrainAbAttr, [ TerrainType.MISTY, TerrainType.ELECTRIC, TerrainType.GRASSY, TerrainType.PSYCHIC ])
.uncopiable()
.unreplaceable()

View File

@ -1111,7 +1111,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(SpeciesId.SLIGGOO, 40, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]})
],
[SpeciesId.SLIGGOO]: [
new SpeciesEvolution(SpeciesId.GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]}, SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(SpeciesId.GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HEAVY_FOG ]}, SpeciesWildEvolutionDelay.LONG)
],
[SpeciesId.BERGMITE]: [
new SpeciesEvolution(SpeciesId.HISUI_AVALUGG, 37, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}),
@ -1334,7 +1334,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
new SpeciesEvolution(SpeciesId.HISUI_ZOROARK, 30, null, null)
],
[SpeciesId.HISUI_SLIGGOO]: [
new SpeciesEvolution(SpeciesId.HISUI_GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN ]}, SpeciesWildEvolutionDelay.LONG)
new SpeciesEvolution(SpeciesId.HISUI_GOODRA, 50, null, {key: EvoCondKey.WEATHER, weather: [ WeatherType.RAIN, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HEAVY_FOG ]}, SpeciesWildEvolutionDelay.LONG)
],
[SpeciesId.SPRIGATITO]: [
new SpeciesEvolution(SpeciesId.FLORAGATO, 16, null, null)

View File

@ -764,9 +764,6 @@ export default abstract class Move implements Localizable {
applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy);
if (moveAccuracy.value === -1) {
return moveAccuracy.value;
}
const isOhko = this.hasAttr("OneHitKOAccuracyAttr");
@ -774,7 +771,11 @@ export default abstract class Move implements Localizable {
globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
}
if (globalScene.arena.weather?.weatherType === WeatherType.FOG) {
if (moveAccuracy.value === -1) { //Check accuracy after applying items modifier in case of Micle Berry
return moveAccuracy.value;
}
if (globalScene.arena.weather?.weatherType === WeatherType.FOG || globalScene.arena.weather?.weatherType === WeatherType.HEAVY_FOG) {
/**
* The 0.9 multiplier is PokeRogue-only implementation, Bulbapedia uses 3/5
* See Fog {@link https://bulbapedia.bulbagarden.net/wiki/Fog}
@ -9401,7 +9402,7 @@ export function initMoves() {
if (!weather) {
return 1;
}
const weatherTypes = [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN ];
const weatherTypes = [ WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN, WeatherType.HEAVY_FOG ];
if (weatherTypes.includes(weather.weatherType) && !weather.isEffectSuppressed()) {
return 2;
}

View File

@ -0,0 +1,449 @@
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initBattleWithEnemyConfig,
setEncounterRewards,
leaveEncounterWithoutBattle,
transitionMysteryEncounterIntroVisuals,
generateModifierType,
setEncounterExp,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { BerryType } from "#enums/berry-type";
import { randSeedInt } from "#app/utils/common";
import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/data/data-lists";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import { Nature } from "#enums/nature";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { TimeOfDayRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { TimeOfDay } from "#enums/time-of-day";
import type { AbilityId } from "#enums/ability-id";
import { Stat } from "#enums/stat";
import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { ModifierTier } from "#enums/modifier-tier";
import {
MoveRequirement,
AbilityRequirement,
CombinationPokemonRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import {
DEFOG_MOVES,
DEFOG_ABILITIES,
LIGHT_ABILITIES,
LIGHT_MOVES,
} from "#app/data/mystery-encounters/requirements/requirement-groups";
import { BiomeId } from "#enums/biome-id";
import { WeatherType } from "#enums/weather-type";
import FogOverlay from "#app/ui/fog-overlay";
// the i18n namespace for the encounter
const namespace = "mysteryEncounters/creepingFog";
/**
* Creeping Fog Mystery Encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/4418 | GitHub Issue #4418}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
**/
export const CreepingFogEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.CREEPING_FOG,
)
.withSceneRequirement(new TimeOfDayRequirement([TimeOfDay.DUSK, TimeOfDay.DAWN, TimeOfDay.NIGHT]))
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(51, 179)
.withFleeAllowed(false)
.withIntroSpriteConfigs([])
.withIntroDialogue([
{
text: `${namespace}:intro`,
},
])
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit(() => {
const waveIndex = globalScene.currentBattle.waveIndex;
const encounter = globalScene.currentBattle.mysteryEncounter!;
const chosenPokemonAttributes = chooseBoss();
const chosenPokemon = chosenPokemonAttributes[0] as SpeciesId;
const naturePokemon = chosenPokemonAttributes[1] as Nature;
const abilityPokemon = chosenPokemonAttributes[2] as AbilityId;
const passivePokemon = chosenPokemon[3] as boolean;
const movesPokemon = chosenPokemonAttributes[4] as MoveId[];
const modifPokemon = chosenPokemonAttributes[5] as HeldModifierConfig[];
const segments = waveIndex < 80 ? 2 : waveIndex < 140 ? 3 : 4;
const pokemonConfig: EnemyPokemonConfig = {
species: getPokemonSpecies(chosenPokemon),
formIndex: [SpeciesId.LYCANROC, SpeciesId.PIDGEOT].includes(chosenPokemon) ? 1 : 0,
isBoss: true,
shiny: false,
customPokemonData: new CustomPokemonData({ spriteScale: 1 + segments * 0.05 }),
nature: naturePokemon,
moveSet: movesPokemon,
abilityIndex: abilityPokemon,
passive: passivePokemon,
bossSegments: segments,
modifierConfigs: modifPokemon,
};
const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5,
pokemonConfigs: [pokemonConfig],
};
encounter.enemyPartyConfigs = [config];
encounter.spriteConfigs = [
{
spriteKey: chosenPokemon.toString(),
fileRoot: "pokemon",
repeat: true,
hasShadow: true,
hidden: true,
x: 0,
tint: 1,
y: 0,
yShadow: -3,
},
];
const overlayWidth = globalScene.game.canvas.width / 6;
const overlayHeight = globalScene.game.canvas.height / 6 - 48;
const fogOverlay = new FogOverlay({
delayVisibility: false,
scale: 1,
onSide: true,
right: true,
x: 1,
y: overlayHeight * -1 - 48,
width: overlayWidth,
height: overlayHeight,
});
encounter.misc = {
fogOverlay,
};
globalScene.ui.add(fogOverlay);
globalScene.ui.sendToBack(fogOverlay);
globalScene.tweens.add({
targets: fogOverlay,
alpha: 0.5,
ease: "Sine.easeIn",
duration: 2000,
});
fogOverlay.active = true;
fogOverlay.setVisible(true);
return true;
})
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected`,
},
],
})
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.tweens.add({
targets: encounter.misc.fogOverlay,
alpha: 0,
ease: "Sine.easeOut",
duration: 2000,
});
})
.withOptionPhase(async () => {
//Battle Fog Boss
const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.arena.trySetWeather(WeatherType.HEAVY_FOG);
//TODO start fog and stuff
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
fillRemaining: true,
});
await transitionMysteryEncounterIntroVisuals();
await initBattleWithEnemyConfig(config);
})
.build(),
)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
new MoveRequirement(DEFOG_MOVES, true),
new AbilityRequirement(DEFOG_ABILITIES, true),
),
)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`,
},
],
})
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.tweens.add({
targets: encounter.misc.fogOverlay,
alpha: 0,
ease: "Sine.easeOut",
duration: 2000,
});
})
.withOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const primary = encounter.options[1].primaryPokemon!;
if (globalScene.currentBattle.waveIndex >= 140) {
setEncounterExp([primary.id], encounter.enemyPartyConfigs![0].pokemonConfigs![0].species.baseExp);
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
fillRemaining: true,
});
await transitionMysteryEncounterIntroVisuals();
await initBattleWithEnemyConfig(config);
} else {
setEncounterExp([primary.id], encounter.enemyPartyConfigs![0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle();
}
})
.build(),
)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
new MoveRequirement(LIGHT_MOVES, true),
new AbilityRequirement(LIGHT_ABILITIES, true),
),
)
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`,
},
],
})
.withPreOptionPhase(async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.tweens.add({
targets: encounter.misc.fogOverlay,
alpha: 0,
ease: "Sine.easeOut",
duration: 2000,
});
})
.withOptionPhase(async () => {
//Navigate through the Fog
const encounter = globalScene.currentBattle.mysteryEncounter!;
const primary = encounter.options[2].primaryPokemon!;
globalScene.arena.trySetWeather(WeatherType.HEAVY_FOG);
if (globalScene.currentBattle.waveIndex >= 140) {
setEncounterExp([primary.id], encounter.enemyPartyConfigs![0].pokemonConfigs![0].species.baseExp);
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE],
fillRemaining: true,
});
await transitionMysteryEncounterIntroVisuals();
await initBattleWithEnemyConfig(config);
} else {
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ULTRA],
fillRemaining: true,
});
setEncounterExp([primary.id], encounter.enemyPartyConfigs![0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle();
}
})
.build(),
)
.withSimpleOption(
{
buttonLabel: `${namespace}:option.4.label`,
buttonTooltip: `${namespace}:option.4.tooltip`,
selected: [
{
text: `${namespace}:option.4.selected`,
},
],
},
async () => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
globalScene.tweens.add({
targets: encounter.misc.fogOverlay,
alpha: 0,
ease: "Sine.easeOut",
duration: 2000,
});
const pokemon = globalScene.getPlayerPokemon(); //Can we use this?
globalScene.arena.trySetWeather(WeatherType.FOG, pokemon);
// Leave encounter with no rewards or exp
leaveEncounterWithoutBattle(true);
return true;
},
)
.build();
function chooseBoss() {
const biome = globalScene.arena.biomeType;
const wave = globalScene.currentBattle.waveIndex;
const allBiomePokemon = [
[
SpeciesId.MACHAMP,
Nature.JOLLY,
1,
false,
[MoveId.DYNAMIC_PUNCH, MoveId.STONE_EDGE, MoveId.DUAL_CHOP, MoveId.FISSURE],
[],
],
[
SpeciesId.GRIMMSNARL,
Nature.ADAMANT,
null,
false,
[MoveId.STONE_EDGE, MoveId.CLOSE_COMBAT, MoveId.IRON_TAIL, MoveId.PLAY_ROUGH],
[{ modifier: generateModifierType(modifierTypes.MICLE_BERRY) as PokemonHeldItemModifierType }],
],
];
const ForestTallGrassPokemon = [
[
SpeciesId.LYCANROC,
Nature.JOLLY,
2,
false,
[MoveId.STONE_EDGE, MoveId.CLOSE_COMBAT, MoveId.IRON_TAIL, MoveId.PLAY_ROUGH],
[],
],
[
SpeciesId.ALOLA_RATICATE,
Nature.ADAMANT,
1,
false,
[MoveId.FALSE_SURRENDER, MoveId.SUCKER_PUNCH, MoveId.PLAY_ROUGH, MoveId.POPULATION_BOMB],
[{ modifier: generateModifierType(modifierTypes.REVIVER_SEED) as PokemonHeldItemModifierType }],
],
];
const SwampLakePokemon = [
[
SpeciesId.POLIWRATH,
Nature.NAIVE,
null,
true,
[MoveId.DYNAMIC_PUNCH, MoveId.HYDRO_PUMP, MoveId.DUAL_CHOP, MoveId.HYPNOSIS],
[
{ modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType },
{ modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType },
],
],
];
const GraveyardPokemon = [
[
SpeciesId.GOLURK,
Nature.ADAMANT,
2,
false,
[MoveId.EARTHQUAKE, MoveId.POLTERGEIST, MoveId.DYNAMIC_PUNCH, MoveId.STONE_EDGE],
[],
],
[
SpeciesId.HONEDGE,
Nature.CAREFUL,
0,
false,
[MoveId.IRON_HEAD, MoveId.POLTERGEIST, MoveId.SACRED_SWORD, MoveId.SHADOW_SNEAK],
[],
],
[
SpeciesId.ZWEILOUS,
Nature.BRAVE,
null,
true,
[MoveId.DRAGON_RUSH, MoveId.CRUNCH, MoveId.GUNK_SHOT, MoveId.SCREECH],
[{ modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, stackCount: 2 }],
],
];
const wave110_140Pokemon = [
[
SpeciesId.SCOLIPEDE,
Nature.ADAMANT,
2,
false,
[MoveId.MEGAHORN, MoveId.NOXIOUS_TORQUE, MoveId.ROLLOUT, MoveId.BANEFUL_BUNKER],
[{ modifier: generateModifierType(modifierTypes.MICLE_BERRY) as PokemonHeldItemModifierType }],
],
[
SpeciesId.MIENSHAO,
Nature.JOLLY,
null,
true,
[MoveId.HIGH_JUMP_KICK, MoveId.STONE_EDGE, MoveId.BLAZE_KICK, MoveId.GUNK_SHOT],
[],
],
[
SpeciesId.DRACOZOLT,
Nature.JOLLY,
null,
true,
[MoveId.BOLT_BEAK, MoveId.DRAGON_RUSH, MoveId.EARTHQUAKE, MoveId.STONE_EDGE],
[],
],
];
const wave140PlusPokemon = [
[
SpeciesId.PIDGEOT,
Nature.HASTY,
0,
false,
[MoveId.HURRICANE, MoveId.HEAT_WAVE, MoveId.FOCUS_BLAST, MoveId.WILDBOLT_STORM],
[],
],
];
let pool = allBiomePokemon as [SpeciesId, Nature, AbilityId, boolean, MoveId[], HeldModifierConfig[]][];
// Include biome-specific Pokémon if within wave 50-80
if (wave >= 50) {
if (biome === BiomeId.FOREST || biome === BiomeId.TALL_GRASS) {
pool = pool.concat(
ForestTallGrassPokemon as [SpeciesId, Nature, AbilityId, boolean, MoveId[], HeldModifierConfig[]][],
);
}
if (biome === BiomeId.SWAMP || biome === BiomeId.LAKE) {
pool = pool.concat(SwampLakePokemon as [SpeciesId, Nature, AbilityId, boolean, MoveId[], HeldModifierConfig[]][]);
}
if (biome === BiomeId.GRAVEYARD) {
pool = pool.concat(GraveyardPokemon as [SpeciesId, Nature, AbilityId, boolean, MoveId[], HeldModifierConfig[]][]);
}
}
// Waves 110-140 content
if (wave >= 110) {
pool = pool.concat(wave110_140Pokemon as [SpeciesId, Nature, AbilityId, boolean, MoveId[], HeldModifierConfig[]][]);
}
// Wave 140+
if (wave >= 140) {
pool = pool.concat(wave140PlusPokemon as [SpeciesId, Nature, AbilityId, boolean, MoveId[], HeldModifierConfig[]][]);
}
// Randomly choose one
return pool[randSeedInt(pool.length, 0)];
}

View File

@ -32,6 +32,7 @@ import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fu
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
import { CreepingFogEncounter } from "#app/data/mystery-encounters/encounters/creeping-fog-encounter";
import { getBiomeName } from "#app/data/balance/biomes";
export const EXTREME_ENCOUNTER_BIOMES = [
@ -192,11 +193,11 @@ export const mysteryEncountersByBiome = new Map<BiomeId, MysteryEncounterType[]>
[BiomeId.TOWN, []],
[BiomeId.PLAINS, [MysteryEncounterType.SLUMBERING_SNORLAX]],
[BiomeId.GRASS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE]],
[BiomeId.TALL_GRASS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE]],
[BiomeId.TALL_GRASS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE, MysteryEncounterType.CREEPING_FOG]],
[BiomeId.METROPOLIS, []],
[BiomeId.FOREST, [MysteryEncounterType.SAFARI_ZONE, MysteryEncounterType.ABSOLUTE_AVARICE]],
[BiomeId.FOREST, [MysteryEncounterType.SAFARI_ZONE, MysteryEncounterType.ABSOLUTE_AVARICE, MysteryEncounterType.CREEPING_FOG]],
[BiomeId.SEA, [MysteryEncounterType.LOST_AT_SEA]],
[BiomeId.SWAMP, [MysteryEncounterType.SAFARI_ZONE]],
[BiomeId.SWAMP, [MysteryEncounterType.SAFARI_ZONE, MysteryEncounterType.CREEPING_FOG]],
[BiomeId.BEACH, []],
[BiomeId.LAKE, []],
[BiomeId.SEABED, []],
@ -208,7 +209,7 @@ export const mysteryEncountersByBiome = new Map<BiomeId, MysteryEncounterType[]>
[BiomeId.MEADOW, []],
[BiomeId.POWER_PLANT, []],
[BiomeId.VOLCANO, [MysteryEncounterType.FIERY_FALLOUT, MysteryEncounterType.DANCING_LESSONS]],
[BiomeId.GRAVEYARD, []],
[BiomeId.GRAVEYARD, [MysteryEncounterType.CREEPING_FOG]],
[BiomeId.DOJO, []],
[BiomeId.FACTORY, []],
[BiomeId.RUINS, []],
@ -257,6 +258,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter;
allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter;
allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter;
allMysteryEncounters[MysteryEncounterType.CREEPING_FOG] = CreepingFogEncounter;
// Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => {

View File

@ -108,6 +108,16 @@ export const EXTORTION_MOVES = [
MoveId.STRING_SHOT,
];
/**
* Moves that can clear a foggy weather
*/
export const DEFOG_MOVES = [MoveId.DEFOG, MoveId.RAPID_SPIN, MoveId.GUST];
/**
* Moves that can help navigate through foggy weather
*/
export const LIGHT_MOVES = [MoveId.FLASH, MoveId.FORESIGHT];
/**
* Abilities that (loosely) can be used to trap/rob someone
*/
@ -135,3 +145,18 @@ export const FIRE_RESISTANT_ABILITIES = [
AbilityId.STEAM_ENGINE,
AbilityId.PRIMORDIAL_SEA,
];
/**
* Abilities that can clear foggy weather
*/
export const DEFOG_ABILITIES = [AbilityId.AIR_LOCK, AbilityId.CLOUD_NINE];
/**
* Abilities that can help navigate through foggy weather
*/
export const LIGHT_ABILITIES = [
AbilityId.KEEN_EYE,
AbilityId.ILLUMINATE,
AbilityId.COMPOUND_EYES,
AbilityId.VICTORY_STAR,
];

View File

@ -256,9 +256,9 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(SpeciesId.CASTFORM, "", "snowy", new SpeciesFormChangeWeatherTrigger(AbilityId.FORECAST, [ WeatherType.HAIL, WeatherType.SNOW ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "sunny", "snowy", new SpeciesFormChangeWeatherTrigger(AbilityId.FORECAST, [ WeatherType.HAIL, WeatherType.SNOW ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "rainy", "snowy", new SpeciesFormChangeWeatherTrigger(AbilityId.FORECAST, [ WeatherType.HAIL, WeatherType.SNOW ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "sunny", "", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "rainy", "", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "snowy", "", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "sunny", "", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HEAVY_FOG ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "rainy", "", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HEAVY_FOG ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "snowy", "", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HEAVY_FOG ]), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "sunny", "", new SpeciesFormChangeActiveTrigger(), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "rainy", "", new SpeciesFormChangeActiveTrigger(), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "snowy", "", new SpeciesFormChangeActiveTrigger(), true)
@ -300,7 +300,7 @@ export const pokemonFormChanges: PokemonFormChanges = {
],
[SpeciesId.CHERRIM]: [
new SpeciesFormChange(SpeciesId.CHERRIM, "overcast", "sunshine", new SpeciesFormChangeWeatherTrigger(AbilityId.FLOWER_GIFT, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]), true),
new SpeciesFormChange(SpeciesId.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ]), true),
new SpeciesFormChange(SpeciesId.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeRevertWeatherFormTrigger(AbilityId.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HEAVY_FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ]), true),
new SpeciesFormChange(SpeciesId.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeActiveTrigger(), true)
],
[SpeciesId.LOPUNNY]: [

View File

@ -37,6 +37,7 @@ export class Weather {
case WeatherType.HEAVY_RAIN:
case WeatherType.HARSH_SUN:
case WeatherType.STRONG_WINDS:
case WeatherType.HEAVY_FOG:
return true;
}
@ -137,6 +138,8 @@ export function getWeatherStartMessage(weatherType: WeatherType): string | null
return i18next.t("weather:snowStartMessage");
case WeatherType.FOG:
return i18next.t("weather:fogStartMessage");
case WeatherType.HEAVY_FOG:
return i18next.t("weather:heavyFogStartMessage");
case WeatherType.HEAVY_RAIN:
return i18next.t("weather:heavyRainStartMessage");
case WeatherType.HARSH_SUN:
@ -162,6 +165,8 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string | null
return i18next.t("weather:snowLapseMessage");
case WeatherType.FOG:
return i18next.t("weather:fogLapseMessage");
case WeatherType.HEAVY_FOG:
return i18next.t("weather:heavyFogLapseMessage");
case WeatherType.HEAVY_RAIN:
return i18next.t("weather:heavyRainLapseMessage");
case WeatherType.HARSH_SUN:
@ -202,6 +207,8 @@ export function getWeatherClearMessage(weatherType: WeatherType): string | null
return i18next.t("weather:snowClearMessage");
case WeatherType.FOG:
return i18next.t("weather:fogClearMessage");
case WeatherType.HEAVY_FOG:
return i18next.t("weather:fogClearMessage");
case WeatherType.HEAVY_RAIN:
return i18next.t("weather:heavyRainClearMessage");
case WeatherType.HARSH_SUN:
@ -221,6 +228,8 @@ export function getLegendaryWeatherContinuesMessage(weatherType: WeatherType): s
return i18next.t("weather:heavyRainContinueMessage");
case WeatherType.STRONG_WINDS:
return i18next.t("weather:strongWindsContinueMessage");
case WeatherType.HEAVY_FOG:
return i18next.t("weather:heavyFogContinueMessage");
}
return null;
}

View File

@ -29,5 +29,6 @@ export enum MysteryEncounterType {
FUN_AND_GAMES,
UNCOMMON_BREED,
GLOBAL_TRADE_SYSTEM,
THE_EXPERT_POKEMON_BREEDER
THE_EXPERT_POKEMON_BREEDER,
CREEPING_FOG
}

View File

@ -9,4 +9,5 @@ export enum WeatherType {
HEAVY_RAIN,
HARSH_SUN,
STRONG_WINDS,
HEAVY_FOG,
}

View File

@ -327,8 +327,15 @@ export class Arena {
if (
this.weather?.isImmutable() &&
![WeatherType.HARSH_SUN, WeatherType.HEAVY_RAIN, WeatherType.STRONG_WINDS, WeatherType.NONE].includes(weather)
![
WeatherType.HARSH_SUN,
WeatherType.HEAVY_RAIN,
WeatherType.STRONG_WINDS,
WeatherType.HEAVY_FOG,
WeatherType.NONE,
].includes(weather)
) {
if (oldWeatherType !== WeatherType.HEAVY_FOG) {
globalScene.phaseManager.unshiftNew(
"CommonAnimPhase",
undefined,
@ -336,6 +343,7 @@ export class Arena {
CommonAnim.SUNNY + (oldWeatherType - 1),
true,
);
}
globalScene.phaseManager.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!);
return false;
}

View File

@ -236,6 +236,7 @@ export class LoadingScene extends SceneBase {
this.loadAtlas("pb", "");
this.loadAtlas("items", "");
this.loadImage("heavy_fog", "");
this.loadAtlas("types", "");
// Get current lang and load the types atlas for it. English will only load types while all other languages will load types and types_<lang>

View File

@ -1147,6 +1147,9 @@ export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModif
}
getDescription(): string {
if (this.amount === -1) {
return i18next.t("modifierType:ModifierType.MICLE_BERRY.description");
}
return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", {
accuracyAmount: this.amount,
});
@ -2141,6 +2144,8 @@ const modifierTypeInitObj = Object.freeze({
GRIP_CLAW: () =>
new ContactHeldItemTransferChanceModifierType("modifierType:ModifierType.GRIP_CLAW", "grip_claw", 10),
WIDE_LENS: () => new PokemonMoveAccuracyBoosterModifierType("modifierType:ModifierType.WIDE_LENS", "wide_lens", 5),
MICLE_BERRY: () =>
new PokemonMoveAccuracyBoosterModifierType("modifierType:ModifierType.MICLE_BERRY", "micle_berry", -1),
MULTI_LENS: () => new PokemonMultiHitModifierType("modifierType:ModifierType.MULTI_LENS", "zoom_lens"),

View File

@ -2739,8 +2739,11 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier
* @returns always `true`
*/
override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean {
moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * this.getStackCount();
if (this.accuracyAmount !== -1) {
moveAccuracy.value = moveAccuracy.value + this.accuracyAmount * this.getStackCount();
} else {
moveAccuracy.value = -1;
}
return true;
}

View File

@ -299,6 +299,7 @@ export async function initI18n(): Promise<void> {
"mysteryEncounters/uncommonBreed",
"mysteryEncounters/globalTradeSystem",
"mysteryEncounters/theExpertPokemonBreeder",
"mysteryEncounters/creepingFog",
"mysteryEncounterMessages",
],
detection: {

52
src/ui/fog-overlay.ts Normal file
View File

@ -0,0 +1,52 @@
import { globalScene } from "#app/global-scene";
export interface FogOverlaySettings {
delayVisibility?: boolean;
scale?: number;
top?: boolean;
right?: boolean;
onSide?: boolean;
x?: number;
y?: number;
width?: number;
height?: number;
}
const EFF_HEIGHT = 48;
const EFF_WIDTH = 82;
export default class FogOverlay extends Phaser.GameObjects.Container {
public active = false;
private val: Phaser.GameObjects.Container;
private typ: Phaser.GameObjects.Sprite;
constructor(options?: FogOverlaySettings) {
if (options?.onSide) {
options.top = false;
}
super(globalScene, options?.x, options?.y);
const scale = options?.scale || 1; // set up the scale
this.setScale(scale);
this.val = new Phaser.GameObjects.Container(
globalScene,
options?.onSide && !options?.right ? EFF_WIDTH : 0,
options?.top ? EFF_HEIGHT : 0,
);
this.typ = globalScene.add.sprite(25, EFF_HEIGHT - 35, "heavy_fog");
this.typ.setAlpha(1);
this.setAlpha(0);
this.typ.setScale(0.8);
this.val.add(this.typ);
this.add(this.val);
this.setVisible(false);
}
clear() {
this.setVisible(false);
this.active = false;
}
isActive(): boolean {
return this.active;
}
}

View File

@ -0,0 +1,372 @@
import type BattleScene from "#app/battle-scene";
import { CreepingFogEncounter } from "#app/data/mystery-encounters/encounters/creeping-fog-encounter";
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { BiomeId } from "#enums/biome-id";
import { TimeOfDay } from "#enums/time-of-day";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { ModifierTier } from "#enums/modifier-tier";
import { CommandPhase } from "#app/phases/command-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { UiMode } from "#enums/ui-mode";
import { MoveId } from "#enums/move-id";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import {
runMysteryEncounterToEnd,
runSelectMysteryEncounterOption,
skipBattleRunMysteryEncounterRewardsPhase,
} from "#test/mystery-encounter/encounter-test-utils";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import GameManager from "#test/testUtils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { WeatherType } from "#enums/weather-type";
const namespace = "mysteryEncounters/creepingFog";
const defaultParty = [SpeciesId.LAPRAS, SpeciesId.GENGAR, SpeciesId.ABRA];
const defaultBiome = BiomeId.FOREST;
const defaultWave = 51;
const enemyPokemonForest50_110 = [
SpeciesId.MACHAMP,
SpeciesId.GRIMMSNARL,
SpeciesId.LYCANROC,
SpeciesId.ALOLA_RATICATE,
];
const enemyPokemonSwamp110_140 = [
SpeciesId.MACHAMP,
SpeciesId.GRIMMSNARL,
SpeciesId.POLIWRATH,
SpeciesId.SCOLIPEDE,
SpeciesId.MIENSHAO,
SpeciesId.DRACOZOLT,
];
const enemyPokemonGraveyard140_Plus = [
SpeciesId.MACHAMP,
SpeciesId.GRIMMSNARL,
SpeciesId.GOLURK,
SpeciesId.HONEDGE,
SpeciesId.ZWEILOUS,
SpeciesId.SCOLIPEDE,
SpeciesId.MIENSHAO,
SpeciesId.DRACOZOLT,
SpeciesId.PIDGEOT,
];
const enemyMoveset = {
[SpeciesId.MACHAMP]: [MoveId.DYNAMIC_PUNCH, MoveId.STONE_EDGE, MoveId.DUAL_CHOP, MoveId.FISSURE],
[SpeciesId.GRIMMSNARL]: [MoveId.STONE_EDGE, MoveId.CLOSE_COMBAT, MoveId.IRON_TAIL, MoveId.PLAY_ROUGH],
[SpeciesId.LYCANROC]: [MoveId.STONE_EDGE, MoveId.CLOSE_COMBAT, MoveId.IRON_TAIL, MoveId.PLAY_ROUGH],
[SpeciesId.ALOLA_RATICATE]: [MoveId.FALSE_SURRENDER, MoveId.SUCKER_PUNCH, MoveId.PLAY_ROUGH, MoveId.POPULATION_BOMB],
[SpeciesId.POLIWRATH]: [MoveId.DYNAMIC_PUNCH, MoveId.HYDRO_PUMP, MoveId.DUAL_CHOP, MoveId.HYPNOSIS],
[SpeciesId.GOLURK]: [MoveId.EARTHQUAKE, MoveId.POLTERGEIST, MoveId.DYNAMIC_PUNCH, MoveId.STONE_EDGE],
[SpeciesId.HONEDGE]: [MoveId.IRON_HEAD, MoveId.POLTERGEIST, MoveId.SACRED_SWORD, MoveId.SHADOW_SNEAK],
[SpeciesId.ZWEILOUS]: [MoveId.DRAGON_RUSH, MoveId.CRUNCH, MoveId.GUNK_SHOT, MoveId.SCREECH],
[SpeciesId.SCOLIPEDE]: [MoveId.MEGAHORN, MoveId.NOXIOUS_TORQUE, MoveId.ROLLOUT, MoveId.BANEFUL_BUNKER],
[SpeciesId.MIENSHAO]: [MoveId.HIGH_JUMP_KICK, MoveId.STONE_EDGE, MoveId.BLAZE_KICK, MoveId.GUNK_SHOT],
[SpeciesId.DRACOZOLT]: [MoveId.BOLT_BEAK, MoveId.DRAGON_RUSH, MoveId.EARTHQUAKE, MoveId.STONE_EDGE],
[SpeciesId.PIDGEOT]: [MoveId.HURRICANE, MoveId.HEAT_WAVE, MoveId.FOCUS_BLAST, MoveId.WILDBOLT_STORM],
};
describe("Creeping Fog - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves();
game.override.startingTimeOfDay(TimeOfDay.NIGHT);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<BiomeId, MysteryEncounterType[]>([
[BiomeId.FOREST, [MysteryEncounterType.CREEPING_FOG]],
[BiomeId.FOREST, [MysteryEncounterType.SAFARI_ZONE]],
[BiomeId.SPACE, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
]),
);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
expect(CreepingFogEncounter.encounterType).toBe(MysteryEncounterType.CREEPING_FOG);
expect(CreepingFogEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA);
expect(CreepingFogEncounter.dialogue).toBeDefined();
expect(CreepingFogEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}:intro` }]);
expect(CreepingFogEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
expect(CreepingFogEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}:description`);
expect(CreepingFogEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}:query`);
expect(CreepingFogEncounter.options.length).toBe(4);
});
it("should not spawn outside of proper biomes", async () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.ULTRA);
game.override.startingBiome(BiomeId.SPACE);
game.override.startingTimeOfDay(TimeOfDay.NIGHT);
await game.runToMysteryEncounter();
expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.CREEPING_FOG);
});
it("should not spawn outside of proper time of day", async () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.ULTRA);
game.override.startingTimeOfDay(TimeOfDay.DAY);
await game.runToMysteryEncounter();
expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.CREEPING_FOG);
});
describe("Option 1 - Confront the shadow", () => {
it("should have the correct properties", () => {
const option1 = CreepingFogEncounter.options[0];
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected`,
},
],
});
});
it("should start battle against shadowy Pokemon from the Forest Low Level Pool", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
const partyLead = scene.getPlayerParty()[0];
partyLead.level = 1000;
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 1, undefined, true);
//Expect that the weather is set to heavy fog
expect(scene.arena.weather?.weatherType).toBe(WeatherType.HEAVY_FOG);
const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyPokemonForest50_110).toContain(enemyField[0].species.speciesId);
const moveset = enemyField[0].moveset.map(m => m.moveId);
expect(enemyMoveset[enemyField[0].species.speciesId]).toEqual(moveset);
});
it("should start battle against shadowy Pokemon from the Swamp Mid Level Pool", async () => {
game.override.startingWave(113);
game.override.startingBiome(BiomeId.SWAMP);
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
// Make party lead's level arbitrarily high to not get KOed by move
const partyLead = scene.getPlayerParty()[0];
partyLead.level = 1000;
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 1, undefined, true);
//Expect that the weather is set to heavy fog
expect(scene.arena.weather?.weatherType).toBe(WeatherType.HEAVY_FOG);
const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyPokemonSwamp110_140).toContain(enemyField[0].species.speciesId);
const moveset = enemyField[0].moveset.map(m => m.moveId);
expect(enemyMoveset[enemyField[0].species.speciesId]).toEqual(moveset);
});
it("should start battle against shadowy Pokemon from the Graveyard High Level Pool", async () => {
game.override.startingWave(143);
game.override.startingBiome(BiomeId.GRAVEYARD);
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
// Make party lead's level arbitrarily high to not get KOed by move
const partyLead = scene.getPlayerParty()[0];
partyLead.level = 1000;
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 1, undefined, true);
//Expect that the weather is set to heavy fog
expect(scene.arena.weather?.weatherType).toBe(WeatherType.HEAVY_FOG);
const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyPokemonGraveyard140_Plus).toContain(enemyField[0].species.speciesId);
const moveset = enemyField[0].moveset.map(m => m.moveId);
expect(enemyMoveset[enemyField[0].species.speciesId]).toEqual(moveset);
});
it("should have a 2 rogue tier items in the rewards after battle", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(
h => h instanceof ModifierSelectUiHandler,
) as ModifierSelectUiHandler;
expect(
modifierSelectHandler.options[0].modifierTypeOption.type.tier -
modifierSelectHandler.options[0].modifierTypeOption.upgradeCount,
).toEqual(ModifierTier.ROGUE);
expect(
modifierSelectHandler.options[1].modifierTypeOption.type.tier -
modifierSelectHandler.options[1].modifierTypeOption.upgradeCount,
).toEqual(ModifierTier.ROGUE);
});
});
describe("Option 2 - Clear the Fog", () => {
it("should have the correct properties", () => {
const option2 = CreepingFogEncounter.options[1];
expect(option2.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
expect(option2.dialogue).toBeDefined();
expect(option2.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`,
},
],
});
});
it("should skip battle with Pokemon if wave level under 140", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
scene.getPlayerParty()[1].moveset = [new PokemonMove(MoveId.DEFOG)];
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
it("should not skip battle with Pokemon", async () => {
game.override.startingWave(143);
game.override.startingBiome(BiomeId.GRAVEYARD);
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
scene.getPlayerParty()[1].moveset = [new PokemonMove(MoveId.DEFOG)];
const partyLead = scene.getPlayerParty()[0];
partyLead.level = 1000;
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyPokemonGraveyard140_Plus).toContain(enemyField[0].species.speciesId);
const moveset = enemyField[0].moveset.map(m => m.moveId);
expect(enemyMoveset[enemyField[0].species.speciesId]).toEqual(moveset);
});
it("should NOT be selectable if the player doesn't have a defog type move", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
scene.getPlayerParty().forEach(p => (p.moveset = []));
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
await runSelectMysteryEncounterOption(game, 2);
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
});
});
describe("Option 3 - Navigate through the Fog", () => {
it("should have the correct properties", () => {
const option3 = CreepingFogEncounter.options[2];
expect(option3.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
expect(option3.dialogue).toBeDefined();
expect(option3.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`,
},
],
});
});
it("should skip battle with Pokemon if wave level under 140", async () => {
game.override.startingWave(63);
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
scene.getPlayerParty()[1].moveset = [new PokemonMove(MoveId.FORESIGHT)];
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
it("should not skip battle with Pokemon", async () => {
game.override.startingWave(143);
game.override.startingBiome(BiomeId.GRAVEYARD);
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
scene.getPlayerParty()[1].moveset = [new PokemonMove(MoveId.FORESIGHT)];
const partyLead = scene.getPlayerParty()[0];
partyLead.level = 1000;
partyLead.calculateStats();
await runMysteryEncounterToEnd(game, 3, undefined, true);
//Expect that the weather is set to heavy fog
expect(scene.arena.weather?.weatherType).toBe(WeatherType.HEAVY_FOG);
const enemyField = scene.getEnemyField();
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyPokemonGraveyard140_Plus).toContain(enemyField[0].species.speciesId);
const moveset = enemyField[0].moveset.map(m => m.moveId);
expect(enemyMoveset[enemyField[0].species.speciesId]).toEqual(moveset);
});
it("should NOT be selectable if the player doesn't have a light type move", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
scene.getPlayerParty().forEach(p => (p.moveset = []));
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.phaseManager.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
await runSelectMysteryEncounterOption(game, 3);
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
});
});
describe("Option 4 - Leave the encounter", () => {
it("should have the correct properties", () => {
const option4 = CreepingFogEncounter.options[3];
expect(option4.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option4.dialogue).toBeDefined();
expect(option4.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option.4.label`,
buttonTooltip: `${namespace}:option.4.tooltip`,
selected: [
{
text: `${namespace}:option.4.selected`,
},
],
});
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.CREEPING_FOG, defaultParty);
await runMysteryEncounterToEnd(game, 4);
//Expect that the weather is set to fog
expect(scene.arena.weather?.weatherType).toBe(WeatherType.FOG);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
});

View File

@ -17,6 +17,7 @@ import { GameManagerHelper } from "./gameManagerHelper";
import { coerceArray, shiftCharCodes } from "#app/utils/common";
import type { RandomTrainerOverride } from "#app/overrides";
import type { BattleType } from "#enums/battle-type";
import type { TimeOfDay } from "#enums/time-of-day";
/**
* Helper to handle overrides in tests
@ -38,6 +39,17 @@ export class OverridesHelper extends GameManagerHelper {
return this;
}
/**
* Override the starting time of day
* @param timeOfDay - The time of day to be set
* @returns `this`
*/
public startingTimeOfDay(timeOfDay: TimeOfDay): this {
vi.spyOn(Overrides, "ARENA_TINT_OVERRIDE", "get").mockReturnValue(timeOfDay);
this.log(`Starting time of day set to ${timeOfDay}!`);
return this;
}
/**
* Override the starting wave index
* @param wave - The wave to set. Classic: `1`-`200`