Restore move history entry for charge phases

This commit is contained in:
innerthunder 2024-10-06 18:05:16 -07:00
parent 97806d7d4d
commit 185f7f2656
4 changed files with 88 additions and 84 deletions

View File

@ -6132,6 +6132,10 @@ const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
return false; return false;
} }
if (allMoves[copiableMove.move].isChargingMove() && copiableMove.result === MoveResult.OTHER) {
return false;
}
// TODO: Add last turn of Bide // TODO: Add last turn of Bide
return true; return true;

View File

@ -2,7 +2,7 @@ import BattleScene from "#app/battle-scene";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { MoveChargeAnim } from "#app/data/battle-anims"; import { MoveChargeAnim } from "#app/data/battle-anims";
import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/move"; import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/move";
import Pokemon, { PokemonMove } from "#app/field/pokemon"; import Pokemon, { MoveResult, PokemonMove } from "#app/field/pokemon";
import { BooleanHolder } from "#app/utils"; import { BooleanHolder } from "#app/utils";
import { MovePhase } from "#app/phases/move-phase"; import { MovePhase } from "#app/phases/move-phase";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";
@ -26,13 +26,10 @@ export class MoveChargePhase extends PokemonPhase {
const user = this.getUserPokemon(); const user = this.getUserPokemon();
const target = this.getTargetPokemon(); const target = this.getTargetPokemon();
if (!target) {
return this.end();
}
const move = this.move.getMove(); const move = this.move.getMove();
if (!(move.isChargingMove())) {
return this.end(); if (!target || !(move.isChargingMove())) {
return super.end();
} }
new MoveChargeAnim(move.chargeAnim, move.id, user).play(this.scene, false, () => { new MoveChargeAnim(move.chargeAnim, move.id, user).play(this.scene, false, () => {
@ -61,6 +58,9 @@ export class MoveChargePhase extends PokemonPhase {
} else { } else {
user.getMoveQueue().push({ move: move.id, targets: [ this.targetIndex ]}); user.getMoveQueue().push({ move: move.id, targets: [ this.targetIndex ]});
} }
// Add this move's charging phase to the user's move history
user.pushMoveHistory({ move: this.move.moveId, targets: [ this.targetIndex ], result: MoveResult.OTHER });
} }
super.end(); super.end();
} }

View File

