mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-20 16:42:45 +02:00
[Item] Add Weather and Terrain Extender Item (#4799)
* [Item] Add Weather and Terrain Extender Item * Add Documentation * Clean Up Unit Tests * Add Weight Function * Include Suggestions
This commit is contained in:
parent
db850c79cd
commit
6316218bd3
@ -308,7 +308,7 @@ export class ClearWeatherAbAttr extends AbAttr {
|
||||
|
||||
public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, true);
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -334,7 +334,7 @@ export class ClearTerrainAbAttr extends AbAttr {
|
||||
|
||||
public override apply(pokemon: Pokemon, passive: boolean, simulated:boolean, cancelled: Utils.BooleanHolder, args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetTerrain(TerrainType.NONE, true, true);
|
||||
globalScene.arena.trySetTerrain(TerrainType.NONE, true, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -954,7 +954,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
|
||||
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetTerrain(this.terrainType, true);
|
||||
globalScene.arena.trySetTerrain(this.terrainType, false, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1126,7 +1126,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
|
||||
|
||||
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetWeather(this.weatherType, true);
|
||||
globalScene.arena.trySetWeather(this.weatherType, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2483,7 +2483,7 @@ export class PostSummonWeatherChangeAbAttr extends PostSummonAbAttr {
|
||||
|
||||
override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetWeather(this.weatherType, true);
|
||||
globalScene.arena.trySetWeather(this.weatherType, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2503,7 +2503,7 @@ export class PostSummonTerrainChangeAbAttr extends PostSummonAbAttr {
|
||||
|
||||
override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetTerrain(this.terrainType, true);
|
||||
globalScene.arena.trySetTerrain(this.terrainType, false, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2886,7 +2886,7 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
|
||||
}
|
||||
|
||||
if (turnOffWeather) {
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, false);
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2986,7 +2986,7 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr {
|
||||
*/
|
||||
override applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, false);
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4137,7 +4137,7 @@ export class PostBiomeChangeWeatherChangeAbAttr extends PostBiomeChangeAbAttr {
|
||||
|
||||
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetWeather(this.weatherType, true);
|
||||
globalScene.arena.trySetWeather(this.weatherType, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4157,7 +4157,7 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr {
|
||||
|
||||
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): void {
|
||||
if (!simulated) {
|
||||
globalScene.arena.trySetTerrain(this.terrainType, true);
|
||||
globalScene.arena.trySetTerrain(this.terrainType, false, pokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2851,7 +2851,7 @@ export class WeatherChangeAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
return globalScene.arena.trySetWeather(this.weatherType, true);
|
||||
return globalScene.arena.trySetWeather(this.weatherType, user);
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
@ -2870,7 +2870,7 @@ export class ClearWeatherAttr extends MoveEffectAttr {
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (globalScene.arena.weather?.weatherType === this.weatherType) {
|
||||
return globalScene.arena.trySetWeather(WeatherType.NONE, true);
|
||||
return globalScene.arena.trySetWeather(WeatherType.NONE, user);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -2887,7 +2887,7 @@ export class TerrainChangeAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
return globalScene.arena.trySetTerrain(this.terrainType, true, true);
|
||||
return globalScene.arena.trySetTerrain(this.terrainType, true, user);
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
@ -2906,7 +2906,7 @@ export class ClearTerrainAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
return globalScene.arena.trySetTerrain(TerrainType.NONE, true, true);
|
||||
return globalScene.arena.trySetTerrain(TerrainType.NONE, true, user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6454,7 +6454,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
|
||||
export class ChillyReceptionAttr extends ForceSwitchOutAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
globalScene.arena.trySetWeather(WeatherType.SNOW, true);
|
||||
globalScene.arena.trySetWeather(WeatherType.SNOW, user);
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,8 @@ export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.w
|
||||
// Load animations/sfx for Volcarona moves
|
||||
loadCustomMovesForEncounter([Moves.FIRE_SPIN, Moves.QUIVER_DANCE]);
|
||||
|
||||
globalScene.arena.trySetWeather(WeatherType.SUNNY, true);
|
||||
const pokemon = globalScene.getEnemyPokemon();
|
||||
globalScene.arena.trySetWeather(WeatherType.SUNNY, pokemon);
|
||||
|
||||
encounter.setDialogueToken("volcaronaName", getPokemonSpecies(Species.VOLCARONA).getName());
|
||||
|
||||
|
@ -42,6 +42,7 @@ import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrig
|
||||
import { CommonAnimPhase } from "#app/phases/common-anim-phase";
|
||||
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { FieldEffectModifier } from "#app/modifier/modifier";
|
||||
|
||||
export class Arena {
|
||||
public biomeType: Biome;
|
||||
@ -311,10 +312,10 @@ export class Arena {
|
||||
/**
|
||||
* Attempts to set a new weather to the battle
|
||||
* @param weather {@linkcode WeatherType} new {@linkcode WeatherType} to set
|
||||
* @param hasPokemonSource boolean if the new weather is from a pokemon
|
||||
* @param user {@linkcode Pokemon} that caused the weather effect
|
||||
* @returns true if new weather set, false if no weather provided or attempting to set the same weather as currently in use
|
||||
*/
|
||||
trySetWeather(weather: WeatherType, hasPokemonSource: boolean): boolean {
|
||||
trySetWeather(weather: WeatherType, user?: Pokemon): boolean {
|
||||
if (Overrides.WEATHER_OVERRIDE) {
|
||||
return this.trySetWeatherOverride(Overrides.WEATHER_OVERRIDE);
|
||||
}
|
||||
@ -336,7 +337,14 @@ export class Arena {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.weather = weather ? new Weather(weather, hasPokemonSource ? 5 : 0) : null;
|
||||
const weatherDuration = new Utils.NumberHolder(0);
|
||||
|
||||
if (!Utils.isNullOrUndefined(user)) {
|
||||
weatherDuration.value = 5;
|
||||
globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, weatherDuration);
|
||||
}
|
||||
|
||||
this.weather = weather ? new Weather(weather, weatherDuration.value) : null;
|
||||
this.eventTarget.dispatchEvent(
|
||||
new WeatherChangedEvent(oldWeatherType, this.weather?.weatherType!, this.weather?.turnsLeft!),
|
||||
); // TODO: is this bang correct?
|
||||
@ -398,14 +406,29 @@ export class Arena {
|
||||
return !(this.terrain?.terrainType === (terrain || undefined));
|
||||
}
|
||||
|
||||
trySetTerrain(terrain: TerrainType, hasPokemonSource: boolean, ignoreAnim = false): boolean {
|
||||
/**
|
||||
* Attempts to set a new terrain effect to the battle
|
||||
* @param terrain {@linkcode TerrainType} new {@linkcode TerrainType} to set
|
||||
* @param ignoreAnim boolean if the terrain animation should be ignored
|
||||
* @param user {@linkcode Pokemon} that caused the terrain effect
|
||||
* @returns true if new terrain set, false if no terrain provided or attempting to set the same terrain as currently in use
|
||||
*/
|
||||
trySetTerrain(terrain: TerrainType, ignoreAnim = false, user?: Pokemon): boolean {
|
||||
if (!this.canSetTerrain(terrain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldTerrainType = this.terrain?.terrainType || TerrainType.NONE;
|
||||
|
||||
this.terrain = terrain ? new Terrain(terrain, hasPokemonSource ? 5 : 0) : null;
|
||||
const terrainDuration = new Utils.NumberHolder(0);
|
||||
|
||||
if (!Utils.isNullOrUndefined(user)) {
|
||||
terrainDuration.value = 5;
|
||||
globalScene.applyModifier(FieldEffectModifier, user.isPlayer(), user, terrainDuration);
|
||||
}
|
||||
|
||||
this.terrain = terrain ? new Terrain(terrain, terrainDuration.value) : null;
|
||||
|
||||
this.eventTarget.dispatchEvent(
|
||||
new TerrainChangedEvent(oldTerrainType, this.terrain?.terrainType!, this.terrain?.turnsLeft!),
|
||||
); // TODO: are those bangs correct?
|
||||
@ -802,9 +825,9 @@ export class Arena {
|
||||
resetArenaEffects(): void {
|
||||
// Don't reset weather if a Biome's permanent weather is active
|
||||
if (this.weather?.turnsLeft !== 0) {
|
||||
this.trySetWeather(WeatherType.NONE, false);
|
||||
this.trySetWeather(WeatherType.NONE);
|
||||
}
|
||||
this.trySetTerrain(TerrainType.NONE, false, true);
|
||||
this.trySetTerrain(TerrainType.NONE, true);
|
||||
this.resetPlayerFaintCount();
|
||||
this.removeAllTags();
|
||||
}
|
||||
|
@ -97,6 +97,7 @@ import {
|
||||
type PersistentModifier,
|
||||
TempExtraModifierModifier,
|
||||
CriticalCatchChanceBoosterModifier,
|
||||
FieldEffectModifier,
|
||||
} from "#app/modifier/modifier";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import Overrides from "#app/overrides";
|
||||
@ -1998,6 +1999,13 @@ export const modifierTypes = {
|
||||
return new PokemonNatureChangeModifierType(randSeedInt(getEnumValues(Nature).length) as Nature);
|
||||
}),
|
||||
|
||||
MYSTICAL_ROCK: () =>
|
||||
new ModifierType(
|
||||
"modifierType:ModifierType.MYSTICAL_ROCK",
|
||||
"mystical_rock",
|
||||
(type, args) => new FieldEffectModifier(type, (args[0] as Pokemon).id),
|
||||
),
|
||||
|
||||
TERA_SHARD: () =>
|
||||
new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) {
|
||||
@ -2810,6 +2818,47 @@ const modifierPool: ModifierPool = {
|
||||
},
|
||||
10,
|
||||
),
|
||||
new WeightedModifierType(
|
||||
modifierTypes.MYSTICAL_ROCK,
|
||||
(party: Pokemon[]) => {
|
||||
return party.some(p => {
|
||||
const moveset = p.getMoveset(true).map(m => m.moveId);
|
||||
|
||||
const hasAbility = [
|
||||
Abilities.DRIZZLE,
|
||||
Abilities.ORICHALCUM_PULSE,
|
||||
Abilities.DRIZZLE,
|
||||
Abilities.SAND_STREAM,
|
||||
Abilities.SAND_SPIT,
|
||||
Abilities.SNOW_WARNING,
|
||||
Abilities.ELECTRIC_SURGE,
|
||||
Abilities.HADRON_ENGINE,
|
||||
Abilities.PSYCHIC_SURGE,
|
||||
Abilities.GRASSY_SURGE,
|
||||
Abilities.SEED_SOWER,
|
||||
Abilities.MISTY_SURGE,
|
||||
].some(a => p.hasAbility(a, false, true));
|
||||
|
||||
const hasMoves = [
|
||||
Moves.SUNNY_DAY,
|
||||
Moves.RAIN_DANCE,
|
||||
Moves.SANDSTORM,
|
||||
Moves.SNOWSCAPE,
|
||||
Moves.HAIL,
|
||||
Moves.CHILLY_RECEPTION,
|
||||
Moves.ELECTRIC_TERRAIN,
|
||||
Moves.PSYCHIC_TERRAIN,
|
||||
Moves.GRASSY_TERRAIN,
|
||||
Moves.MISTY_TERRAIN,
|
||||
].some(m => moveset.includes(m));
|
||||
|
||||
return hasAbility || hasMoves;
|
||||
})
|
||||
? 10
|
||||
: 0;
|
||||
},
|
||||
10,
|
||||
),
|
||||
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
|
||||
new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)),
|
||||
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9),
|
||||
|
@ -2014,6 +2014,38 @@ export class ResetNegativeStatStageModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items, namely Mystical Rock, that extend the
|
||||
* duration of weather and terrain effects.
|
||||
* @extends PokemonHeldItemModifier
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class FieldEffectModifier extends PokemonHeldItemModifier {
|
||||
/**
|
||||
* Provides two more turns per stack to any weather or terrain effect caused
|
||||
* by the holder.
|
||||
* @param pokemon {@linkcode Pokemon} that holds the held item
|
||||
* @param fieldDuration {@linkcode NumberHolder} that stores the current field effect duration
|
||||
* @returns `true` if the field effect extension was applied successfully
|
||||
*/
|
||||
override apply(_pokemon: Pokemon, fieldDuration: NumberHolder): boolean {
|
||||
fieldDuration.value += 2 * this.stackCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
override matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof FieldEffectModifier;
|
||||
}
|
||||
|
||||
override clone(): FieldEffectModifier {
|
||||
return new FieldEffectModifier(this.type, this.pokemonId, this.stackCount);
|
||||
}
|
||||
|
||||
override getMaxHeldItemCount(_pokemon?: Pokemon): number {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ConsumablePokemonModifier extends ConsumableModifier {
|
||||
public pokemonId: number;
|
||||
|
||||
|
@ -684,7 +684,7 @@ export class EncounterPhase extends BattlePhase {
|
||||
*/
|
||||
trySetWeatherIfNewBiome(): void {
|
||||
if (!this.loaded) {
|
||||
globalScene.arena.trySetWeather(getRandomWeatherType(globalScene.arena), false);
|
||||
globalScene.arena.trySetWeather(getRandomWeatherType(globalScene.arena));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,6 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
||||
* Set biome weather.
|
||||
*/
|
||||
trySetWeatherIfNewBiome(): void {
|
||||
globalScene.arena.trySetWeather(getRandomWeatherType(globalScene.arena), false);
|
||||
globalScene.arena.trySetWeather(getRandomWeatherType(globalScene.arena));
|
||||
}
|
||||
}
|
||||
|
@ -68,12 +68,12 @@ export class TurnEndPhase extends FieldPhase {
|
||||
globalScene.arena.lapseTags();
|
||||
|
||||
if (globalScene.arena.weather && !globalScene.arena.weather.lapse()) {
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, false);
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE);
|
||||
globalScene.arena.triggerWeatherBasedFormChangesToNormal();
|
||||
}
|
||||
|
||||
if (globalScene.arena.terrain && !globalScene.arena.terrain.lapse()) {
|
||||
globalScene.arena.trySetTerrain(TerrainType.NONE, false);
|
||||
globalScene.arena.trySetTerrain(TerrainType.NONE);
|
||||
}
|
||||
|
||||
this.end();
|
||||
|
@ -181,7 +181,7 @@ describe("Abilities - Forecast", () => {
|
||||
|
||||
expect(castform.formIndex).toBe(SNOWY_FORM);
|
||||
|
||||
game.scene.arena.trySetWeather(WeatherType.FOG, false);
|
||||
game.scene.arena.trySetWeather(WeatherType.FOG);
|
||||
game.move.select(Moves.SPLASH);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
await game.phaseInterceptor.to("TurnStartPhase");
|
||||
|
60
test/items/mystical_rock.test.ts
Normal file
60
test/items/mystical_rock.test.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phase from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
describe("Items - Mystical Rock", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phase.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
|
||||
game.override
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.moveset([Moves.SUNNY_DAY, Moves.GRASSY_TERRAIN])
|
||||
.startingHeldItems([{ name: "MYSTICAL_ROCK", count: 2 }])
|
||||
.battleType("single");
|
||||
});
|
||||
|
||||
it("should increase weather duration by +2 turns per stack", async () => {
|
||||
await game.classicMode.startBattle([Species.GASTLY]);
|
||||
|
||||
game.move.select(Moves.SUNNY_DAY);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
|
||||
const weather = globalScene.arena.weather;
|
||||
|
||||
expect(weather).toBeDefined();
|
||||
expect(weather!.turnsLeft).to.equal(9);
|
||||
});
|
||||
|
||||
it("should increase terrain duration by +2 turns per stack", async () => {
|
||||
await game.classicMode.startBattle([Species.GASTLY]);
|
||||
|
||||
game.move.select(Moves.GRASSY_TERRAIN);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
|
||||
const terrain = globalScene.arena.terrain;
|
||||
|
||||
expect(terrain).toBeDefined();
|
||||
expect(terrain!.turnsLeft).to.equal(9);
|
||||
});
|
||||
});
|
@ -120,7 +120,7 @@ describe("Moves - Dive", () => {
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
await game.phaseInterceptor.to("TurnStartPhase", false);
|
||||
game.scene.arena.trySetWeather(WeatherType.HARSH_SUN, false);
|
||||
game.scene.arena.trySetWeather(WeatherType.HARSH_SUN);
|
||||
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
expect(playerPokemon.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||
|
Loading…
Reference in New Issue
Block a user