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, "w": 16,
"h": 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() .unreplaceable()
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.attr(PostSummonFormChangeByWeatherAbAttr, AbilityId.FORECAST) .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) new Ability(AbilityId.STICKY_HOLD, 3)
.attr(BlockItemTheftAbAttr) .attr(BlockItemTheftAbAttr)
.bypassFaint() .bypassFaint()
@ -8306,7 +8306,7 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.SPDEF, 1.5) .conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), AllyStatMultiplierAbAttr, Stat.SPDEF, 1.5)
.attr(NoFusionAbilityAbAttr) .attr(NoFusionAbilityAbAttr)
.attr(PostSummonFormChangeByWeatherAbAttr, AbilityId.FLOWER_GIFT) .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() .uncopiable()
.unreplaceable() .unreplaceable()
.ignorable(), .ignorable(),
@ -9018,7 +9018,7 @@ export function initAbilities() {
.unreplaceable() .unreplaceable()
.ignorable(), .ignorable(),
new Ability(AbilityId.TERAFORM_ZERO, 9) 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 ]) .attr(ClearTerrainAbAttr, [ TerrainType.MISTY, TerrainType.ELECTRIC, TerrainType.GRASSY, TerrainType.PSYCHIC ])
.uncopiable() .uncopiable()
.unreplaceable() .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]}) new SpeciesEvolution(SpeciesId.SLIGGOO, 40, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DAWN, TimeOfDay.DAY]})
], ],
[SpeciesId.SLIGGOO]: [ [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]: [ [SpeciesId.BERGMITE]: [
new SpeciesEvolution(SpeciesId.HISUI_AVALUGG, 37, null, {key: EvoCondKey.TIME, time: [TimeOfDay.DUSK, TimeOfDay.NIGHT]}), 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) new SpeciesEvolution(SpeciesId.HISUI_ZOROARK, 30, null, null)
], ],
[SpeciesId.HISUI_SLIGGOO]: [ [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]: [ [SpeciesId.SPRIGATITO]: [
new SpeciesEvolution(SpeciesId.FLORAGATO, 16, null, null) 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); applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy); applyPreDefendAbAttrs("WonderSkinAbAttr", target, user, this, { value: false }, simulated, moveAccuracy);
if (moveAccuracy.value === -1) {
return moveAccuracy.value;
}
const isOhko = this.hasAttr("OneHitKOAccuracyAttr"); const isOhko = this.hasAttr("OneHitKOAccuracyAttr");
@ -774,7 +771,11 @@ export default abstract class Move implements Localizable {
globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); 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 * The 0.9 multiplier is PokeRogue-only implementation, Bulbapedia uses 3/5
* See Fog {@link https://bulbapedia.bulbagarden.net/wiki/Fog} * See Fog {@link https://bulbapedia.bulbagarden.net/wiki/Fog}
@ -9401,7 +9402,7 @@ export function initMoves() {
if (!weather) { if (!weather) {
return 1; 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()) { if (weatherTypes.includes(weather.weatherType) && !weather.isEffectSuppressed()) {
return 2; 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 { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-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 { 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"; import { getBiomeName } from "#app/data/balance/biomes";
export const EXTREME_ENCOUNTER_BIOMES = [ export const EXTREME_ENCOUNTER_BIOMES = [
@ -192,11 +193,11 @@ export const mysteryEncountersByBiome = new Map<BiomeId, MysteryEncounterType[]>
[BiomeId.TOWN, []], [BiomeId.TOWN, []],
[BiomeId.PLAINS, [MysteryEncounterType.SLUMBERING_SNORLAX]], [BiomeId.PLAINS, [MysteryEncounterType.SLUMBERING_SNORLAX]],
[BiomeId.GRASS, [MysteryEncounterType.SLUMBERING_SNORLAX, MysteryEncounterType.ABSOLUTE_AVARICE]], [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.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.SEA, [MysteryEncounterType.LOST_AT_SEA]],
[BiomeId.SWAMP, [MysteryEncounterType.SAFARI_ZONE]], [BiomeId.SWAMP, [MysteryEncounterType.SAFARI_ZONE, MysteryEncounterType.CREEPING_FOG]],
[BiomeId.BEACH, []], [BiomeId.BEACH, []],
[BiomeId.LAKE, []], [BiomeId.LAKE, []],
[BiomeId.SEABED, []], [BiomeId.SEABED, []],
@ -208,7 +209,7 @@ export const mysteryEncountersByBiome = new Map<BiomeId, MysteryEncounterType[]>
[BiomeId.MEADOW, []], [BiomeId.MEADOW, []],
[BiomeId.POWER_PLANT, []], [BiomeId.POWER_PLANT, []],
[BiomeId.VOLCANO, [MysteryEncounterType.FIERY_FALLOUT, MysteryEncounterType.DANCING_LESSONS]], [BiomeId.VOLCANO, [MysteryEncounterType.FIERY_FALLOUT, MysteryEncounterType.DANCING_LESSONS]],
[BiomeId.GRAVEYARD, []], [BiomeId.GRAVEYARD, [MysteryEncounterType.CREEPING_FOG]],
[BiomeId.DOJO, []], [BiomeId.DOJO, []],
[BiomeId.FACTORY, []], [BiomeId.FACTORY, []],
[BiomeId.RUINS, []], [BiomeId.RUINS, []],
@ -257,6 +258,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter; allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter;
allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter; allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter;
allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter; allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter;
allMysteryEncounters[MysteryEncounterType.CREEPING_FOG] = CreepingFogEncounter;
// Add extreme encounters to biome map // Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => { extremeBiomeEncounters.forEach(encounter => {

View File

@ -108,6 +108,16 @@ export const EXTORTION_MOVES = [
MoveId.STRING_SHOT, 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 * Abilities that (loosely) can be used to trap/rob someone
*/ */
@ -135,3 +145,18 @@ export const FIRE_RESISTANT_ABILITIES = [
AbilityId.STEAM_ENGINE, AbilityId.STEAM_ENGINE,
AbilityId.PRIMORDIAL_SEA, 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, "", "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, "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, "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, "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 ]), 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 ]), 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, "sunny", "", new SpeciesFormChangeActiveTrigger(), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "rainy", "", new SpeciesFormChangeActiveTrigger(), true), new SpeciesFormChange(SpeciesId.CASTFORM, "rainy", "", new SpeciesFormChangeActiveTrigger(), true),
new SpeciesFormChange(SpeciesId.CASTFORM, "snowy", "", new SpeciesFormChangeActiveTrigger(), true) new SpeciesFormChange(SpeciesId.CASTFORM, "snowy", "", new SpeciesFormChangeActiveTrigger(), true)
@ -300,7 +300,7 @@ export const pokemonFormChanges: PokemonFormChanges = {
], ],
[SpeciesId.CHERRIM]: [ [SpeciesId.CHERRIM]: [
new SpeciesFormChange(SpeciesId.CHERRIM, "overcast", "sunshine", new SpeciesFormChangeWeatherTrigger(AbilityId.FLOWER_GIFT, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]), true), 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) new SpeciesFormChange(SpeciesId.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeActiveTrigger(), true)
], ],
[SpeciesId.LOPUNNY]: [ [SpeciesId.LOPUNNY]: [

View File

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

View File

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

View File

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

View File

@ -327,8 +327,15 @@ export class Arena {
if ( if (
this.weather?.isImmutable() && 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( globalScene.phaseManager.unshiftNew(
"CommonAnimPhase", "CommonAnimPhase",
undefined, undefined,
@ -336,6 +343,7 @@ export class Arena {
CommonAnim.SUNNY + (oldWeatherType - 1), CommonAnim.SUNNY + (oldWeatherType - 1),
true, true,
); );
}
globalScene.phaseManager.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!); globalScene.phaseManager.queueMessage(getLegendaryWeatherContinuesMessage(oldWeatherType)!);
return false; return false;
} }

View File

@ -236,6 +236,7 @@ export class LoadingScene extends SceneBase {
this.loadAtlas("pb", ""); this.loadAtlas("pb", "");
this.loadAtlas("items", ""); this.loadAtlas("items", "");
this.loadImage("heavy_fog", "");
this.loadAtlas("types", ""); 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> // 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 { getDescription(): string {
if (this.amount === -1) {
return i18next.t("modifierType:ModifierType.MICLE_BERRY.description");
}
return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", { return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", {
accuracyAmount: this.amount, accuracyAmount: this.amount,
}); });
@ -2141,6 +2144,8 @@ const modifierTypeInitObj = Object.freeze({
GRIP_CLAW: () => GRIP_CLAW: () =>
new ContactHeldItemTransferChanceModifierType("modifierType:ModifierType.GRIP_CLAW", "grip_claw", 10), new ContactHeldItemTransferChanceModifierType("modifierType:ModifierType.GRIP_CLAW", "grip_claw", 10),
WIDE_LENS: () => new PokemonMoveAccuracyBoosterModifierType("modifierType:ModifierType.WIDE_LENS", "wide_lens", 5), 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"), MULTI_LENS: () => new PokemonMultiHitModifierType("modifierType:ModifierType.MULTI_LENS", "zoom_lens"),

View File

@ -2739,8 +2739,11 @@ export class PokemonMoveAccuracyBoosterModifier extends PokemonHeldItemModifier
* @returns always `true` * @returns always `true`
*/ */
override apply(_pokemon: Pokemon, moveAccuracy: NumberHolder): boolean { 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; return true;
} }

View File

@ -299,6 +299,7 @@ export async function initI18n(): Promise<void> {
"mysteryEncounters/uncommonBreed", "mysteryEncounters/uncommonBreed",
"mysteryEncounters/globalTradeSystem", "mysteryEncounters/globalTradeSystem",
"mysteryEncounters/theExpertPokemonBreeder", "mysteryEncounters/theExpertPokemonBreeder",
"mysteryEncounters/creepingFog",
"mysteryEncounterMessages", "mysteryEncounterMessages",
], ],
detection: { 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 { coerceArray, shiftCharCodes } from "#app/utils/common";
import type { RandomTrainerOverride } from "#app/overrides"; import type { RandomTrainerOverride } from "#app/overrides";
import type { BattleType } from "#enums/battle-type"; import type { BattleType } from "#enums/battle-type";
import type { TimeOfDay } from "#enums/time-of-day";
/** /**
* Helper to handle overrides in tests * Helper to handle overrides in tests
@ -38,6 +39,17 @@ export class OverridesHelper extends GameManagerHelper {
return this; 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 * Override the starting wave index
* @param wave - The wave to set. Classic: `1`-`200` * @param wave - The wave to set. Classic: `1`-`200`