mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-24 07:23:24 +02:00
Fixed synchronoise and added tests
This commit is contained in:
parent
7e402d02b0
commit
9187047b94
@ -5352,27 +5352,26 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr {
|
||||
/**
|
||||
* Attribute for moves which have a custom type chart interaction.
|
||||
*/
|
||||
export class VariableMoveTypeChartAttr extends MoveAttr {
|
||||
export abstract class VariableMoveTypeChartAttr extends MoveAttr {
|
||||
/**
|
||||
* Apply the attribute to change the move's type effectiveness multiplier.
|
||||
* @param user - The {@linkcode Pokemon} using the move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param move - The {@linkcode Move} with this attribute
|
||||
* @param args -
|
||||
* `[0]`: A {@linkcode NumberHolder} holding the type effectiveness
|
||||
* `[0]`: A {@linkcode NumberHolder} holding the current type effectiveness
|
||||
* `[1]`: The target's entire defensive type profile
|
||||
* `[2]`: The current {@linkcode PokemonType} of the move
|
||||
* @returns `true` if application of the attribute succeeds
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean {
|
||||
return false;
|
||||
}
|
||||
abstract public override apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute to implement {@linkcode MoveId.FREEZE_DRY}'s guaranteed water type super effectiveness.
|
||||
*/
|
||||
export class FreezeDryAttr extends VariableMoveTypeChartAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean {
|
||||
public override apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean {
|
||||
const [multiplier, types, moveType] = args;
|
||||
if (!types.includes(PokemonType.WATER)) {
|
||||
return false;
|
||||
@ -5390,7 +5389,7 @@ export class FreezeDryAttr extends VariableMoveTypeChartAttr {
|
||||
* against all ungrounded flying types.
|
||||
*/
|
||||
export class NeutralDamageAgainstFlyingTypeAttr extends VariableMoveTypeChartAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean {
|
||||
public override apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean {
|
||||
const [multiplier, types] = args;
|
||||
if (target.isGrounded() || !types.includes(PokemonType.FLYING)) {
|
||||
return false;
|
||||
@ -5401,6 +5400,26 @@ export class NeutralDamageAgainstFlyingTypeAttr extends VariableMoveTypeChartAtt
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute used by {@linkcode MoveId.SYNCHRONOISE} to render the move ineffective
|
||||
* against all targets who do not share a type with the user.
|
||||
*/
|
||||
export class HitsSameTypeAttr extends VariableMoveTypeChartAttr {
|
||||
public override apply(user: Pokemon, _target: Pokemon, _move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean {
|
||||
const [multiplier, types] = args;
|
||||
if (user.getTypes(true).eev(type => types.includes(type))) {
|
||||
return false;
|
||||
}
|
||||
multiplier.value = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return unknownTypeCondition;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attribute used by {@linkcode MoveId.FLYING_PRESS} to add the Flying Type to its type effectiveness.
|
||||
*/
|
||||
@ -8140,19 +8159,6 @@ export class UpperHandCondition extends MoveCondition {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Does this need to extend from this?
|
||||
// The only reason it might is to show ineffectiveness text but w/e
|
||||
export class HitsSameTypeAttr extends VariableMoveTypeChartAttr {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const multiplier = args[0] as NumberHolder;
|
||||
if (!user.getTypes(true).some(type => target.getTypes(true).includes(type))) {
|
||||
multiplier.value = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute used for Conversion 2, to convert the user's type to a random type that resists the target's last used move.
|
||||
* Fails if the user already has ALL types that resist the target's last used move.
|
||||
@ -8419,6 +8425,7 @@ const MoveAttrs = Object.freeze({
|
||||
VariableMoveTypeChartAttr,
|
||||
FreezeDryAttr,
|
||||
OneHitKOAccuracyAttr,
|
||||
HitsSameTypeAttr,
|
||||
SheerColdAccuracyAttr,
|
||||
MissEffectAttr,
|
||||
NoEffectAttr,
|
||||
@ -8487,7 +8494,7 @@ const MoveAttrs = Object.freeze({
|
||||
VariableTargetAttr,
|
||||
AfterYouAttr,
|
||||
ForceLastAttr,
|
||||
HitsSameTypeAttr,
|
||||
,
|
||||
ResistLastMoveTypeAttr,
|
||||
ExposedMoveAttr,
|
||||
});
|
||||
@ -10010,9 +10017,8 @@ export function initMoves() {
|
||||
.attr(CompareWeightPowerAttr)
|
||||
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED),
|
||||
new AttackMove(MoveId.SYNCHRONOISE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||
.condition(unknownTypeCondition)
|
||||
.attr(HitsSameTypeAttr),
|
||||
.attr(HitsSameTypeAttr)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(MoveId.ELECTRO_BALL, PokemonType.ELECTRIC, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 5)
|
||||
.attr(ElectroBallPowerAttr)
|
||||
.ballBombMove(),
|
||||
|
@ -2547,7 +2547,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
// Apply any typing changes from Freeze-Dry, etc.
|
||||
if (move) {
|
||||
applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multi, types, moveType);
|
||||
applyMoveAttrs("VariableMoveTypeChartAttr", source ?? null, this, move, multi, types, moveType);
|
||||
}
|
||||
|
||||
// Handle strong winds lowering effectiveness of types super effective against pure flying
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { BattlerIndex } from "#enums/battler-index";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { MoveResult } from "#enums/move-result";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { GameManager } from "#test/test-utils/game-manager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Moves - Synchronoise", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -23,7 +25,6 @@ describe("Moves - Synchronoise", () => {
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([MoveId.SYNCHRONOISE])
|
||||
.ability(AbilityId.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.criticalHits(false)
|
||||
@ -32,25 +33,65 @@ describe("Moves - Synchronoise", () => {
|
||||
.enemyMoveset(MoveId.SPLASH);
|
||||
});
|
||||
|
||||
// TODO: Write test
|
||||
it.todo("should affect all opponents that share a type with the user");
|
||||
it("should affect all opponents that share a type with the user", async () => {
|
||||
game.override.battleStyle("double");
|
||||
await game.classicMode.startBattle([SpeciesId.BIBAREL, SpeciesId.STARLY]);
|
||||
|
||||
const [bidoof, starly] = game.scene.getPlayerField();
|
||||
const [karp1, karp2] = game.scene.getEnemyField();
|
||||
// Mock 2nd magikarp to be a completely different type
|
||||
vi.spyOn(karp2, "getTypes").mockReturnValue([PokemonType.GRASS]);
|
||||
|
||||
game.move.use(MoveId.SYNCHRONOISE, BattlerIndex.PLAYER);
|
||||
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(bidoof).toHaveUsedMove({ move: MoveId.SYNCHRONOISE, result: MoveResult.SUCCESS });
|
||||
expect(starly).not.toHaveFullHp();
|
||||
expect(karp1).not.toHaveFullHp();
|
||||
expect(karp2).toHaveFullHp();
|
||||
});
|
||||
|
||||
it("should consider the user's Tera Type if it is Terastallized", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.BIDOOF]);
|
||||
|
||||
const playerPokemon = game.field.getPlayerPokemon();
|
||||
const enemyPokemon = game.field.getEnemyPokemon();
|
||||
const bidoof = game.field.getPlayerPokemon();
|
||||
const karp = game.field.getEnemyPokemon();
|
||||
|
||||
playerPokemon.teraType = PokemonType.WATER;
|
||||
game.move.selectWithTera(MoveId.SYNCHRONOISE);
|
||||
game.field.forceTera(bidoof, PokemonType.WATER);
|
||||
game.move.use(MoveId.SYNCHRONOISE);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(enemyPokemon).not.toHaveFullHp();
|
||||
expect(bidoof).toHaveUsedMove({ move: MoveId.SYNCHRONOISE, result: MoveResult.SUCCESS });
|
||||
expect(karp).not.toHaveFullHp();
|
||||
});
|
||||
|
||||
// TODO: Write test
|
||||
it.todo("should fail if no opponents share a type with the user");
|
||||
it("should consider the user/target's normal types if Terastallized into Tera Stellar", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.ABRA]);
|
||||
|
||||
// TODO: Write test
|
||||
it.todo("should fail if the user is typeless");
|
||||
const abra = game.field.getPlayerPokemon();
|
||||
const karp = game.field.getEnemyPokemon();
|
||||
|
||||
game.field.forceTera(abra, PokemonType.STELLAR);
|
||||
game.field.forceTera(karp, PokemonType.STELLAR);
|
||||
game.move.use(MoveId.SYNCHRONOISE);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(abra).toHaveUsedMove({ move: MoveId.SYNCHRONOISE, result: MoveResult.MISS });
|
||||
expect(karp).toHaveFullHp();
|
||||
});
|
||||
|
||||
it("should count as ineffective if no enemies share types with the user", async () => {
|
||||
await game.classicMode.startBattle([SpeciesId.BIBAREL]);
|
||||
|
||||
const bibarel = game.field.getPlayerPokemon();
|
||||
const karp = game.field.getEnemyPokemon();
|
||||
bibarel.summonData.types = [PokemonType.UNKNOWN];
|
||||
|
||||
game.move.use(MoveId.SYNCHRONOISE);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(bibarel).toHaveUsedMove({ move: MoveId.SYNCHRONOISE, result: MoveResult.MISS });
|
||||
expect(karp).toHaveFullHp();
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */
|
||||
import type { globalScene } from "#app/global-scene";
|
||||
import type { MoveHelper } from "#test/test-utils/helpers/move-helper";
|
||||
/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */
|
||||
|
||||
import type { Ability } from "#abilities/ability";
|
||||
@ -75,12 +76,14 @@ export class FieldHelper extends GameManagerHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a given Pokemon to be terastallized to the given type.
|
||||
* Force a given Pokemon to be Terastallized to the given type.
|
||||
*
|
||||
* @param pokemon - The pokemon to terastallize.
|
||||
* @param teraType - The {@linkcode PokemonType} to terastallize into; defaults to the pokemon's primary type.
|
||||
* @param pokemon - The pokemon to Terastallize
|
||||
* @param teraType - The {@linkcode PokemonType} to Terastallize into; defaults to `pokemon`'s primary type if not provided
|
||||
* @remarks
|
||||
* This function only mocks the Pokemon's tera-related variables; it does NOT activate any tera-related abilities.
|
||||
* If activating on-Terastallize effects is desired, use either {@linkcode MoveHelper.use} with `useTera=true`,
|
||||
* or {@linkcode MoveHelper.selectWithTera} instead.
|
||||
*/
|
||||
public forceTera(pokemon: Pokemon, teraType: PokemonType = pokemon.getSpeciesForm(true).type1): void {
|
||||
vi.spyOn(pokemon, "isTerastallized", "get").mockReturnValue(true);
|
||||
|
@ -97,11 +97,15 @@ export class MoveHelper extends GameManagerHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a move _already in the player's moveset_ to be used during the next {@linkcode CommandPhase}, **which will also terastallize on this turn**.
|
||||
* Select a move _already in the player's moveset_ to be used during the next {@linkcode CommandPhase},
|
||||
* **which will also terastallize on this turn**.
|
||||
* Activates all relevant abilities and effects on Terastallizing (equivalent to inputting the command manually)
|
||||
* @param move - The {@linkcode MoveId} to use.
|
||||
* @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified.
|
||||
* @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves.
|
||||
* If set to `null`, will forgo normal target selection entirely (useful for UI tests)
|
||||
* @remarks
|
||||
* Will fail the current test if the move being selected is not in the user's moveset.
|
||||
*/
|
||||
public selectWithTera(
|
||||
move: MoveId,
|
||||
|
Loading…
Reference in New Issue
Block a user