[P2][Beta] Freeze-dry Re-implementation (#4874)
This commit is contained in:
parent
f778bd5877
commit
b1138c1d70
|
@ -516,7 +516,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
|
||||||
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
const modifierValue = args.length > 0
|
const modifierValue = args.length > 0
|
||||||
? (args[0] as Utils.NumberHolder).value
|
? (args[0] as Utils.NumberHolder).value
|
||||||
: pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker);
|
: pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move);
|
||||||
|
|
||||||
if (move instanceof AttackMove && modifierValue < 2) {
|
if (move instanceof AttackMove && modifierValue < 2) {
|
||||||
cancelled.value = true; // Suppresses "No Effect" message
|
cancelled.value = true; // Suppresses "No Effect" message
|
||||||
|
@ -3180,7 +3180,7 @@ function getAnticipationCondition(): AbAttrCondition {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// the move's base type (not accounting for variable type changes) is super effective
|
// the move's base type (not accounting for variable type changes) is super effective
|
||||||
if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) {
|
if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// move is a OHKO
|
// move is a OHKO
|
||||||
|
|
|
@ -897,7 +897,7 @@ export class AttackMove extends Move {
|
||||||
|
|
||||||
let attackScore = 0;
|
let attackScore = 0;
|
||||||
|
|
||||||
const effectiveness = target.getAttackTypeEffectiveness(this.type, user);
|
const effectiveness = target.getAttackTypeEffectiveness(this.type, user, undefined, undefined, this);
|
||||||
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
|
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
|
||||||
if (attackScore) {
|
if (attackScore) {
|
||||||
if (this.category === MoveCategory.PHYSICAL) {
|
if (this.category === MoveCategory.PHYSICAL) {
|
||||||
|
@ -4971,48 +4971,6 @@ export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
const multiplier = args[0] as Utils.NumberHolder;
|
|
||||||
if (target.isOfType(Type.WATER) && multiplier.value !== 0) {
|
|
||||||
const multipleTypes = (target.getTypes().length > 1);
|
|
||||||
|
|
||||||
if (multipleTypes) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
multiplier.value = 2;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr {
|
export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr {
|
||||||
/**
|
/**
|
||||||
* Checks to see if the Target is Ice-Type or not. If so, the move will have no effect.
|
* Checks to see if the Target is Ice-Type or not. If so, the move will have no effect.
|
||||||
|
@ -5040,6 +4998,41 @@ export class FlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute for moves which have a custom type chart interaction.
|
||||||
|
*/
|
||||||
|
export class VariableMoveTypeChartAttr extends MoveAttr {
|
||||||
|
/**
|
||||||
|
* @param user {@linkcode Pokemon} using the move
|
||||||
|
* @param target {@linkcode Pokemon} target of the move
|
||||||
|
* @param move {@linkcode Move} with this attribute
|
||||||
|
* @param args [0] {@linkcode NumberHolder} holding the type effectiveness
|
||||||
|
* @param args [1] A single defensive type of the target
|
||||||
|
*
|
||||||
|
* @returns true if application of the attribute succeeds
|
||||||
|
*/
|
||||||
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class forces Freeze-Dry to be super effective against Water Type.
|
||||||
|
*/
|
||||||
|
export class FreezeDryAttr extends VariableMoveTypeChartAttr {
|
||||||
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
|
const multiplier = args[0] as Utils.NumberHolder;
|
||||||
|
const defType = args[1] as Type;
|
||||||
|
|
||||||
|
if (defType === Type.WATER) {
|
||||||
|
multiplier.value = 2;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class OneHitKOAccuracyAttr extends VariableAccuracyAttr {
|
export class OneHitKOAccuracyAttr extends VariableAccuracyAttr {
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
const accuracy = args[0] as Utils.NumberHolder;
|
const accuracy = args[0] as Utils.NumberHolder;
|
||||||
|
@ -9448,8 +9441,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(FreezeDryAttr)
|
.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.
|
|
||||||
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()
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
|
|
|
@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "#app/battle-scene";
|
||||||
import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
|
import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
|
||||||
import { variantData } from "#app/data/variant";
|
import { variantData } from "#app/data/variant";
|
||||||
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
|
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info";
|
||||||
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move";
|
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr, VariableMoveTypeChartAttr } from "#app/data/move";
|
||||||
import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
|
||||||
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
|
import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
|
||||||
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
||||||
|
@ -1632,7 +1632,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
const moveType = source.getMoveType(move);
|
const moveType = source.getMoveType(move);
|
||||||
|
|
||||||
const typeMultiplier = new Utils.NumberHolder((move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr))
|
const typeMultiplier = new Utils.NumberHolder((move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr))
|
||||||
? this.getAttackTypeEffectiveness(moveType, source, false, simulated)
|
? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move)
|
||||||
: 1);
|
: 1);
|
||||||
|
|
||||||
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
|
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier);
|
||||||
|
@ -1684,9 +1684,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
* @param source {@linkcode Pokemon} the Pokemon using the move
|
* @param source {@linkcode Pokemon} the Pokemon using the move
|
||||||
* @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks)
|
* @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks)
|
||||||
* @param simulated tag to only apply the strong winds effect message when the move is used
|
* @param simulated tag to only apply the strong winds effect message when the move is used
|
||||||
|
* @param move (optional) the move whose type effectiveness is to be checked. Used for applying {@linkcode VariableMoveTypeChartAttr}
|
||||||
* @returns a multiplier for the type effectiveness
|
* @returns a multiplier for the type effectiveness
|
||||||
*/
|
*/
|
||||||
getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier {
|
getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true, move?: Move): TypeDamageMultiplier {
|
||||||
if (moveType === Type.STELLAR) {
|
if (moveType === Type.STELLAR) {
|
||||||
return this.isTerastallized() ? 2 : 1;
|
return this.isTerastallized() ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
@ -1705,6 +1706,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
let multiplier = types.map(defType => {
|
let multiplier = types.map(defType => {
|
||||||
const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType));
|
const multiplier = new Utils.NumberHolder(getTypeDamageMultiplier(moveType, defType));
|
||||||
applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier);
|
applyChallenges(this.scene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier);
|
||||||
|
if (move) {
|
||||||
|
applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType);
|
||||||
|
}
|
||||||
if (source) {
|
if (source) {
|
||||||
const ignoreImmunity = new Utils.BooleanHolder(false);
|
const ignoreImmunity = new Utils.BooleanHolder(false);
|
||||||
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) {
|
if (source.isActive(true) && source.hasAbilityWithAttr(IgnoreTypeImmunityAbAttr)) {
|
||||||
|
|
|
@ -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 { Type } from "#enums/type";
|
||||||
import { Challenges } from "#enums/challenges";
|
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";
|
||||||
|
@ -29,7 +30,7 @@ describe("Moves - Freeze-Dry", () => {
|
||||||
.enemyMoveset(Moves.SPLASH)
|
.enemyMoveset(Moves.SPLASH)
|
||||||
.starterSpecies(Species.FEEBAS)
|
.starterSpecies(Species.FEEBAS)
|
||||||
.ability(Abilities.BALL_FETCH)
|
.ability(Abilities.BALL_FETCH)
|
||||||
.moveset([ Moves.FREEZE_DRY ]);
|
.moveset([ Moves.FREEZE_DRY, Moves.FORESTS_CURSE, Moves.SOAK ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should deal 2x damage to pure water types", async () => {
|
it("should deal 2x damage to pure water types", async () => {
|
||||||
|
@ -98,6 +99,71 @@ describe("Moves - Freeze-Dry", () => {
|
||||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should deal 8x damage to water/ground/grass type under Forest's Curse", async () => {
|
||||||
|
game.override.enemySpecies(Species.QUAGSIRE);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.FORESTS_CURSE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 2x damage to steel type terastallized into water", async () => {
|
||||||
|
game.override.enemySpecies(Species.SKARMORY)
|
||||||
|
.enemyHeldItems([{ name: "TERA_SHARD", type: Type.WATER }]);
|
||||||
|
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(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 0.5x damage to water type terastallized into fire", async () => {
|
||||||
|
game.override.enemySpecies(Species.PELIPPER)
|
||||||
|
.enemyHeldItems([{ name: "TERA_SHARD", type: Type.FIRE }]);
|
||||||
|
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.5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should deal 0.5x damage to water type Terapagos with Tera Shell", async () => {
|
||||||
|
game.override.enemySpecies(Species.TERAPAGOS)
|
||||||
|
.enemyAbility(Abilities.TERA_SHELL);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
game.move.select(Moves.SOAK);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.move.select(Moves.FREEZE_DRY);
|
||||||
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(enemy.getMoveEffectiveness).toHaveReturnedWith(0.5);
|
||||||
|
});
|
||||||
|
|
||||||
it("should deal 2x damage to water type under Normalize", async () => {
|
it("should deal 2x damage to water type under Normalize", async () => {
|
||||||
game.override.ability(Abilities.NORMALIZE);
|
game.override.ability(Abilities.NORMALIZE);
|
||||||
await game.classicMode.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
Loading…
Reference in New Issue