Merge branch 'beta' into instruct

This commit is contained in:
Bertie690 2024-11-14 07:27:34 -05:00 committed by GitHub
commit 113de0afae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 205 additions and 15 deletions

View File

@ -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;
@ -9541,7 +9567,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()

View File

@ -26,6 +26,7 @@ import {
applyMoveAttrs, applyMoveAttrs,
AttackMove, AttackMove,
DelayedAttackAttr, DelayedAttackAttr,
FlinchAttr,
HitsTagAttr, HitsTagAttr,
MissEffectAttr, MissEffectAttr,
MoveAttr, MoveAttr,
@ -510,6 +511,10 @@ export class MoveEffectPhase extends PokemonPhase {
*/ */
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean) : () => void { protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean) : () => void {
return () => { return () => {
if (this.move.getMove().hasAttr(FlinchAttr)) {
return;
}
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.getMove().hitsSubstitute(user, target)) { if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !this.move.getMove().hitsSubstitute(user, target)) {
const flinched = new BooleanHolder(false); const flinched = new BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);

View File

@ -29,10 +29,14 @@ export class QuietFormChangePhase extends BattlePhase {
const preName = getPokemonNameWithAffix(this.pokemon); const preName = getPokemonNameWithAffix(this.pokemon);
if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag)) { if (!this.pokemon.isOnField() || this.pokemon.getTag(SemiInvulnerableTag) || this.pokemon.isFainted()) {
this.pokemon.changeForm(this.formChange).then(() => { if (this.pokemon.isPlayer() || this.pokemon.isActive()) {
this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500); this.pokemon.changeForm(this.formChange).then(() => {
}); this.scene.ui.showText(getSpeciesFormChangeMessage(this.pokemon, this.formChange, preName), null, () => this.end(), 1500);
});
} else {
this.end();
}
return; return;
} }

View File

@ -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);
});
}); });