mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-06 00:12:16 +02:00
Implementation of natural gift
This commit is contained in:
parent
356cffaf97
commit
677f15c64b
@ -1 +1 @@
|
||||
Subproject commit acad8499a4ca488a9871902de140f635235f309a
|
||||
Subproject commit e07ab625f2080afe36b61fad291b0ec5eff4000c
|
@ -4644,14 +4644,17 @@ export class VariableMoveTypeAttr extends MoveAttr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute used to control the Power and Type of Natural Gift
|
||||
* Attribute used to control the Power and Type of Natural Gift.
|
||||
* Takes over the Power calculation of Natural Gift while {@linkcode NaturalGiftTypeAttr}
|
||||
* takes care of the Move Type.
|
||||
* @extends VariablePowerAttr
|
||||
*/
|
||||
export class NaturalGiftPowerAttr extends VariablePowerAttr {
|
||||
private randomBerry;
|
||||
|
||||
/**
|
||||
* Overrides the power of Natural Gift depending on the consumed berry
|
||||
* Overrides the power of Natural Gift depending on the consumed berry.
|
||||
* This also removes the berry.
|
||||
* @param user - The Pokémon using the move.
|
||||
* @param target - The target Pokémon.
|
||||
* @param move - The move being used.
|
||||
@ -4659,20 +4662,56 @@ export class NaturalGiftPowerAttr extends VariablePowerAttr {
|
||||
* @returns A boolean indicating whether the move was successfully applied.
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
console.log("GHNote Natural Gift POWER Attr called");
|
||||
const power = args[0];
|
||||
if (!(power instanceof Utils.NumberHolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.randomBerry = NaturalGiftBerrySelector.getRandomBerry(user);
|
||||
|
||||
if (this.randomBerry) {
|
||||
power.value = this.randomBerry.getNaturalGiftPower();
|
||||
|
||||
/** Berries do not get eaten during specific weather conditions */
|
||||
const weather = globalScene.arena.weather;
|
||||
if (!weather?.isEffectSuppressed()) {
|
||||
if (weather?.weatherType === WeatherType.HEAVY_RAIN && this.randomBerry.getNaturalGiftType === Type.FIRE) {
|
||||
NaturalGiftBerrySelector.resetBerry();
|
||||
return true;
|
||||
} else if (weather?.weatherType === WeatherType.HARSH_SUN && this.randomBerry.getNaturalGiftType === Type.WATER) {
|
||||
NaturalGiftBerrySelector.resetBerry();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/** If user used {@linkcode Moves.POWDER} and Natural Gift turns fire type the berry is not confused*/
|
||||
if (user.getTag(BattlerTagType.POWDER) && this.randomBerry.getNaturalGiftType === Type.FIRE) {
|
||||
NaturalGiftBerrySelector.resetBerry();
|
||||
return true;
|
||||
}
|
||||
|
||||
user.loseHeldItem(this.randomBerry, user.isPlayer());
|
||||
globalScene.updateModifiers(user.isPlayer());
|
||||
NaturalGiftBerrySelector.resetBerry();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute used to control the type of Natural Gift
|
||||
* Takes over the Type calculation of Natural Gift while {@linkcode NaturalGiftPowerAttr}
|
||||
* takes care of the Move power.
|
||||
* @extends VariableMoveTypeAttr
|
||||
*/
|
||||
export class NaturalGiftTypeAttr extends VariableMoveTypeAttr {
|
||||
private randomBerry;
|
||||
|
||||
/**
|
||||
* Overrides the type of Natural Gift depending on the consumed berry
|
||||
* Overrides the type of Natural Gift depending on the consumed berry.
|
||||
* The item used for the move is not removed here but in {@linkcode NaturalGiftPowerAttr}
|
||||
* since it is called last.
|
||||
* @param user - The Pokémon using the move.
|
||||
* @param target - The target Pokémon.
|
||||
* @param move - The move being used.
|
||||
@ -4680,12 +4719,50 @@ export class NaturalGiftTypeAttr extends VariableMoveTypeAttr {
|
||||
* @returns A boolean indicating whether the move was successfully applied.
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
console.log("GHNote Natural Gift TYPE Attr called");
|
||||
const moveType = args[0];
|
||||
if (!(moveType instanceof Utils.NumberHolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.randomBerry = NaturalGiftBerrySelector.getRandomBerry(user);
|
||||
|
||||
if (this.randomBerry) {
|
||||
moveType.value = this.randomBerry.getNaturalGiftType();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class NaturalGiftBerrySelector {
|
||||
private static selectedBerry;
|
||||
|
||||
/**
|
||||
* select random berry from user
|
||||
* @param user Pokemon using Natural Gift
|
||||
* @returns A random berry to use in {@linkcode NaturalGiftPowerAttr} and {@linkcode NaturalGiftTypeAttr}
|
||||
*/
|
||||
public static getRandomBerry(user: Pokemon): BerryModifier {
|
||||
if (!this.selectedBerry) {
|
||||
const berries = globalScene.findModifiers(
|
||||
m => m instanceof BerryModifier && m.pokemonId === user.id,
|
||||
user.isPlayer()
|
||||
) as BerryModifier[];
|
||||
this.selectedBerry = berries.at(user.randSeedInt(berries.length)) ?? null;
|
||||
}
|
||||
return this.selectedBerry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the selected berry
|
||||
*/
|
||||
public static resetBerry() {
|
||||
this.selectedBerry = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attribute used for Tera Starstorm that changes the move type to Stellar
|
||||
* @extends VariableMoveTypeAttr
|
||||
@ -9362,6 +9439,10 @@ export function initMoves() {
|
||||
new AttackMove(Moves.NATURAL_GIFT, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 4)
|
||||
.attr(NaturalGiftPowerAttr)
|
||||
.attr(NaturalGiftTypeAttr)
|
||||
.condition((user) => {
|
||||
const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
|
||||
return userBerries.length > 0;
|
||||
})
|
||||
.makesContact(false),
|
||||
//.unimplemented(),
|
||||
new AttackMove(Moves.FEINT, Type.NORMAL, MoveCategory.PHYSICAL, 30, 100, 10, -1, 2, 4)
|
||||
|
@ -16,7 +16,6 @@ import { StatusEffect } from "#enums/status-effect";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { VariantTier } from "#enums/variant-tier";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
|
||||
/**
|
||||
* Overrides that are using when testing different in game situations
|
||||
@ -33,13 +32,7 @@ import { BerryType } from "#enums/berry-type";
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const overrides = {
|
||||
OPP_LEVEL_OVERRIDE: 100,
|
||||
OPP_MOVESET_OVERRIDE: [ Moves.SPLASH ],
|
||||
STARTING_LEVEL_OVERRIDE: 1,
|
||||
MOVESET_OVERRIDE: [ Moves.NATURAL_GIFT ],
|
||||
STARTING_HELD_ITEMS_OVERRIDE: [{ name: "BERRY", type: BerryType.SITRUS }]
|
||||
} satisfies Partial<InstanceType<typeof DefaultOverrides>>;
|
||||
const overrides = {} satisfies Partial<InstanceType<typeof DefaultOverrides>>;
|
||||
|
||||
/**
|
||||
* If you need to add Overrides values for local testing do that inside {@linkcode overrides}
|
||||
|
162
src/test/moves/natural_gift.test.ts
Normal file
162
src/test/moves/natural_gift.test.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Moves - Natural Gift", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
/**
|
||||
* Count the number of held items a Pokemon has, accounting for stacks of multiple items.
|
||||
*/
|
||||
function getHeldItemCount(pokemon: Pokemon): number {
|
||||
const stackCounts = pokemon.getHeldItems().map(m => m.getStackCount());
|
||||
if (stackCounts.length) {
|
||||
return stackCounts.reduce((a, b) => a + b);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.disableCrits()
|
||||
.moveset(Moves.NATURAL_GIFT)
|
||||
.ability(Abilities.BALL_FETCH)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemySpecies(Species.RATTATA)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.startingLevel(10)
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
/**
|
||||
* There is currently no way to test interaction with heavy rain(Weather), harsh sunlight(Weather) and Powder(Move)
|
||||
* since there are currently no berries that change the type to Fire/Water
|
||||
*/
|
||||
it("should deal double damage to Fighting type if Sitrus Berry is consumed", async () => {
|
||||
game.override
|
||||
.startingHeldItems([
|
||||
{ name: "BERRY", type: BerryType.SITRUS, count: 3 },
|
||||
])
|
||||
.enemySpecies(Species.MACHAMP);
|
||||
|
||||
await game.classicMode.startBattle([ Species.BULBASAUR ]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.NATURAL_GIFT);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(getHeldItemCount(player)).toBe(2);
|
||||
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
|
||||
});
|
||||
|
||||
it("should deal half damage to Steel type if Sitrus Berry is consumed", async () => {
|
||||
game.override
|
||||
.startingHeldItems([
|
||||
{ name: "BERRY", type: BerryType.SITRUS, count: 3 },
|
||||
])
|
||||
.enemySpecies(Species.KLINK);
|
||||
|
||||
await game.classicMode.startBattle([ Species.BULBASAUR ]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.NATURAL_GIFT);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(getHeldItemCount(player)).toBe(2);
|
||||
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.5);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ganlon Berry should turn Natural Gift to Ice type (1/2x dmg to Water type).
|
||||
* With Electrify Natural Gift should deal 2x dmg to Water type
|
||||
*/
|
||||
it("should not override Electrify (deal double damage against Water pkm with Ganlon Berry)", async () => {
|
||||
game.override
|
||||
.startingHeldItems([
|
||||
{ name: "BERRY", type: BerryType.GANLON, count: 3 },
|
||||
])
|
||||
.enemyMoveset(Moves.ELECTRIFY)
|
||||
.enemySpecies(Species.MAGIKARP);
|
||||
|
||||
await game.classicMode.startBattle([ Species.BULBASAUR ]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.NATURAL_GIFT);
|
||||
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(getHeldItemCount(player)).toBe(2);
|
||||
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
|
||||
});
|
||||
|
||||
it("should fail if no berries are held", async () => {
|
||||
game.override
|
||||
.startingHeldItems([]);
|
||||
|
||||
await game.classicMode.startBattle([ Species.BULBASAUR ]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.NATURAL_GIFT);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(player.getLastXMoves(1)[0].result).toBe(MoveResult.FAIL);
|
||||
});
|
||||
|
||||
it("should not be affected by Normalize", async () => {
|
||||
game.override
|
||||
.startingHeldItems([
|
||||
{ name: "BERRY", type: BerryType.SITRUS, count: 3 },
|
||||
])
|
||||
.ability(Abilities.NORMALIZE)
|
||||
.enemySpecies(Species.MACHAMP);
|
||||
|
||||
await game.classicMode.startBattle([ Species.BULBASAUR ]);
|
||||
const player = game.scene.getPlayerPokemon()!;
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.NATURAL_GIFT);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(getHeldItemCount(player)).toBe(2);
|
||||
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user