mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-14 11:29:28 +02:00
Moved methods around to be in sequential order
This commit is contained in:
parent
b2d10b7006
commit
6cfb26c528
@ -87,110 +87,11 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
this.hitChecks = Array(this.targets.length).fill([HitCheckResult.PENDING, 0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute targets and the results of hit checks of the invoked move against all targets,
|
||||
* organized by battler index.
|
||||
*
|
||||
* **This is *not* a pure function**; it has the following side effects
|
||||
* - `this.hitChecks` - The results of the hit checks against each target
|
||||
* - `this.moveHistoryEntry` - Sets success or failure based on the hit check results
|
||||
* - user.turnData.hitCount and user.turnData.hitsLeft - Both set to 1 if the
|
||||
* move was unsuccessful against all targets
|
||||
*
|
||||
* @returns The targets of the invoked move
|
||||
* @see {@linkcode hitCheck}
|
||||
*/
|
||||
private conductHitChecks(user: Pokemon, fieldMove: boolean): Pokemon[] {
|
||||
/** All Pokemon targeted by this phase's invoked move */
|
||||
/** Whether any hit check ended in a success */
|
||||
let anySuccess = false;
|
||||
/** Whether the attack missed all of its targets */
|
||||
let allMiss = true;
|
||||
|
||||
let targets = this.getTargets();
|
||||
|
||||
// For field targeted moves, we only look for the first target that may magic bounce
|
||||
|
||||
for (const [i, target] of targets.entries()) {
|
||||
const hitCheck = this.hitCheck(target);
|
||||
// If the move bounced and was a field targeted move,
|
||||
// then immediately stop processing other targets
|
||||
if (fieldMove && hitCheck[0] === HitCheckResult.REFLECTED) {
|
||||
targets = [target];
|
||||
this.hitChecks = [hitCheck];
|
||||
break;
|
||||
}
|
||||
if (hitCheck[0] === HitCheckResult.HIT) {
|
||||
anySuccess = true;
|
||||
} else {
|
||||
allMiss ||= hitCheck[0] === HitCheckResult.MISS;
|
||||
}
|
||||
this.hitChecks[i] = hitCheck;
|
||||
}
|
||||
|
||||
if (anySuccess) {
|
||||
this.moveHistoryEntry.result = MoveResult.SUCCESS;
|
||||
} else {
|
||||
user.turnData.hitCount = 1;
|
||||
user.turnData.hitsLeft = 1;
|
||||
this.moveHistoryEntry.result = allMiss ? MoveResult.MISS : MoveResult.FAIL;
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the move to each of the resolved targets.
|
||||
* @param targets - The resolved set of targets of the move
|
||||
* @throws Error if there was an unexpected hit check result
|
||||
*/
|
||||
private applyToTargets(user: Pokemon, targets: Pokemon[]): void {
|
||||
let firstHit = true;
|
||||
for (const [i, target] of targets.entries()) {
|
||||
const [hitCheckResult, effectiveness] = this.hitChecks[i];
|
||||
switch (hitCheckResult) {
|
||||
case HitCheckResult.HIT:
|
||||
this.applyMoveEffects(target, effectiveness, firstHit);
|
||||
firstHit = false;
|
||||
if (isFieldTargeted(this.move)) {
|
||||
// Stop processing other targets if the move is a field move
|
||||
return;
|
||||
}
|
||||
break;
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: The fallthrough is intentional
|
||||
case HitCheckResult.NO_EFFECT:
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t(this.move.id === MoveId.SHEER_COLD ? "battle:hitResultImmune" : "battle:hitResultNoEffect", {
|
||||
pokemonName: getPokemonNameWithAffix(target),
|
||||
}),
|
||||
);
|
||||
case HitCheckResult.NO_EFFECT_NO_MESSAGE:
|
||||
case HitCheckResult.PROTECTED:
|
||||
case HitCheckResult.TARGET_NOT_ON_FIELD:
|
||||
applyMoveAttrs("NoEffectAttr", user, target, this.move);
|
||||
break;
|
||||
case HitCheckResult.MISS:
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }),
|
||||
);
|
||||
applyMoveAttrs("MissEffectAttr", user, target, this.move);
|
||||
break;
|
||||
case HitCheckResult.REFLECTED:
|
||||
globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MoveReflectPhase", target, user, this.move);
|
||||
break;
|
||||
case HitCheckResult.PENDING:
|
||||
case HitCheckResult.ERROR:
|
||||
throw new Error("Unexpected hit check result");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override start(): void {
|
||||
super.start();
|
||||
|
||||
/** The Pokemon using this phase's invoked move */
|
||||
const user = this.getUserPokemon();
|
||||
|
||||
if (!user) {
|
||||
super.end();
|
||||
return;
|
||||
@ -285,6 +186,58 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
this.postAnimCallback(user, targets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute targets and the results of hit checks of the invoked move against all targets,
|
||||
* organized by battler index.
|
||||
*
|
||||
* **This is *not* a pure function**; it has the following side effects
|
||||
* - `this.hitChecks` - The results of the hit checks against each target
|
||||
* - `this.moveHistoryEntry` - Sets success or failure based on the hit check results
|
||||
* - user.turnData.hitCount and user.turnData.hitsLeft - Both set to 1 if the
|
||||
* move was unsuccessful against all targets
|
||||
*
|
||||
* @returns The targets of the invoked move
|
||||
* @see {@linkcode hitCheck}
|
||||
*/
|
||||
private conductHitChecks(user: Pokemon, fieldMove: boolean): Pokemon[] {
|
||||
/** All Pokemon targeted by this phase's invoked move */
|
||||
/** Whether any hit check ended in a success */
|
||||
let anySuccess = false;
|
||||
/** Whether the attack missed all of its targets */
|
||||
let allMiss = true;
|
||||
|
||||
let targets = this.getTargets();
|
||||
|
||||
// For field targeted moves, we only look for the first target that may magic bounce
|
||||
|
||||
for (const [i, target] of targets.entries()) {
|
||||
const hitCheck = this.hitCheck(target);
|
||||
// If the move bounced and was a field targeted move,
|
||||
// then immediately stop processing other targets
|
||||
if (fieldMove && hitCheck[0] === HitCheckResult.REFLECTED) {
|
||||
targets = [target];
|
||||
this.hitChecks = [hitCheck];
|
||||
break;
|
||||
}
|
||||
if (hitCheck[0] === HitCheckResult.HIT) {
|
||||
anySuccess = true;
|
||||
} else {
|
||||
allMiss ||= hitCheck[0] === HitCheckResult.MISS;
|
||||
}
|
||||
this.hitChecks[i] = hitCheck;
|
||||
}
|
||||
|
||||
if (anySuccess) {
|
||||
this.moveHistoryEntry.result = MoveResult.SUCCESS;
|
||||
} else {
|
||||
user.turnData.hitCount = 1;
|
||||
user.turnData.hitsLeft = 1;
|
||||
this.moveHistoryEntry.result = allMiss ? MoveResult.MISS : MoveResult.FAIL;
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to be called after the move animation is played
|
||||
*/
|
||||
@ -316,121 +269,52 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
this.end();
|
||||
}
|
||||
|
||||
public override end(): void {
|
||||
const user = this.getUserPokemon();
|
||||
if (!user) {
|
||||
super.end();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this phase isn't for the invoked move's last strike (and we still have something to hit),
|
||||
* unshift another MoveEffectPhase for the next strike before ending this phase.
|
||||
*/
|
||||
if (--user.turnData.hitsLeft >= 1 && this.getFirstTarget()) {
|
||||
this.addNextHitPhase();
|
||||
super.end();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* All hits of the move have resolved by now.
|
||||
* Queue message for multi-strike moves before applying Shell Bell heals & proccing Dancer-like effects.
|
||||
*/
|
||||
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
|
||||
if (hitsTotal > 1 || user.turnData.hitsLeft > 0) {
|
||||
// Queue message if multiple hits occurred or were slated to occur (such as a Triple Axel miss)
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal }));
|
||||
}
|
||||
|
||||
globalScene.applyModifiers(HitHealModifier, this.player, user);
|
||||
this.getTargets().forEach(target => {
|
||||
target.turnData.moveEffectiveness = null;
|
||||
});
|
||||
super.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies reactive effects that occur when a Pokémon is hit.
|
||||
* (i.e. Effect Spore, Disguise, Liquid Ooze, Beak Blast)
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||
* @param wasCritical - `true` if the move was a critical hit
|
||||
* Apply the move to each of the resolved targets.
|
||||
* @param targets - The resolved set of targets of the move
|
||||
* @throws Error if there was an unexpected hit check result
|
||||
*/
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, wasCritical = false): void {
|
||||
const params = { pokemon: target, opponent: user, move: this.move, hitResult };
|
||||
applyAbAttrs("PostDefendAbAttr", params);
|
||||
|
||||
if (wasCritical) {
|
||||
applyAbAttrs("PostReceiveCritStatStageChangeAbAttr", params);
|
||||
}
|
||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles checking for and applying Flinches
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||
* @param dealsDamage - `true` if the attempted move successfully dealt damage
|
||||
*/
|
||||
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void {
|
||||
if (this.move.hasAttr("FlinchAttr")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
dealsDamage &&
|
||||
!target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") &&
|
||||
!this.move.hitsSubstitute(user, target)
|
||||
) {
|
||||
const flinched = new BooleanHolder(false);
|
||||
globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
|
||||
if (flinched.value) {
|
||||
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.id, user.id);
|
||||
private applyToTargets(user: Pokemon, targets: Pokemon[]): void {
|
||||
let firstHit = true;
|
||||
for (const [i, target] of targets.entries()) {
|
||||
const [hitCheckResult, effectiveness] = this.hitChecks[i];
|
||||
switch (hitCheckResult) {
|
||||
case HitCheckResult.HIT:
|
||||
this.applyMoveEffects(target, effectiveness, firstHit);
|
||||
firstHit = false;
|
||||
if (isFieldTargeted(this.move)) {
|
||||
// Stop processing other targets if the move is a field move
|
||||
return;
|
||||
}
|
||||
break;
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: The fallthrough is intentional
|
||||
case HitCheckResult.NO_EFFECT:
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t(this.move.id === MoveId.SHEER_COLD ? "battle:hitResultImmune" : "battle:hitResultNoEffect", {
|
||||
pokemonName: getPokemonNameWithAffix(target),
|
||||
}),
|
||||
);
|
||||
case HitCheckResult.NO_EFFECT_NO_MESSAGE:
|
||||
case HitCheckResult.PROTECTED:
|
||||
case HitCheckResult.TARGET_NOT_ON_FIELD:
|
||||
applyMoveAttrs("NoEffectAttr", user, target, this.move);
|
||||
break;
|
||||
case HitCheckResult.MISS:
|
||||
globalScene.phaseManager.queueMessage(
|
||||
i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }),
|
||||
);
|
||||
applyMoveAttrs("MissEffectAttr", user, target, this.move);
|
||||
break;
|
||||
case HitCheckResult.REFLECTED:
|
||||
globalScene.phaseManager.appendNewToPhase("MoveEndPhase", "MoveReflectPhase", target, user, this.move);
|
||||
break;
|
||||
case HitCheckResult.PENDING:
|
||||
case HitCheckResult.ERROR:
|
||||
throw new Error("Unexpected hit check result");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Return whether the target is protected by protect or a relevant conditional protection
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - {@linkcode Pokemon} the target to check for protection
|
||||
* @param move - The {@linkcode Move} being used
|
||||
* @returns Whether the pokemon was protected
|
||||
*/
|
||||
private protectedCheck(user: Pokemon, target: Pokemon): boolean {
|
||||
/** The {@linkcode ArenaTagSide} to which the target belongs */
|
||||
const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
/** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */
|
||||
const hasConditionalProtectApplied = new BooleanHolder(false);
|
||||
/** Does the applied conditional protection bypass Protect-ignoring effects? */
|
||||
const bypassIgnoreProtect = new BooleanHolder(false);
|
||||
/** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */
|
||||
if (!this.move.isAllyTarget()) {
|
||||
globalScene.arena.applyTagsForSide(
|
||||
ConditionalProtectTag,
|
||||
targetSide,
|
||||
false,
|
||||
hasConditionalProtectApplied,
|
||||
user,
|
||||
target,
|
||||
this.move.id,
|
||||
bypassIgnoreProtect,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Break up this chunky boolean to make it more palatable
|
||||
return (
|
||||
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.moveTarget) &&
|
||||
(bypassIgnoreProtect.value || !this.move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) &&
|
||||
(hasConditionalProtectApplied.value ||
|
||||
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
|
||||
target.findTags(t => t instanceof ProtectedTag).some(t => target.lapseTag(t.tagType))) ||
|
||||
(this.move.category !== MoveCategory.STATUS &&
|
||||
target.findTags(t => t instanceof DamageProtectedTag).some(t => target.lapseTag(t.tagType))))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conduct the hit check and type effectiveness for this move against the target
|
||||
*
|
||||
@ -451,10 +335,6 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
const user = this.getUserPokemon();
|
||||
const move = this.move;
|
||||
|
||||
if (!user) {
|
||||
return [HitCheckResult.ERROR, 0];
|
||||
}
|
||||
|
||||
// Moves targeting the user bypass all checks
|
||||
if (move.moveTarget === MoveTarget.USER) {
|
||||
return [HitCheckResult.HIT, 1];
|
||||
@ -487,8 +367,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
return [HitCheckResult.PROTECTED, 0];
|
||||
}
|
||||
|
||||
// Check for magic bounce
|
||||
if (isMoveReflectableBy(move, target, this.useMode)) {
|
||||
// Reflected moves cannot be reflected again
|
||||
if (isMoveReflectableBy(this.move, target, this.useMode)) {
|
||||
return [HitCheckResult.REFLECTED, 0];
|
||||
}
|
||||
|
||||
@ -559,9 +439,6 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
*/
|
||||
public checkBypassAccAndInvuln(target: Pokemon) {
|
||||
const user = this.getUserPokemon();
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
if (user.hasAbilityWithAttr("AlwaysHitAbAttr") || target.hasAbilityWithAttr("AlwaysHitAbAttr")) {
|
||||
return true;
|
||||
}
|
||||
@ -593,79 +470,43 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
return move.getAttrs("HitsTagAttr").some(hta => hta.tagType === semiInvulnerableTag.tagType);
|
||||
}
|
||||
|
||||
/** @returns The {@linkcode Pokemon} using this phase's invoked move */
|
||||
public getUserPokemon(): Pokemon | null {
|
||||
// TODO: Make this purely a battler index
|
||||
if (this.battlerIndex > BattlerIndex.ENEMY_2) {
|
||||
return globalScene.getPokemonById(this.battlerIndex);
|
||||
}
|
||||
return (this.player ? globalScene.getPlayerField() : globalScene.getEnemyField())[this.fieldIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns An array of {@linkcode Pokemon} that are:
|
||||
* - On-field and active
|
||||
* - Non-fainted
|
||||
* - Targeted by this phase's invoked move
|
||||
* Check whether the target is protected by protect or a relevant conditional protection.
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - The target {@linkcode Pokemon} to check for protection
|
||||
* @returns Whether the target was protected
|
||||
*/
|
||||
public getTargets(): Pokemon[] {
|
||||
return globalScene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1);
|
||||
}
|
||||
|
||||
/** @returns The first active, non-fainted target of this phase's invoked move. */
|
||||
public getFirstTarget(): Pokemon | undefined {
|
||||
return this.getTargets()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given {@linkcode Pokemon} from this phase's target list
|
||||
* @param target - The {@linkcode Pokemon} to be removed
|
||||
*/
|
||||
protected removeTarget(target: Pokemon): void {
|
||||
const targetIndex = this.targets.indexOf(target.getBattlerIndex());
|
||||
if (targetIndex !== -1) {
|
||||
this.targets.splice(this.targets.indexOf(target.getBattlerIndex()), 1);
|
||||
private protectedCheck(user: Pokemon, target: Pokemon): boolean {
|
||||
/** The {@linkcode ArenaTagSide} to which the target belongs */
|
||||
const targetSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
/** Has the invoked move been cancelled by conditional protection (e.g Quick Guard)? */
|
||||
const hasConditionalProtectApplied = new BooleanHolder(false);
|
||||
/** Does the applied conditional protection bypass Protect-ignoring effects? */
|
||||
const bypassIgnoreProtect = new BooleanHolder(false);
|
||||
/** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */
|
||||
if (!this.move.isAllyTarget()) {
|
||||
globalScene.arena.applyTagsForSide(
|
||||
ConditionalProtectTag,
|
||||
targetSide,
|
||||
false,
|
||||
hasConditionalProtectApplied,
|
||||
user,
|
||||
target,
|
||||
this.move.id,
|
||||
bypassIgnoreProtect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents subsequent strikes of this phase's invoked move from occurring
|
||||
* @param target - If defined, only stop subsequent strikes against this {@linkcode Pokemon}
|
||||
*/
|
||||
public stopMultiHit(target?: Pokemon): void {
|
||||
// If given a specific target, remove the target from subsequent strikes
|
||||
if (target) {
|
||||
this.removeTarget(target);
|
||||
}
|
||||
const user = this.getUserPokemon();
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
// If no target specified, or the specified target was the last of this move's
|
||||
// targets, completely cancel all subsequent strikes.
|
||||
if (!target || this.targets.length === 0) {
|
||||
user.turnData.hitCount = 1;
|
||||
user.turnData.hitsLeft = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshifts a new `MoveEffectPhase` with the same properties as this phase.
|
||||
* Used to queue the next hit of multi-strike moves.
|
||||
*/
|
||||
protected addNextHitPhase(): void {
|
||||
globalScene.phaseManager.unshiftNew("MoveEffectPhase", this.battlerIndex, this.targets, this.move, this.useMode);
|
||||
}
|
||||
|
||||
/** Removes all substitutes that were broken by this phase's invoked move */
|
||||
protected updateSubstitutes(): void {
|
||||
const targets = this.getTargets();
|
||||
for (const target of targets) {
|
||||
const substitute = target.getTag(SubstituteTag);
|
||||
if (substitute && substitute.hp <= 0) {
|
||||
target.lapseTag(BattlerTagType.SUBSTITUTE);
|
||||
}
|
||||
}
|
||||
// TODO: Break up this chunky boolean to make it more palatable
|
||||
return (
|
||||
![MoveTarget.ENEMY_SIDE, MoveTarget.BOTH_SIDES].includes(this.move.moveTarget) &&
|
||||
(bypassIgnoreProtect.value || !this.move.doesFlagEffectApply({ flag: MoveFlags.IGNORE_PROTECT, user, target })) &&
|
||||
(hasConditionalProtectApplied.value ||
|
||||
(!target.findTags(t => t instanceof DamageProtectedTag).length &&
|
||||
target.findTags(t => t instanceof ProtectedTag).some(t => target.lapseTag(t.tagType))) ||
|
||||
(this.move.category !== MoveCategory.STATUS &&
|
||||
target.findTags(t => t instanceof DamageProtectedTag).some(t => target.lapseTag(t.tagType))))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -713,9 +554,6 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
*/
|
||||
protected applyMoveEffects(target: Pokemon, effectiveness: TypeDamageMultiplier, firstTarget: boolean): void {
|
||||
const user = this.getUserPokemon();
|
||||
if (isNullOrUndefined(user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
|
||||
|
||||
@ -739,7 +577,33 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method of for {@linkcode applyMoveEffects} that applies damage to the target.
|
||||
* Apply the result of this phase's move to the given target
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - The {@linkcode Pokemon} struck by the move
|
||||
* @param effectiveness - The effectiveness of the move against the target
|
||||
*/
|
||||
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
||||
const moveCategory = user.getMoveCategory(target, this.move);
|
||||
|
||||
if (moveCategory === MoveCategory.STATUS) {
|
||||
return [HitResult.STATUS, false];
|
||||
}
|
||||
|
||||
const result = this.applyMoveDamage(user, target, effectiveness);
|
||||
|
||||
if (user.turnData.hitsLeft === 1 || target.isFainted()) {
|
||||
this.queueHitResultMessage(result[0]);
|
||||
}
|
||||
|
||||
if (target.isFainted()) {
|
||||
this.onFaintTarget(user, target);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method of {@linkcode applyMove} that applies damage to the target.
|
||||
*
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
@ -837,6 +701,29 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
return [result, isCritical];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method of {@linkcode applyMove} that queues the hit-result message
|
||||
* on the final strike of the move against a target
|
||||
* @param result - The {@linkcode HitResult} of the move
|
||||
*/
|
||||
protected queueHitResultMessage(result: HitResult) {
|
||||
let msg: string | undefined;
|
||||
switch (result) {
|
||||
case HitResult.SUPER_EFFECTIVE:
|
||||
msg = i18next.t("battle:hitResultSuperEffective");
|
||||
break;
|
||||
case HitResult.NOT_VERY_EFFECTIVE:
|
||||
msg = i18next.t("battle:hitResultNotVeryEffective");
|
||||
break;
|
||||
case HitResult.ONE_HIT_KO:
|
||||
msg = i18next.t("battle:hitResultOneHitKO");
|
||||
break;
|
||||
}
|
||||
if (msg) {
|
||||
globalScene.phaseManager.queueMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method of {@linkcode applyMove} that handles the event of a target fainting.
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
@ -861,55 +748,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method of {@linkcode applyMove} that queues the hit-result message
|
||||
* on the final strike of the move against a target
|
||||
* @param result - The {@linkcode HitResult} of the move
|
||||
*/
|
||||
protected queueHitResultMessage(result: HitResult) {
|
||||
let msg: string | undefined;
|
||||
switch (result) {
|
||||
case HitResult.SUPER_EFFECTIVE:
|
||||
msg = i18next.t("battle:hitResultSuperEffective");
|
||||
break;
|
||||
case HitResult.NOT_VERY_EFFECTIVE:
|
||||
msg = i18next.t("battle:hitResultNotVeryEffective");
|
||||
break;
|
||||
case HitResult.ONE_HIT_KO:
|
||||
msg = i18next.t("battle:hitResultOneHitKO");
|
||||
break;
|
||||
}
|
||||
if (msg) {
|
||||
globalScene.phaseManager.queueMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/** Apply the result of this phase's move to the given target
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - The {@linkcode Pokemon} struck by the move
|
||||
* @param effectiveness - The effectiveness of the move against the target
|
||||
*/
|
||||
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
||||
const moveCategory = user.getMoveCategory(target, this.move);
|
||||
|
||||
if (moveCategory === MoveCategory.STATUS) {
|
||||
return [HitResult.STATUS, false];
|
||||
}
|
||||
|
||||
const result = this.applyMoveDamage(user, target, effectiveness);
|
||||
|
||||
if (user.turnData.hitsLeft === 1 || target.isFainted()) {
|
||||
this.queueHitResultMessage(result[0]);
|
||||
}
|
||||
|
||||
if (target.isFainted()) {
|
||||
this.onFaintTarget(user, target);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all effects aimed at the move's target.
|
||||
* Sub-method of {@linkcode applyMovetEffects} that applies all effects aimed at the move's target.
|
||||
* To be used when the target is successfully and directly hit by the move.
|
||||
* @param user - The {@linkcode Pokemon} using the move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
@ -947,4 +786,152 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method of {@linkcode applyOnTargetEffects} that applies reactive effects that occur when a Pokémon is hit.
|
||||
* (i.e. Effect Spore, Disguise, Liquid Ooze, Beak Blast)
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||
* @param wasCritical - `true` if the move was a critical hit
|
||||
*/
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, wasCritical = false): void {
|
||||
const params = { pokemon: target, opponent: user, move: this.move, hitResult };
|
||||
applyAbAttrs("PostDefendAbAttr", params);
|
||||
|
||||
if (wasCritical) {
|
||||
applyAbAttrs("PostReceiveCritStatStageChangeAbAttr", params);
|
||||
}
|
||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-method of {@linkcode applyOnTargetEffects} that handles checking for and applying flinches.
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||
* @param dealsDamage - `true` if the attempted move successfully dealt damage
|
||||
*/
|
||||
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void {
|
||||
if (this.move.hasAttr("FlinchAttr")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
dealsDamage &&
|
||||
!target.hasAbilityWithAttr("IgnoreMoveEffectsAbAttr") &&
|
||||
!this.move.hitsSubstitute(user, target)
|
||||
) {
|
||||
const flinched = new BooleanHolder(false);
|
||||
globalScene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
|
||||
if (flinched.value) {
|
||||
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.id, user.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override end(): void {
|
||||
const user = this.getUserPokemon();
|
||||
|
||||
/**
|
||||
* If this phase isn't for the invoked move's last strike (and we still have something to hit),
|
||||
* unshift another MoveEffectPhase for the next strike before ending this phase.
|
||||
*/
|
||||
if (--user.turnData.hitsLeft >= 1 && this.getFirstTarget()) {
|
||||
this.addNextHitPhase();
|
||||
super.end();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* All hits of the move have resolved by now.
|
||||
* Queue message for multi-strike moves before applying Shell Bell heals & proccing Dancer-like effects.
|
||||
*/
|
||||
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
|
||||
if (hitsTotal > 1 || user.turnData.hitsLeft > 0) {
|
||||
// Queue message if multiple hits occurred or were slated to occur (such as a Triple Axel miss)
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:attackHitsCount", { count: hitsTotal }));
|
||||
}
|
||||
|
||||
globalScene.applyModifiers(HitHealModifier, this.player, user);
|
||||
this.getTargets().forEach(target => {
|
||||
target.turnData.moveEffectiveness = null;
|
||||
});
|
||||
super.end();
|
||||
}
|
||||
|
||||
// #region Helpers
|
||||
|
||||
/**
|
||||
* @returns The {@linkcode Pokemon} using this phase's invoked move.
|
||||
* Is never null during the move execution itself, as {@linkcode start} ends the phase immediately if a source is missing.
|
||||
* @todo Delete in favor of {@linkcode PokemonPhase.getPokemon}
|
||||
*/
|
||||
public getUserPokemon(): Pokemon {
|
||||
return super.getPokemon()!;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns An array of {@linkcode Pokemon} that are:
|
||||
* - On-field and active
|
||||
* - Non-fainted
|
||||
* - Targeted by this phase's invoked move
|
||||
*/
|
||||
public getTargets(): Pokemon[] {
|
||||
return globalScene.getField(true).filter(p => this.targets.indexOf(p.getBattlerIndex()) > -1);
|
||||
}
|
||||
|
||||
/** @returns The first active, non-fainted target of this phase's invoked move. */
|
||||
public getFirstTarget(): Pokemon | undefined {
|
||||
return this.getTargets()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given {@linkcode Pokemon} from this phase's target list
|
||||
* @param target - The {@linkcode Pokemon} to be removed
|
||||
*/
|
||||
protected removeTarget(target: Pokemon): void {
|
||||
const targetIndex = this.targets.indexOf(target.getBattlerIndex());
|
||||
if (targetIndex !== -1) {
|
||||
this.targets.splice(this.targets.indexOf(target.getBattlerIndex()), 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents subsequent strikes of this phase's invoked move from occurring
|
||||
* @param target - If defined, only stop subsequent strikes against this {@linkcode Pokemon}
|
||||
*/
|
||||
public stopMultiHit(target?: Pokemon): void {
|
||||
// If given a specific target, remove the target from subsequent strikes
|
||||
if (target) {
|
||||
this.removeTarget(target);
|
||||
}
|
||||
const user = this.getUserPokemon();
|
||||
// If no target specified, or the specified target was the last of this move's
|
||||
// targets, completely cancel all subsequent strikes.
|
||||
if (!target || this.targets.length === 0) {
|
||||
user.turnData.hitCount = 1;
|
||||
user.turnData.hitsLeft = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshifts a new `MoveEffectPhase` with the same properties as this phase.
|
||||
* Used to queue the next hit of multi-strike moves.
|
||||
*/
|
||||
protected addNextHitPhase(): void {
|
||||
globalScene.phaseManager.unshiftNew("MoveEffectPhase", this.battlerIndex, this.targets, this.move, this.useMode);
|
||||
}
|
||||
|
||||
/** Removes all substitutes that were broken by this phase's invoked move */
|
||||
protected updateSubstitutes(): void {
|
||||
const targets = this.getTargets();
|
||||
for (const target of targets) {
|
||||
const substitute = target.getTag(SubstituteTag);
|
||||
if (substitute && substitute.hp <= 0) {
|
||||
target.lapseTag(BattlerTagType.SUBSTITUTE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// # endregion Helpers
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ export class MoveReflectPhase extends Phase {
|
||||
}
|
||||
|
||||
override start(): void {
|
||||
this.pokemon.turnData.extraTurns++;
|
||||
// Magic Coat takes precedeence over Magic Bounce if both apply at once
|
||||
const magicCoatTag = this.pokemon.getTag(BattlerTagType.MAGIC_COAT) as MagicCoatTag | undefined;
|
||||
if (magicCoatTag) {
|
||||
|
Loading…
Reference in New Issue
Block a user