[Move] Implement Freeze Dry type-changed interactions (#4840)
* Full implementation of freeze-dry including edge cases such as Normalize and Electrify plus tests
* Update comments
* renamed WaterSuperEffectTypeMultiplierAttr to FreezeDryAttr
* Added test case for freeze dry during inverse battles
* cleaned up code making it more general
* Added some more documentation
* implementing reviewed changes
* used getMoveType() instead of move.type
* added additional test cases to freeze dry
* Revert "used getMoveType() instead of move.type"
This reverts commit 03445dfab4
.
* added reviewed changes without changing public/locales
---------
Co-authored-by: ga27lok <geeil.han@tum.de>
This commit is contained in:
parent
162eea500d
commit
0c521bbe08
|
@ -4971,16 +4971,42 @@ export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WaterSuperEffectTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr {
|
/**
|
||||||
|
* This class forces Freeze-Dry to be super effective against Water Type.
|
||||||
|
* It considers if target is Mono or Dual Type and calculates the new Multiplier accordingly.
|
||||||
|
* @see {@linkcode apply}
|
||||||
|
*/
|
||||||
|
export class FreezeDryAttr extends VariableMoveTypeMultiplierAttr {
|
||||||
|
/**
|
||||||
|
* If the target is Mono Type (Water only) then a 2x Multiplier is always forced.
|
||||||
|
* If target is Dual Type (containing Water) then only a 2x Multiplier is forced for the Water Type.
|
||||||
|
*
|
||||||
|
* Additionally Freeze-Dry's effectiveness against water is always forced during {@linkcode InverseBattleChallenge}.
|
||||||
|
* The multiplier is recalculated for the non-Water Type in case of Dual Type targets containing Water Type.
|
||||||
|
*
|
||||||
|
* @param user The {@linkcode Pokemon} applying the move
|
||||||
|
* @param target The {@linkcode Pokemon} targeted by the move
|
||||||
|
* @param move The move used by the user
|
||||||
|
* @param args `[0]` a {@linkcode Utils.NumberHolder | NumberHolder} containing a type effectiveness multiplier
|
||||||
|
* @returns `true` if super effectiveness on water type is forced; `false` otherwise
|
||||||
|
*/
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
const multiplier = args[0] as Utils.NumberHolder;
|
const multiplier = args[0] as Utils.NumberHolder;
|
||||||
if (target.isOfType(Type.WATER)) {
|
if (target.isOfType(Type.WATER) && multiplier.value !== 0) {
|
||||||
const effectivenessAgainstWater = new Utils.NumberHolder(getTypeDamageMultiplier(move.type, Type.WATER));
|
const multipleTypes = (target.getTypes().length > 1);
|
||||||
applyChallenges(user.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, effectivenessAgainstWater);
|
|
||||||
if (effectivenessAgainstWater.value !== 0) {
|
if (multipleTypes) {
|
||||||
multiplier.value *= 2 / effectivenessAgainstWater.value;
|
const nonWaterType = target.getTypes().filter(type => type !== Type.WATER)[0];
|
||||||
|
const effectivenessAgainstTarget = new Utils.NumberHolder(getTypeDamageMultiplier(user.getMoveType(move), nonWaterType));
|
||||||
|
|
||||||
|
applyChallenges(user.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, effectivenessAgainstTarget);
|
||||||
|
|
||||||
|
multiplier.value = effectivenessAgainstTarget.value * 2;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
multiplier.value = 2;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -9422,7 +9448,7 @@ export function initMoves() {
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||||
new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6)
|
new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6)
|
||||||
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
||||||
.attr(WaterSuperEffectTypeMultiplierAttr)
|
.attr(FreezeDryAttr)
|
||||||
.edgeCase(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell.
|
.edgeCase(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell.
|
||||||
new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
|
new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
|
||||||
.soundBased()
|
.soundBased()
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { BattlerIndex } from "#app/battle";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Moves } from "#app/enums/moves";
|
import { Moves } from "#app/enums/moves";
|
||||||
import { Species } from "#app/enums/species";
|
import { Species } from "#app/enums/species";
|
||||||
|
import { Challenges } from "#enums/challenges";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
@ -97,8 +98,7 @@ describe("Moves - Freeze-Dry", () => {
|
||||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
});
|
});
|
||||||
|
|
||||||
// enable if this is ever fixed (lol)
|
it("should deal 2x damage to water type under Normalize", async () => {
|
||||||
it.todo("should deal 2x damage to water types under Normalize", async () => {
|
|
||||||
game.override.ability(Abilities.NORMALIZE);
|
game.override.ability(Abilities.NORMALIZE);
|
||||||
await game.classicMode.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
@ -112,8 +112,39 @@ describe("Moves - Freeze-Dry", () => {
|
||||||
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// enable once Electrify is implemented (and the interaction is fixed, as above)
|
it("should deal 0.25x damage to rock/steel type under Normalize", async () => {
|
||||||
it.todo("should deal 2x damage to water types under Electrify", async () => {
|
game.override
|
||||||
|
.ability(Abilities.NORMALIZE)
|
||||||
|
.enemySpecies(Species.SHIELDON);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 0x damage to water/ghost type under Normalize", async () => {
|
||||||
|
game.override
|
||||||
|
.ability(Abilities.NORMALIZE)
|
||||||
|
.enemySpecies(Species.JELLICENT);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 2x damage to water type under Electrify", async () => {
|
||||||
game.override.enemyMoveset([ Moves.ELECTRIFY ]);
|
game.override.enemyMoveset([ Moves.ELECTRIFY ]);
|
||||||
await game.classicMode.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
@ -126,4 +157,128 @@ describe("Moves - Freeze-Dry", () => {
|
||||||
|
|
||||||
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should deal 4x damage to water/flying type under Electrify", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyMoveset([ Moves.ELECTRIFY ])
|
||||||
|
.enemySpecies(Species.GYARADOS);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 0x damage to water/ground type under Electrify", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyMoveset([ Moves.ELECTRIFY ])
|
||||||
|
.enemySpecies(Species.BARBOACH);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 0.25x damage to Grass/Dragon type under Electrify", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyMoveset([ Moves.ELECTRIFY ])
|
||||||
|
.enemySpecies(Species.FLAPPLE);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.25);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 2x damage to Water type during inverse battle", async () => {
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.FREEZE_DRY ])
|
||||||
|
.enemySpecies(Species.MAGIKARP);
|
||||||
|
game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1);
|
||||||
|
|
||||||
|
|
||||||
|
await game.challengeMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 2x damage to Water type during inverse battle under Normalize", async () => {
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.FREEZE_DRY ])
|
||||||
|
.ability(Abilities.NORMALIZE)
|
||||||
|
.enemySpecies(Species.MAGIKARP);
|
||||||
|
game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1);
|
||||||
|
|
||||||
|
await game.challengeMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 2x damage to Water type during inverse battle under Electrify", async () => {
|
||||||
|
game.override
|
||||||
|
.moveset([ Moves.FREEZE_DRY ])
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyMoveset([ Moves.ELECTRIFY ]);
|
||||||
|
game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1);
|
||||||
|
|
||||||
|
await game.challengeMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveLastReturnedWith(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 1x damage to water/flying type during inverse battle under Electrify", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyMoveset([ Moves.ELECTRIFY ])
|
||||||
|
.enemySpecies(Species.GYARADOS);
|
||||||
|
|
||||||
|
game.challengeMode.addChallenge(Challenges.INVERSE_BATTLE, 1, 1);
|
||||||
|
|
||||||
|
await game.challengeMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue