mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-09-24 07:23:24 +02:00
Moved inverse battle check to getTypeDamageMultiplier
to avoid duplication; fixed tests
This commit is contained in:
parent
41d2b14cbd
commit
7b912ee033
@ -4199,9 +4199,9 @@ function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
|
||||
const anticipationCondition: AbAttrCondition = (pokemon: Pokemon) =>
|
||||
pokemon.getOpponents().some(opponent =>
|
||||
opponent.moveset.some(movesetMove => {
|
||||
// ignore null/undefined moves or non-attacks
|
||||
const move = movesetMove?.getMove();
|
||||
if (!move?.is("AttackMove")) {
|
||||
// ignore non-attacks
|
||||
const move = movesetMove.getMove();
|
||||
if (!move.is("AttackMove")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -5395,7 +5395,7 @@ export class FreezeDryAttr extends VariableMoveTypeChartAttr {
|
||||
|
||||
// Replace whatever the prior "normal" water effectiveness was with a guaranteed 2x multi
|
||||
const normalEff = getTypeDamageMultiplier(moveType, PokemonType.WATER)
|
||||
multiplier.value = 2 * multiplier.value / normalEff;
|
||||
multiplier.value *= 2 / normalEff;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -5411,7 +5411,6 @@ export class NeutralDamageAgainstFlyingTypeAttr extends VariableMoveTypeChartAtt
|
||||
return false;
|
||||
}
|
||||
multiplier.value = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -8147,12 +8146,13 @@ export class UpperHandCondition extends MoveCondition {
|
||||
|
||||
/**
|
||||
* 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~~ Does nothing if the user already has ALL types that resist the target's last used move.
|
||||
* Fails if the opponent has not used a move yet
|
||||
* Fails if the type is unknown or stellar
|
||||
* ~~Fails~~ Does nothing if the type is unknown or stellar
|
||||
*
|
||||
* TODO:
|
||||
* If a move has its type changed (e.g. {@linkcode MoveId.HIDDEN_POWER}), it will check the new type.
|
||||
* Does not fail when it should
|
||||
*/
|
||||
export class ResistLastMoveTypeAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
@ -8182,8 +8182,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr {
|
||||
if (moveData.type === PokemonType.STELLAR || moveData.type === PokemonType.UNKNOWN) {
|
||||
return false;
|
||||
}
|
||||
const userTypes = user.getTypes();
|
||||
const validTypes = this.getTypeResistances(globalScene.gameMode, moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types
|
||||
const validTypes = this.getTypeResistances(user, moveData.type)
|
||||
if (!validTypes.length) {
|
||||
return false;
|
||||
}
|
||||
@ -8197,21 +8196,26 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr {
|
||||
|
||||
/**
|
||||
* Retrieve the types resisting a given type. Used by Conversion 2
|
||||
* @returns An array populated with Types, or an empty array if no resistances exist (Unknown or Stellar type)
|
||||
* @param moveType - The type of the move having been used
|
||||
* @returns An array containing all types that resist the given move's type
|
||||
* and are not currently shared by the user
|
||||
*/
|
||||
getTypeResistances(gameMode: GameMode, type: number): PokemonType[] {
|
||||
const typeResistances: PokemonType[] = [];
|
||||
private getTypeResistances(user: Pokemon, moveType: PokemonType): PokemonType[] {
|
||||
const resistances: PokemonType[] = [];
|
||||
const userTypes = user.getTypes(true, true)
|
||||
|
||||
for (let i = 0; i < Object.keys(PokemonType).length; i++) {
|
||||
const multiplier = new NumberHolder(1);
|
||||
multiplier.value = getTypeDamageMultiplier(type, i);
|
||||
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
|
||||
if (multiplier.value < 1) {
|
||||
typeResistances.push(i);
|
||||
|
||||
for (const type of getEnumValues(PokemonType)) {
|
||||
if (userTypes.includes(type)) {
|
||||
continue;
|
||||
}
|
||||
const multiplier = getTypeDamageMultiplier(moveType, type);
|
||||
if (multiplier < 1) {
|
||||
resistances.push(type);
|
||||
}
|
||||
}
|
||||
|
||||
return typeResistances;
|
||||
return resistances;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
|
@ -1,15 +1,28 @@
|
||||
import { ChallengeType } from "#enums/challenge-type";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { applyChallenges } from "#utils/challenge-utils";
|
||||
import { NumberHolder } from "#utils/common";
|
||||
|
||||
export type TypeDamageMultiplier = 0 | 0.125 | 0.25 | 0.5 | 1 | 2 | 4 | 8;
|
||||
|
||||
export type SingleTypeDamageMultiplier = 0 | 0.5 | 1 | 2;
|
||||
|
||||
/**
|
||||
* Get the type effectiveness multiplier of one PokemonType against another.
|
||||
* Get the base type effectiveness of one `PokemonType` against another. \
|
||||
* Accounts for Inverse Battle's reversed type effectiveness, but does not apply any other effects.
|
||||
* @param attackType - The {@linkcode PokemonType} of the attacker
|
||||
* @param defType - The {@linkcode PokemonType} of the defender
|
||||
* @returns The type damage multiplier between the two types;
|
||||
* will be either `0`, `0.5`, `1` or `2`.
|
||||
*/
|
||||
export function getTypeDamageMultiplier(attackType: PokemonType, defType: PokemonType): TypeDamageMultiplier {
|
||||
export function getTypeDamageMultiplier(attackType: PokemonType, defType: PokemonType): SingleTypeDamageMultiplier {
|
||||
const multi = new NumberHolder(getTypeChartMultiplier(attackType, defType));
|
||||
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multi);
|
||||
return multi.value as SingleTypeDamageMultiplier;
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This simulates the Pokemon type chart with nested `switch case`s
|
||||
function getTypeChartMultiplier(attackType: PokemonType, defType: PokemonType): SingleTypeDamageMultiplier {
|
||||
if (attackType === PokemonType.UNKNOWN || defType === PokemonType.UNKNOWN) {
|
||||
return 1;
|
||||
}
|
||||
@ -270,10 +283,7 @@ export function getTypeDamageMultiplier(attackType: PokemonType, defType: Pokemo
|
||||
case PokemonType.STELLAR:
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the color corresponding to a specific damage multiplier
|
||||
* @returns A color or undefined if the default color should be used
|
||||
|
@ -1,4 +1,5 @@
|
||||
export enum PokemonType {
|
||||
/** Typeless */
|
||||
UNKNOWN = -1,
|
||||
NORMAL = 0,
|
||||
FIGHTING,
|
||||
|
@ -2520,32 +2520,33 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return this.isTerastallized ? 2 : 1;
|
||||
}
|
||||
|
||||
const types = this.getTypes(true, true, undefined, useIllusion);
|
||||
const types = this.getTypes(true, true, false, useIllusion);
|
||||
const arena = globalScene.arena;
|
||||
|
||||
// Handle flying v ground type immunity without removing flying type so effective types are still effective
|
||||
// Related to https://github.com/pagefaultgames/pokerogue/issues/524
|
||||
if (moveType === PokemonType.GROUND && (this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY))) {
|
||||
const flyingIndex = types.indexOf(PokemonType.FLYING);
|
||||
if (flyingIndex > -1) {
|
||||
types.splice(flyingIndex, 1);
|
||||
}
|
||||
// TODO: Fix once gravity makes pokemon actually grounded
|
||||
if (
|
||||
moveType === PokemonType.GROUND &&
|
||||
types.includes(PokemonType.FLYING) &&
|
||||
(this.isGrounded() || arena.hasTag(ArenaTagType.GRAVITY))
|
||||
) {
|
||||
types.splice(types.indexOf(PokemonType.FLYING), 1);
|
||||
}
|
||||
|
||||
const multi = new NumberHolder(1);
|
||||
for (const defenderType of types) {
|
||||
const typeMulti = new NumberHolder(getTypeDamageMultiplier(moveType, defenderType));
|
||||
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, typeMulti);
|
||||
// If the target is immune to the type in question, check for any effects that would ignore said effect
|
||||
const typeMulti = getTypeDamageMultiplier(moveType, defenderType);
|
||||
// If the target is immune to the type in question, check for effects that would ignore said nullification
|
||||
// TODO: Review if the `isActive` check is needed anymore
|
||||
if (
|
||||
source?.isActive(true) &&
|
||||
typeMulti.value === 0 &&
|
||||
typeMulti === 0 &&
|
||||
this.checkIgnoreTypeImmunity({ source, simulated, moveType, defenderType })
|
||||
) {
|
||||
typeMulti.value = 1;
|
||||
continue;
|
||||
}
|
||||
multi.value *= typeMulti.value;
|
||||
multi.value *= typeMulti;
|
||||
}
|
||||
|
||||
// Apply any typing changes from Freeze-Dry, etc.
|
||||
@ -2554,14 +2555,12 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
// Handle strong winds lowering effectiveness of types super effective against pure flying
|
||||
const typeMultiplierAgainstFlying = new NumberHolder(getTypeDamageMultiplier(moveType, PokemonType.FLYING));
|
||||
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, typeMultiplierAgainstFlying);
|
||||
if (
|
||||
!ignoreStrongWinds &&
|
||||
arena.getWeatherType() === WeatherType.STRONG_WINDS &&
|
||||
!arena.weather?.isEffectSuppressed() &&
|
||||
types.includes(PokemonType.FLYING) &&
|
||||
typeMultiplierAgainstFlying.value === 2
|
||||
getTypeDamageMultiplier(moveType, PokemonType.FLYING) === 2
|
||||
) {
|
||||
multi.value /= 2;
|
||||
if (!simulated) {
|
||||
@ -4326,10 +4325,14 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
: this.summonData.tags.find(t => t.tagType === tagType);
|
||||
}
|
||||
|
||||
findTag<T extends BattlerTag>(tagFilter: (tag: BattlerTag) => tag is T): T | undefined;
|
||||
findTag(tagFilter: (tag: BattlerTag) => boolean): BattlerTag | undefined;
|
||||
findTag(tagFilter: (tag: BattlerTag) => boolean) {
|
||||
return this.summonData.tags.find(t => tagFilter(t));
|
||||
}
|
||||
|
||||
findTags<T extends BattlerTag>(tagFilter: (tag: BattlerTag) => tag is T): T[];
|
||||
findTags(tagFilter: (tag: BattlerTag) => boolean): BattlerTag[];
|
||||
findTags(tagFilter: (tag: BattlerTag) => boolean): BattlerTag[] {
|
||||
return this.summonData.tags.filter(t => tagFilter(t));
|
||||
}
|
||||
|
@ -149,7 +149,6 @@ describe("Inverse Battle", () => {
|
||||
expect(enemy.status?.effect).not.toBe(StatusEffect.PARALYSIS);
|
||||
});
|
||||
|
||||
// TODO: These should belong to their respective moves' test files, not the inverse battle mechanic itself
|
||||
it("Ground type is not immune to Thunder Wave - Thunder Wave against Sandshrew", async () => {
|
||||
game.override.moveset([MoveId.THUNDER_WAVE]).enemySpecies(SpeciesId.SANDSHREW);
|
||||
|
||||
|
@ -94,7 +94,7 @@ describe.sequential("Move - Freeze-Dry", () => {
|
||||
|
||||
// Water type terastallized into steel; 0.5x
|
||||
enemy.teraType = PokemonType.STEEL;
|
||||
expectEffectiveness([PokemonType.WATER], 2);
|
||||
expectEffectiveness([PokemonType.WATER], 0.5);
|
||||
});
|
||||
|
||||
it.each<{ name: string; types: typesArray; eff: TypeDamageMultiplier }>([
|
||||
|
@ -70,10 +70,21 @@ export class ChallengeModeHelper extends GameManagerHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions to the start of a battle.
|
||||
* @param species - Optional array of species to start the battle with.
|
||||
* Transitions the challenge game to the start of a new battle.
|
||||
* @param species - An array of {@linkcode Species} to summon.
|
||||
* @returns A promise that resolves when the battle is started.
|
||||
* @todo This duplicates all its code with the classic mode variant...
|
||||
*/
|
||||
async startBattle(species: SpeciesId[]): Promise<void>;
|
||||
/**
|
||||
* Transitions the challenge game to the start of a new battle.
|
||||
* Selects 3 daily run starters with a fixed seed of "test"
|
||||
* (see `DailyRunConfig.getDailyRunStarters` in `daily-run.ts` for more info).
|
||||
* @returns A promise that resolves when the battle is started.
|
||||
* @deprecated - Specifying the starters helps prevent inconsistencies from internal RNG changes.
|
||||
* @todo This duplicates all its code with the classic mode variant...
|
||||
*/
|
||||
async startBattle(): Promise<void>;
|
||||
async startBattle(species?: SpeciesId[]) {
|
||||
await this.runToSummon(species);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user