From 80f6b62d1b7732d147e1e5a04873388f8e18ad4f Mon Sep 17 00:00:00 2001 From: arColm Date: Mon, 20 May 2024 08:02:17 +0900 Subject: [PATCH] Implement Revival Blessing (#343) --- src/data/move.ts | 66 ++++++++++++++++++++++++++++++++++++-- src/field/pokemon.ts | 38 +++++++++++++++++++++- src/phases.ts | 4 +++ src/ui/party-ui-handler.ts | 15 +++++++-- 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 59691f39de7..2d27afcbe47 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1,6 +1,6 @@ import { Moves } from "./enums/moves"; import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims"; -import { BattleEndPhase, MoveEffectPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases"; +import { BattleEndPhase, MoveEffectPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases"; import { BattleStat, getBattleStatName } from "./battle-stat"; import { EncoreTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; @@ -3694,6 +3694,67 @@ export class RemoveScreensAttr extends MoveEffectAttr { } } +/** + * Attribute used for Revival Blessing. + * @extends MoveEffectAttr + * @see {@linkcode apply} + */ +export class RevivalBlessingAttr extends MoveEffectAttr { + constructor(user?: boolean) { + super(true); + } + + /** + * + * @param user {@linkcode Pokemon} using this move + * @param target {@linkcode Pokemon} target of this move + * @param move {@linkcode Move} being used + * @param args N/A + * @returns Promise, true if function succeeds. + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise { + return new Promise(resolve => { + // If user is player, checks if the user has fainted pokemon + if(user instanceof PlayerPokemon + && user.scene.getParty().findIndex(p => p.isFainted())>-1) { + (user as PlayerPokemon).revivalBlessing().then(() => { + resolve(true) + }); + // If user is enemy, checks that it is a trainer, and it has fainted non-boss pokemon in party + } else if(user instanceof EnemyPokemon + && user.hasTrainer() + && user.scene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) { + // Selects a random fainted pokemon + const faintedPokemon = user.scene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss()); + const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)]; + const slotIndex = user.scene.getEnemyParty().findIndex(p => pokemon.id === p.id); + pokemon.resetStatus(); + pokemon.heal(Math.min(Math.max(Math.ceil(Math.floor(0.5 * pokemon.getMaxHp())), 1), pokemon.getMaxHp())); + user.scene.queueMessage(`${pokemon.name} was revived!`,0,true); + + if(user.scene.currentBattle.double && user.scene.getEnemyParty().length > 1) { + const allyPokemon = user.getAlly(); + if(slotIndex<=1) { + user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, pokemon.getFieldIndex(), slotIndex, false, false, false)); + } else if(allyPokemon.isFainted()){ + user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, allyPokemon.getFieldIndex(), slotIndex, false, false,false)); + } + } + resolve(true); + } else { + user.scene.queueMessage(`But it failed!`); + resolve(false); + } + }) + } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer { + if(user.hasTrainer() && user.scene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) + return 20; + + return -20; + } +} export class ForceSwitchOutAttr extends MoveEffectAttr { private user: boolean; @@ -7235,7 +7296,8 @@ export function initMoves() { .partial(), new StatusMove(Moves.REVIVAL_BLESSING, Type.NORMAL, -1, 1, -1, 0, 9) .triageMove() - .unimplemented(), + .attr(RevivalBlessingAttr) + .target(MoveTarget.USER), new AttackMove(Moves.SALT_CURE, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 15, -1, 0, 9) .attr(AddBattlerTagAttr, BattlerTagType.SALT_CURED) .makesContact(false), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e8a63635448..8e56d163141 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -17,7 +17,7 @@ import { initMoveAnim, loadMoveAnimAssets } from '../data/battle-anims'; import { Status, StatusEffect, getRandomStatus } from '../data/status-effect'; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from '../data/pokemon-evolutions'; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from '../data/tms'; -import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase } from '../phases'; +import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchPhase, SwitchSummonPhase, ToggleDoublePositionPhase } from '../phases'; import { BattleStat } from '../data/battle-stat'; import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from '../data/battler-tags'; import { BattlerTagType } from "../data/enums/battler-tag-type"; @@ -2681,6 +2681,42 @@ export class PlayerPokemon extends Pokemon { sd.friendship = Math.max((sd.friendship || 0) + starterAmount.value, 0); } } + /** + * Handles Revival Blessing when used by player. + * @returns Promise to revive a pokemon. + * @see {@linkcode RevivalBlessingAttr} + */ + revivalBlessing(): Promise { + return new Promise(resolve => { + this.scene.ui.setMode(Mode.PARTY, PartyUiMode.REVIVAL_BLESSING, this.getFieldIndex(), (slotIndex:integer, option: PartyOption) => { + if(slotIndex >= 0 && slotIndex<6) { + const pokemon = this.scene.getParty()[slotIndex]; + if(!pokemon || !pokemon.isFainted()) + resolve(); + + pokemon.resetTurnData(); + pokemon.resetStatus(); + pokemon.heal(Math.min(Math.max(Math.ceil(Math.floor(0.5 * pokemon.getMaxHp())), 1), pokemon.getMaxHp())); + this.scene.queueMessage(`${pokemon.name} was revived!`,0,true); + + if(this.scene.currentBattle.double && this.scene.getParty().length > 1) { + const allyPokemon = this.getAlly(); + if(slotIndex<=1) { + // Revived ally pokemon + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), slotIndex, false, false, true)); + this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } else if(allyPokemon.isFainted()) { + // Revived party pokemon, and ally pokemon is fainted + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, allyPokemon.getFieldIndex(), slotIndex, false, false, true)); + this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true)); + } + } + + } + this.scene.ui.setMode(Mode.MESSAGE).then(() => resolve()); + }, PartyUiHandler.FilterFainted) + }) + } getPossibleEvolution(evolution: SpeciesFormEvolution): Promise { return new Promise(resolve => { diff --git a/src/phases.ts b/src/phases.ts index 99308a20941..de40e1abe6b 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -3844,6 +3844,10 @@ export class SwitchPhase extends BattlePhase { if (this.isModal && !this.scene.getParty().filter(p => !p.isFainted() && !p.isActive(true)).length) return super.end(); + // Check if there is any space still in field + if (this.isModal && this.scene.getPlayerField().filter(p => !p.isFainted() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) + return super.end(); + // Override field index to 0 in case of double battle where 2/3 remaining party members fainted at once const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => !p.isFainted()).length > 1 ? this.fieldIndex : 0; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 8b497655a17..253ed8b4b72 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -24,6 +24,7 @@ export enum PartyUiMode { SWITCH, FAINT_SWITCH, POST_BATTLE_SWITCH, + REVIVAL_BLESSING, MODIFIER, MOVE_MODIFIER, TM_MODIFIER, @@ -37,6 +38,7 @@ export enum PartyOption { CANCEL = -1, SEND_OUT, PASS_BATON, + REVIVE, APPLY, TEACH, TRANSFER, @@ -103,6 +105,12 @@ export default class PartyUiHandler extends MessageUiHandler { return null; }; + public static FilterFainted = (pokemon: PlayerPokemon) => { + if(!pokemon.isFainted()) + return `${pokemon.name} still has energy\nto battle!`; + return null; + } + private static FilterAllMoves = (_pokemonMove: PokemonMove) => null; public static FilterItemMaxStacks = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => { @@ -361,7 +369,7 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.cursor < 6) { this.showOptions(); ui.playSelect(); - } else if (this.partyUiMode === PartyUiMode.FAINT_SWITCH) + } else if (this.partyUiMode === PartyUiMode.FAINT_SWITCH || this.partyUiMode === PartyUiMode.REVIVAL_BLESSING) ui.playError(); else return this.processInput(Button.CANCEL); @@ -370,7 +378,7 @@ export default class PartyUiHandler extends MessageUiHandler { if ((this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER || this.partyUiMode === PartyUiMode.SPLICE) && this.transferMode) { this.clearTransfer(); ui.playSelect(); - } else if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH) { + } else if (this.partyUiMode !== PartyUiMode.FAINT_SWITCH && this.partyUiMode !== PartyUiMode.REVIVAL_BLESSING) { if (this.selectCallback) { const selectCallback = this.selectCallback; this.selectCallback = null; @@ -580,6 +588,9 @@ export default class PartyUiHandler extends MessageUiHandler { this.options.push(PartyOption.FORM_CHANGE_ITEM + i); } break; + case PartyUiMode.REVIVAL_BLESSING: + this.options.push(PartyOption.REVIVE); + break; case PartyUiMode.MODIFIER: this.options.push(PartyOption.APPLY); break;