mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-24 18:49:16 +01:00
* Add failure conditions and move failures part 1 * Add second and third failure sequences * Refactor mostly complete, need to recheck tests * Adjust status checks to respect ignoreStatus useModes * Adjust restriction for stuff cheeks * Address bertie's review comments * Add counterRedirectAttr to other counter-like moves * Adjust some documentation for new methods * Make substitute use the move tag * Adjust counter attr to use array.find * Adjust move condition check that occurs in the third failure check sequence * Insert move failure check sequence part 4 into move phase * Revert type adjustment to getBattlerIndex * Make charging moves deduct pp on use instead of on release * Fix first move condition not using 1 based starting wave * Tweak charge move handling and protean timing * Adjust fly tests to expect pp reduction properly * Add missing attribute to counter * Adjust revival blessing hardcore test to respect new return value of isUsable * Adjust copycat test to account for how it actually works * Play sleep animation and message * Remove BYPASS_SLEEP battler tag in favor of boolean holder * Finish unfinished docs * Ensure move restrictions are only checked for players * Adjust pollen puff condition, fix docs on `isOpponent` * Fix failAgainstFinalBossCondition * Fix dig test * Adjust dive's test * Fix missing break in applyConditions * Fix getBattlerIndex for enemyPokemon * Adjust type hint test to not rely on teleport * Minor adjustments from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Add tests for teleport * Minor adjustments from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * PR review changes Fix type hints test name Update Dig/Dive test name Separate TSDoc imports in `pokemon-utils.ts` Add missing `@returns` in `move-phase.ts` Fix comment typos Separate TSDoc imports in `move-phase.ts` Add return hints to `trySelectMove` Minor formatting Remove duplicate `.affectedByGravity()` on Telekinesis Fix docs for `checkRestrictions` Manually format method definition Fix comment spacing Fix variable naming * Address kev's review comments Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Minor adjustments from code review Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> * Remove optional chaining * fix: type for InferKeys * chore: apply biome * chore: fix merge conflicts from Biome update * Remove latent isNullOrUndefined * Drop readonly on timingModifier * docs: Add class comment * Address comments from code review * Drop readonly from timingModifier * Cleanup proc chance computation * Move `cureStatus` into the Pokemon class * Final touchups --------- Co-authored-by: Bertie690 <136088738+Bertie690@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
160 lines
5.4 KiB
TypeScript
160 lines
5.4 KiB
TypeScript
import { allMoves } from "#data/data-lists";
|
|
import type { BattlerIndex } from "#enums/battler-index";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
import { MoveCategory, type MoveDamageCategory } from "#enums/move-category";
|
|
import type { MoveId } from "#enums/move-id";
|
|
import { MoveTarget } from "#enums/move-target";
|
|
import { PokemonType } from "#enums/pokemon-type";
|
|
import type { Pokemon } from "#field/pokemon";
|
|
import { applyMoveAttrs } from "#moves/apply-attrs";
|
|
import type { Move, MoveTargetSet, UserMoveConditionFunc } from "#moves/move";
|
|
import { NumberHolder } from "#utils/common";
|
|
import { areAllies } from "#utils/pokemon-utils";
|
|
|
|
/**
|
|
* Return whether the move targets the field
|
|
*
|
|
* Examples include
|
|
* - Hazard moves like spikes
|
|
* - Weather moves like rain dance
|
|
* - User side moves like reflect and safeguard
|
|
*/
|
|
export function isFieldTargeted(move: Move): boolean {
|
|
switch (move.moveTarget) {
|
|
case MoveTarget.BOTH_SIDES:
|
|
case MoveTarget.USER_SIDE:
|
|
case MoveTarget.ENEMY_SIDE:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determine whether a move is a spread move.
|
|
*
|
|
* @param move - The {@linkcode Move} to check
|
|
* @returns Whether {@linkcode move} is spread-targeted.
|
|
* @remarks
|
|
* Examples include:
|
|
* - Moves targeting all adjacent Pokemon (like Surf)
|
|
* - Moves targeting all adjacent enemies (like Air Cutter)
|
|
*/
|
|
|
|
export function isSpreadMove(move: Move): boolean {
|
|
switch (move.moveTarget) {
|
|
case MoveTarget.ALL_ENEMIES:
|
|
case MoveTarget.ALL_NEAR_ENEMIES:
|
|
case MoveTarget.ALL_OTHERS:
|
|
case MoveTarget.ALL_NEAR_OTHERS:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet {
|
|
const variableTarget = new NumberHolder(0);
|
|
user.getOpponents(false).forEach(p => applyMoveAttrs("VariableTargetAttr", user, p, allMoves[move], variableTarget));
|
|
|
|
let moveTarget: MoveTarget | undefined;
|
|
if (allMoves[move].hasAttr("VariableTargetAttr")) {
|
|
moveTarget = variableTarget.value;
|
|
} else if (replaceTarget !== undefined) {
|
|
moveTarget = replaceTarget;
|
|
} else if (move) {
|
|
moveTarget = allMoves[move].moveTarget;
|
|
} else if (move === undefined) {
|
|
moveTarget = MoveTarget.NEAR_ENEMY;
|
|
}
|
|
const opponents = user.getOpponents(false);
|
|
|
|
let set: Pokemon[] = [];
|
|
let multiple = false;
|
|
const ally: Pokemon | undefined = user.getAlly();
|
|
|
|
switch (moveTarget) {
|
|
case MoveTarget.USER:
|
|
case MoveTarget.PARTY:
|
|
set = [user];
|
|
break;
|
|
case MoveTarget.NEAR_OTHER:
|
|
case MoveTarget.OTHER:
|
|
case MoveTarget.ALL_NEAR_OTHERS:
|
|
case MoveTarget.ALL_OTHERS:
|
|
set = ally != null ? opponents.concat([ally]) : opponents;
|
|
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS;
|
|
break;
|
|
case MoveTarget.NEAR_ENEMY:
|
|
case MoveTarget.ALL_NEAR_ENEMIES:
|
|
case MoveTarget.ALL_ENEMIES:
|
|
case MoveTarget.ENEMY_SIDE:
|
|
set = opponents;
|
|
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
|
|
break;
|
|
case MoveTarget.RANDOM_NEAR_ENEMY:
|
|
set = [opponents[user.randBattleSeedInt(opponents.length)]];
|
|
break;
|
|
case MoveTarget.ATTACKER:
|
|
return { targets: [-1 as BattlerIndex], multiple: false };
|
|
case MoveTarget.NEAR_ALLY:
|
|
case MoveTarget.ALLY:
|
|
set = ally != null ? [ally] : [];
|
|
break;
|
|
case MoveTarget.USER_OR_NEAR_ALLY:
|
|
case MoveTarget.USER_AND_ALLIES:
|
|
case MoveTarget.USER_SIDE:
|
|
set = ally != null ? [user, ally] : [user];
|
|
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
|
|
break;
|
|
case MoveTarget.ALL:
|
|
case MoveTarget.BOTH_SIDES:
|
|
set = (ally != null ? [user, ally] : [user]).concat(opponents);
|
|
multiple = true;
|
|
break;
|
|
case MoveTarget.CURSE:
|
|
{
|
|
const extraTargets = ally != null ? [ally] : [];
|
|
set = user.getTypes(true).includes(PokemonType.GHOST) ? opponents.concat(extraTargets) : [user];
|
|
}
|
|
break;
|
|
}
|
|
|
|
return {
|
|
targets: set
|
|
.filter(p => p?.isActive(true))
|
|
.map(p => p.getBattlerIndex())
|
|
.filter(t => t !== undefined),
|
|
multiple,
|
|
};
|
|
}
|
|
|
|
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
|
|
while (user.getMoveQueue().length > 0 && user.getMoveQueue()[0].move === move.id) {
|
|
user.getMoveQueue().shift();
|
|
}
|
|
user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Determine the target for the `user`'s counter-attack move
|
|
* @param user - The pokemon using the counter-like move
|
|
* @param damageCategory - The category of move to counter (physical or special), or `undefined` to counter both
|
|
* @returns - The battler index of the most recent, non-ally attacker using a move that matches the specified category, or `null` if no such attacker exists
|
|
*/
|
|
export function getCounterAttackTarget(user: Pokemon, damageCategory?: MoveDamageCategory): BattlerIndex | null {
|
|
for (const attackRecord of user.turnData.attacksReceived) {
|
|
// check if the attacker was an ally
|
|
const moveCategory = allMoves[attackRecord.move].category;
|
|
const sourceBattlerIndex = attackRecord.sourceBattlerIndex;
|
|
if (
|
|
moveCategory !== MoveCategory.STATUS
|
|
&& !areAllies(sourceBattlerIndex, user.getBattlerIndex())
|
|
&& (damageCategory === undefined || moveCategory === damageCategory)
|
|
) {
|
|
return sourceBattlerIndex;
|
|
}
|
|
}
|
|
return null;
|
|
}
|