@ -24,10 +24,10 @@ export class MoveEffectPhase extends PokemonPhase {
super(scene, battlerIndex); super(scene, battlerIndex);
this.move = move; this.move = move;
/** /**
* In double battles, if the right Pokemon selects a spread move and the left Pokemon dies * In double battles, if the right Pokemon selects a spread move and the left Pokemon dies
* with no party members available to switch in, then the right Pokemon takes the index * with no party members available to switch in, then the right Pokemon takes the index
* of the left Pokemon and gets hit unless this is checked. * of the left Pokemon and gets hit unless this is checked.
*/ */
if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
const i = targets.indexOf(battlerIndex); const i = targets.indexOf(battlerIndex);
targets.splice(i, i + 1); targets.splice(i, i + 1);
@ -49,9 +49,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Does an effect from this move override other effects on this turn? * Does an effect from this move override other effects on this turn?
* e.g. Charging moves (Fly, etc.) on their first turn of use. * e.g. Charging moves (Fly, etc.) on their first turn of use.
*/ */
const overridden = new Utils.BooleanHolder(false); const overridden = new Utils.BooleanHolder(false);
/** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */ /** The {@linkcode Move} object from {@linkcode allMoves} invoked by this phase */
const move = this.move.getMove(); const move = this.move.getMove();
@ -66,10 +66,10 @@ export class MoveEffectPhase extends PokemonPhase {
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT); user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
/** /**
* If this phase is for the first hit of the invoked move, * If this phase is for the first hit of the invoked move,
* resolve the move's total hit count. This block combines the * resolve the move's total hit count. This block combines the
* effects of the move itself, Parental Bond, and Multi-Lens to do so. * effects of the move itself, Parental Bond, and Multi-Lens to do so.
*/ */
if (user.turnData.hitsLeft === -1) { if (user.turnData.hitsLeft === -1) {
const hitCount = new Utils.IntegerHolder(1); const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit // Assume single target for multi hit
@ -86,16 +86,16 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Log to be entered into the user's move history once the move result is resolved. * Log to be entered into the user's move history once the move result is resolved.
* Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully * Note that `result` (a {@linkcode MoveResult}) logs whether the move was successfully
* used in the sense of "Does it have an effect on the user?". * used in the sense of "Does it have an effect on the user?".
*/ */
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual }; const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
/** /**
* Stores results of hit checks of the invoked move against all targets, organized by battler index. * Stores results of hit checks of the invoked move against all targets, organized by battler index.
* @see {@linkcode hitCheck} * @see {@linkcode hitCheck}
*/ */
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ])); const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const hasActiveTargets = targets.some(t => t.isActive(true)); const hasActiveTargets = targets.some(t => t.isActive(true));
@ -103,11 +103,10 @@ export class MoveEffectPhase extends PokemonPhase {
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)); const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
/** /**
* If no targets are left for the move to hit (FAIL), or the invoked move is single-target * If no targets are left for the move to hit (FAIL), or the invoked move is single-target
* (and not random target) and failed the hit check against its target (MISS), log the move * (and not random target) and failed the hit check against its target (MISS), log the move
* as FAILed or MISSed (depending on the conditions above) and end this phase. * as FAILed or MISSed (depending on the conditions above) and end this phase.
*/ */
if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) { if (!hasActiveTargets || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]] && !targets[0].getTag(ProtectedTag) && !isImmune)) {
this.stopMultiHit(); this.stopMultiHit();
if (hasActiveTargets) { if (hasActiveTargets) {
@ -152,9 +151,9 @@ export class MoveEffectPhase extends PokemonPhase {
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move)); const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
/** /**
* If the move missed a target, stop all future hits against that target * If the move missed a target, stop all future hits against that target
* and move on to the next target (if there is one). * and move on to the next target (if there is one).
*/ */
if (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) { if (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()]) {
this.stopMultiHit(target); this.stopMultiHit(target);
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) })); this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
@ -175,23 +174,23 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Since all fail/miss checks have applied, the move is considered successfully applied. * Since all fail/miss checks have applied, the move is considered successfully applied.
* It's worth noting that if the move has no effect or is protected against, this assignment * It's worth noting that if the move has no effect or is protected against, this assignment
* is overwritten and the move is logged as a FAIL. * is overwritten and the move is logged as a FAIL.
*/ */
moveHistoryEntry.result = MoveResult.SUCCESS; moveHistoryEntry.result = MoveResult.SUCCESS;
/** /**
* Stores the result of applying the invoked move to the target. * Stores the result of applying the invoked move to the target.
* If the target is protected, the result is always `NO_EFFECT`. * If the target is protected, the result is always `NO_EFFECT`.
* Otherwise, the hit result is based on type effectiveness, immunities, * Otherwise, the hit result is based on type effectiveness, immunities,
* and other factors that may negate the attack or status application. * and other factors that may negate the attack or status application.
* *
* Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated * Internally, the call to {@linkcode Pokemon.apply} is where damage is calculated
* (for attack moves) and the target's HP is updated. However, this isn't * (for attack moves) and the target's HP is updated. However, this isn't
* made visible to the user until the resulting {@linkcode DamagePhase} * made visible to the user until the resulting {@linkcode DamagePhase}
* is invoked. * is invoked.
*/ */
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT; const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
@ -209,9 +208,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* If the move has no effect on the target (i.e. the target is protected or immune), * If the move has no effect on the target (i.e. the target is protected or immune),
* change the logged move result to FAIL. * change the logged move result to FAIL.
*/ */
if (hitResult === HitResult.NO_EFFECT) { if (hitResult === HitResult.NO_EFFECT) {
moveHistoryEntry.result = MoveResult.FAIL; moveHistoryEntry.result = MoveResult.FAIL;
} }
@ -220,19 +219,19 @@ export class MoveEffectPhase extends PokemonPhase {
const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()); const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive());
/** /**
* If the user can change forms by using the invoked move, * If the user can change forms by using the invoked move,
* it only changes forms after the move's last hit * it only changes forms after the move's last hit
* (see Relic Song's interaction with Parental Bond when used by Meloetta). * (see Relic Song's interaction with Parental Bond when used by Meloetta).
*/ */
if (lastHit) { if (lastHit) {
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
} }
/** /**
* Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs. * Create a Promise that applys *all* effects from the invoked move's MoveEffectAttrs.
* These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger * These are ordered by trigger type (see {@linkcode MoveEffectTrigger}), and each trigger
* type requires different conditions to be met with respect to the move's hit result. * type requires different conditions to be met with respect to the move's hit result.
*/ */
applyAttrs.push(new Promise(resolve => { applyAttrs.push(new Promise(resolve => {
// Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move) // Apply all effects with PRE_MOVE triggers (if the target isn't immune to the move)
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT, applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit) && hitResult !== HitResult.NO_EFFECT,
@ -240,10 +239,10 @@ export class MoveEffectPhase extends PokemonPhase {
// All other effects require the move to not have failed or have been cancelled to trigger // All other effects require the move to not have failed or have been cancelled to trigger
if (hitResult !== HitResult.FAIL) { if (hitResult !== HitResult.FAIL) {
/** /**
* If the invoked move's effects are meant to trigger during the move's "charge turn," * If the invoked move's effects are meant to trigger during the move's "charge turn,"
* ignore all effects after this point. * ignore all effects after this point.
* Otherwise, apply all self-targeted POST_APPLY effects. * Otherwise, apply all self-targeted POST_APPLY effects.
*/ */
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
&& attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move).then(() => { && attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move).then(() => {
// All effects past this point require the move to have hit the target // All effects past this point require the move to have hit the target
@ -252,9 +251,9 @@ export class MoveEffectPhase extends PokemonPhase {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => { && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => {
/** /**
* If the move hit, and the target doesn't have Shield Dust, * If the move hit, and the target doesn't have Shield Dust,
* apply the chance to flinch the target gained from King's Rock * apply the chance to flinch the target gained from King's Rock
*/ */
if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !move.hitsSubstitute(user, target)) { if (dealsDamage && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && !move.hitsSubstitute(user, target)) {
const flinched = new Utils.BooleanHolder(false); const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
@ -284,9 +283,9 @@ export class MoveEffectPhase extends PokemonPhase {
// Apply the user's post-attack ability effects // Apply the user's post-attack ability effects
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => { applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
/** /**
* If the invoked move is an attack, apply the user's chance to * If the invoked move is an attack, apply the user's chance to
* steal an item from the target granted by Grip Claw * steal an item from the target granted by Grip Claw
*/ */
if (this.move.getMove() instanceof AttackMove) { if (this.move.getMove() instanceof AttackMove) {
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
} }
@ -341,12 +340,12 @@ export class MoveEffectPhase extends PokemonPhase {
end() { end() {
const user = this.getUserPokemon(); const user = this.getUserPokemon();
/** /**
* If this phase isn't for the invoked move's last strike, * If this phase isn't for the invoked move's last strike,
* unshift another MoveEffectPhase for the next strike. * unshift another MoveEffectPhase for the next strike.
* Otherwise, queue a message indicating the number of times the move has struck * Otherwise, queue a message indicating the number of times the move has struck
* (if the move has struck more than once), then apply the heal from Shell Bell * (if the move has struck more than once), then apply the heal from Shell Bell
* to the user. * to the user.
*/ */
if (user) { if (user) {
if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) { if (user.turnData.hitsLeft && --user.turnData.hitsLeft >= 1 && this.getTarget()?.isActive()) {
this.scene.unshiftPhase(this.getNewHitPhase()); this.scene.unshiftPhase(this.getNewHitPhase());
@ -441,9 +440,9 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Removes the given {@linkcode Pokemon} from this phase's target list * Removes the given {@linkcode Pokemon} from this phase's target list
* @param target {@linkcode Pokemon} the Pokemon to be removed * @param target {@linkcode Pokemon} the Pokemon to be removed
*/ */
removeTarget(target: Pokemon): void { removeTarget(target: Pokemon): void {
const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex()); const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex());
if (targetIndex !== -1) { if (targetIndex !== -1) {
@ -452,19 +451,19 @@ export class MoveEffectPhase extends PokemonPhase {
} }
/** /**
* Prevents subsequent strikes of this phase's invoked move from occurring * Prevents subsequent strikes of this phase's invoked move from occurring
* @param target {@linkcode Pokemon} if defined, only stop subsequent * @param target {@linkcode Pokemon} if defined, only stop subsequent
* strikes against this Pokemon * strikes against this Pokemon
*/ */
stopMultiHit(target?: Pokemon): void { stopMultiHit(target?: Pokemon): void {
/** If given a specific target, remove the target from subsequent strikes */ /** If given a specific target, remove the target from subsequent strikes */
if (target) { if (target) {
this.removeTarget(target); this.removeTarget(target);
} }
/** /**
* If no target specified, or the specified target was the last of this move's * If no target specified, or the specified target was the last of this move's
* targets, completely cancel all subsequent strikes. * targets, completely cancel all subsequent strikes.
*/ */
if (!target || this.targets.length === 0 ) { if (!target || this.targets.length === 0 ) {
this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here? this.getUserPokemon()!.turnData.hitCount = 1; // TODO: is the bang correct here?
this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here? this.getUserPokemon()!.turnData.hitsLeft = 1; // TODO: is the bang correct here?

View File

@ -303,6 +303,7 @@ export class MovePhase extends BattlePhase {
// Protean and Libero apply on the charging turn of charge moves // Protean and Libero apply on the charging turn of charge moves
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove()); applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
this.showMoveText();
this.scene.unshiftPhase(new MoveChargePhase(this.scene, this.pokemon.getBattlerIndex(), this.targets[0], this.move)); this.scene.unshiftPhase(new MoveChargePhase(this.scene, this.pokemon.getBattlerIndex(), this.targets[0], this.move));
} }