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.
|
* 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 user - The {@linkcode Pokemon} using the move
|
||||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||||
* @param move - The {@linkcode Move} with this attribute
|
* @param move - The {@linkcode Move} with this attribute
|
||||||
* @param args -
|
* @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
|
* `[1]`: The target's entire defensive type profile
|
||||||
* `[2]`: The current {@linkcode PokemonType} of the move
|
* `[2]`: The current {@linkcode PokemonType} of the move
|
||||||
* @returns `true` if application of the attribute succeeds
|
* @returns `true` if application of the attribute succeeds
|
||||||
*/
|
*/
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean {
|
abstract public override apply(user: Pokemon, target: Pokemon, move: Move, args: [multiplier: NumberHolder, types: PokemonType[], moveType: PokemonType]): boolean;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute to implement {@linkcode MoveId.FREEZE_DRY}'s guaranteed water type super effectiveness.
|
* Attribute to implement {@linkcode MoveId.FREEZE_DRY}'s guaranteed water type super effectiveness.
|
||||||
*/
|
*/
|
||||||
export class FreezeDryAttr extends VariableMoveTypeChartAttr {
|
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;
|
const [multiplier, types, moveType] = args;
|
||||||
if (!types.includes(PokemonType.WATER)) {
|
if (!types.includes(PokemonType.WATER)) {
|
||||||
return false;
|
return false;
|
||||||
@ -5390,7 +5389,7 @@ export class FreezeDryAttr extends VariableMoveTypeChartAttr {
|
|||||||
* against all ungrounded flying types.
|
* against all ungrounded flying types.
|
||||||
*/
|
*/
|
||||||
export class NeutralDamageAgainstFlyingTypeAttr extends VariableMoveTypeChartAttr {
|
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;
|
const [multiplier, types] = args;
|
||||||
if (target.isGrounded() || !types.includes(PokemonType.FLYING)) {
|
if (target.isGrounded() || !types.includes(PokemonType.FLYING)) {
|
||||||
return false;
|
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.
|
* 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.
|
* 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.
|
* 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,
|
VariableMoveTypeChartAttr,
|
||||||
FreezeDryAttr,
|
FreezeDryAttr,
|
||||||
OneHitKOAccuracyAttr,
|
OneHitKOAccuracyAttr,
|
||||||
|
HitsSameTypeAttr,
|
||||||
SheerColdAccuracyAttr,
|
SheerColdAccuracyAttr,
|
||||||
MissEffectAttr,
|
MissEffectAttr,
|
||||||
NoEffectAttr,
|
NoEffectAttr,
|
||||||
@ -8487,7 +8494,7 @@ const MoveAttrs = Object.freeze({
|
|||||||
VariableTargetAttr,
|
VariableTargetAttr,
|
||||||
AfterYouAttr,
|
AfterYouAttr,
|
||||||
ForceLastAttr,
|
ForceLastAttr,
|
||||||
HitsSameTypeAttr,
|
,
|
||||||
ResistLastMoveTypeAttr,
|
ResistLastMoveTypeAttr,
|
||||||
ExposedMoveAttr,
|
ExposedMoveAttr,
|
||||||
});
|
});
|
||||||
@ -10010,9 +10017,8 @@ export function initMoves() {
|
|||||||
.attr(CompareWeightPowerAttr)
|
.attr(CompareWeightPowerAttr)
|
||||||
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED),
|
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED),
|
||||||
new AttackMove(MoveId.SYNCHRONOISE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
|
new AttackMove(MoveId.SYNCHRONOISE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
.attr(HitsSameTypeAttr)
|
||||||
.condition(unknownTypeCondition)
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||||
.attr(HitsSameTypeAttr),
|
|
||||||
new AttackMove(MoveId.ELECTRO_BALL, PokemonType.ELECTRIC, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 5)
|
new AttackMove(MoveId.ELECTRO_BALL, PokemonType.ELECTRIC, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 5)
|
||||||
.attr(ElectroBallPowerAttr)
|
.attr(ElectroBallPowerAttr)
|
||||||
.ballBombMove(),
|
.ballBombMove(),
|
||||||
|
@ -2547,7 +2547,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
// Apply any typing changes from Freeze-Dry, etc.
|
// Apply any typing changes from Freeze-Dry, etc.
|
||||||
if (move) {
|
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
|
// Handle strong winds lowering effectiveness of types super effective against pure flying
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { MoveResult } from "#enums/move-result";
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import Phaser from "phaser";
|
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", () => {
|
describe("Moves - Synchronoise", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -23,7 +25,6 @@ describe("Moves - Synchronoise", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset([MoveId.SYNCHRONOISE])
|
|
||||||
.ability(AbilityId.BALL_FETCH)
|
.ability(AbilityId.BALL_FETCH)
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.criticalHits(false)
|
.criticalHits(false)
|
||||||
@ -32,25 +33,65 @@ describe("Moves - Synchronoise", () => {
|
|||||||
.enemyMoveset(MoveId.SPLASH);
|
.enemyMoveset(MoveId.SPLASH);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Write test
|
it("should affect all opponents that share a type with the user", async () => {
|
||||||
it.todo("should affect all opponents that share a type with the user");
|
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 () => {
|
it("should consider the user's Tera Type if it is Terastallized", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.BIDOOF]);
|
await game.classicMode.startBattle([SpeciesId.BIDOOF]);
|
||||||
|
|
||||||
const playerPokemon = game.field.getPlayerPokemon();
|
const bidoof = game.field.getPlayerPokemon();
|
||||||
const enemyPokemon = game.field.getEnemyPokemon();
|
const karp = game.field.getEnemyPokemon();
|
||||||
|
|
||||||
playerPokemon.teraType = PokemonType.WATER;
|
game.field.forceTera(bidoof, PokemonType.WATER);
|
||||||
game.move.selectWithTera(MoveId.SYNCHRONOISE);
|
game.move.use(MoveId.SYNCHRONOISE);
|
||||||
await game.toEndOfTurn();
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
expect(enemyPokemon).not.toHaveFullHp();
|
expect(bidoof).toHaveUsedMove({ move: MoveId.SYNCHRONOISE, result: MoveResult.SUCCESS });
|
||||||
|
expect(karp).not.toHaveFullHp();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Write test
|
it("should consider the user/target's normal types if Terastallized into Tera Stellar", async () => {
|
||||||
it.todo("should fail if no opponents share a type with the user");
|
await game.classicMode.startBattle([SpeciesId.ABRA]);
|
||||||
|
|
||||||
// TODO: Write test
|
const abra = game.field.getPlayerPokemon();
|
||||||
it.todo("should fail if the user is typeless");
|
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 */
|
/* biome-ignore-start lint/correctness/noUnusedImports: tsdoc imports */
|
||||||
import type { globalScene } from "#app/global-scene";
|
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 */
|
/* biome-ignore-end lint/correctness/noUnusedImports: tsdoc imports */
|
||||||
|
|
||||||
import type { Ability } from "#abilities/ability";
|
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 pokemon - The pokemon to Terastallize
|
||||||
* @param teraType - The {@linkcode PokemonType} to terastallize into; defaults to the pokemon's primary type.
|
* @param teraType - The {@linkcode PokemonType} to Terastallize into; defaults to `pokemon`'s primary type if not provided
|
||||||
* @remarks
|
* @remarks
|
||||||
* This function only mocks the Pokemon's tera-related variables; it does NOT activate any tera-related abilities.
|
* 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 {
|
public forceTera(pokemon: Pokemon, teraType: PokemonType = pokemon.getSpeciesForm(true).type1): void {
|
||||||
vi.spyOn(pokemon, "isTerastallized", "get").mockReturnValue(true);
|
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 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 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.
|
* @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)
|
* 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(
|
public selectWithTera(
|
||||||
move: MoveId,
|
move: MoveId,
|
||||||
|
Loading…
Reference in New Issue
Block a user