From 03989d07b2804cebb2cc6aee0c6e1e9c78a078a6 Mon Sep 17 00:00:00 2001
From: Xiaphear <xiaphearix@gmail.com>
Date: Mon, 11 Mar 2024 18:18:49 +0100
Subject: [PATCH] Added Wring Out. Attempted Healing Wish ( WIP )

---
 src/data/move.ts | 101 +++++++++++++++++++++++++++++++++++++++++++++--
 src/phases.ts    |  18 +++++++--
 2 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/src/data/move.ts b/src/data/move.ts
index 1fa806179dd..e575bf9b5da 100644
--- a/src/data/move.ts
+++ b/src/data/move.ts
@@ -607,6 +607,35 @@ export class HealAttr extends MoveEffectAttr {
   }
 }
 
+export class SacrificialFullRestoreAttr extends SacrificialAttr {
+  constructor() {
+    super();
+  }
+
+  apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+    if (!super.apply(user, target, move, args))
+      return false;
+
+    // We don't know which party member will be chosen, so pick the highest max HP in the party
+    const maxPartyMemberHp = user.scene.getParty().map(p => p.getMaxHp()).reduce((maxHp: integer, hp: integer) => Math.max(hp, maxHp), 0);
+
+    console.log(maxPartyMemberHp);
+
+    user.scene.pushPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
+      maxPartyMemberHp, getPokemonMessage(user, '\'s Healing Wish\nwas granted!'), true, false, false, true));
+
+    return true;
+  }
+
+  getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
+    return -20;
+  }
+
+  getCondition(): MoveConditionFunc {
+    return (user, target, move) => user.scene.getParty().filter(p => p.isActive()).length > user.scene.currentBattle.getBattlerCount();
+  }
+}
+
 export abstract class WeatherHealAttr extends HealAttr {
   constructor() {
     super(0.5);
@@ -827,6 +856,52 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
   }
 }
 
+export class RemoveHeldItemAttr extends MoveEffectAttr {
+  private chance: number;
+
+  constructor(chance: number) {
+    super(false, MoveEffectTrigger.HIT);
+    this.chance = chance;
+  }
+
+  apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
+    return new Promise<boolean>(resolve => {
+      const rand = Phaser.Math.RND.realInRange(0, 1);
+      if (rand >= this.chance)
+        return resolve(false);
+      const heldItems = this.getTargetHeldItems(target).filter(i => i.getTransferrable(false));
+      if (heldItems.length) {
+        const highestItemTier = heldItems.map(m => m.type.getOrInferTier()).reduce((highestTier, tier) => Math.max(tier, highestTier), 0);
+        const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier() === highestItemTier);
+        const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
+        user.scene.tryTransferHeldItemModifier(stolenItem, user, false, false).then(success => {
+          if (success)
+            user.scene.queueMessage(getPokemonMessage(user, ` knocked off\n${target.name}'s ${stolenItem.type.name}!`));
+          resolve(success);
+        });
+        return;
+      }
+
+      resolve(false);
+    });
+  }
+
+  getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
+    return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
+      && (m as PokemonHeldItemModifier).pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
+  }
+
+  getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
+    const heldItems = this.getTargetHeldItems(target);
+    return heldItems.length ? 5 : 0;
+  }
+
+  getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
+    const heldItems = this.getTargetHeldItems(target);
+    return heldItems.length ? -5 : 0;
+  }
+}
+
 export class HealStatusEffectAttr extends MoveEffectAttr {
   private effects: StatusEffect[];
 
@@ -2084,6 +2159,25 @@ export class CopyMoveAttr extends OverrideMoveEffectAttr {
   }
 }
 
+export class ReducePPMoveAttr extends OverrideMoveEffectAttr {
+  apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+    //const lastMove = target.getLastXMoves().find(() => true);
+    const lastMove = user.scene.currentBattle.lastMove;
+
+    const moveTargets = getMoveTargets(user, lastMove);
+    if (!moveTargets.targets.length)
+      return false;
+
+    const targets = moveTargets.multiple || moveTargets.targets.length === 1
+      ? moveTargets.targets
+      : moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
+        ? [ target.getBattlerIndex() ]
+        : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ];
+
+    return true;
+  }
+}
+
 // TODO: Review this
 const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
   const targetMoves = target.getMoveHistory().filter(m => !m.virtual);
@@ -3230,8 +3324,8 @@ export function initMoves() {
     new AttackMove(Moves.GYRO_BALL, "Gyro Ball", Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 5, -1, "The user tackles the target with a high-speed spin. The slower the user compared to the target, the greater the move's power.", -1, 0, 4)
       .attr(BattleStatRatioPowerAttr, Stat.SPD, true)
       .ballBombMove(),
-    new SelfStatusMove(Moves.HEALING_WISH, "Healing Wish", Type.PSYCHIC, -1, 10, -1, "The user faints. In return, the Pokémon taking its place will have its HP restored and status conditions cured.", -1, 0, 4)
-      .attr(SacrificialAttr),
+    new SelfStatusMove(Moves.HEALING_WISH, "Healing Wish (N)", Type.PSYCHIC, -1, 10, -1, "The user faints. In return, the Pokémon taking its place will have its HP restored and status conditions cured.", -1, 0, 4)
+      .attr(SacrificialFullRestoreAttr),
     new AttackMove(Moves.BRINE, "Brine", Type.WATER, MoveCategory.SPECIAL, 65, 100, 10, -1, "If the target's HP is half or less, this attack will hit with double the power.", -1, 0, 4)
       .attr(MovePowerMultiplierAttr, (user, target, move) => target.getHpRatio() < 0.5 ? 2 : 1),
     new AttackMove(Moves.NATURAL_GIFT, "Natural Gift (N)", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, "The user draws power to attack by using its held Berry. The Berry determines the move's type and power.", -1, 0, 4)
@@ -3264,7 +3358,8 @@ export function initMoves() {
       .makesContact(),
     new StatusMove(Moves.HEAL_BLOCK, "Heal Block (N)", Type.PSYCHIC, 100, 15, -1, "For five turns, the user prevents the opposing team from using any moves, Abilities, or held items that recover HP.", -1, 0, 4)
       .target(MoveTarget.ALL_NEAR_ENEMIES),
-    new AttackMove(Moves.WRING_OUT, "Wring Out (N)", Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, "The user powerfully wrings the target. The more HP the target has, the greater the move's power.", -1, 0, 4)
+    new AttackMove(Moves.WRING_OUT, "Wring Out", Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, "The user powerfully wrings the target. The more HP the target has, the greater the move's power.", -1, 0, 4)
+      .attr(OpponentHighHpPowerAttr)
       .makesContact(),
     new SelfStatusMove(Moves.POWER_TRICK, "Power Trick (N)", Type.PSYCHIC, -1, 10, -1, "The user employs its psychic power to switch its Attack stat with its Defense stat.", -1, 0, 4),
     new StatusMove(Moves.GASTRO_ACID, "Gastro Acid (N)", Type.POISON, 100, 10, -1, "The user hurls up its stomach acids on the target. The fluid eliminates the effect of the target's Ability.", -1, 0, 4),
diff --git a/src/phases.ts b/src/phases.ts
index 54141e0e44e..5fb87a91cc6 100644
--- a/src/phases.ts
+++ b/src/phases.ts
@@ -3318,15 +3318,17 @@ export class PokemonHealPhase extends CommonAnimPhase {
   private showFullHpMessage: boolean;
   private skipAnim: boolean;
   private revive: boolean;
+  private healStatus: boolean;
 
-  constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string, showFullHpMessage: boolean, skipAnim?: boolean, revive?: boolean) {
+  constructor(scene: BattleScene, battlerIndex: BattlerIndex, hpHealed: integer, message: string, showFullHpMessage: boolean, skipAnim: boolean = false, revive: boolean = false, healStatus: boolean = false) {
     super(scene, battlerIndex, undefined, CommonAnim.HEALTH_UP);
 
     this.hpHealed = hpHealed;
     this.message = message;
     this.showFullHpMessage = showFullHpMessage;
-    this.skipAnim = !!skipAnim;
-    this.revive = !!revive;
+    this.skipAnim = skipAnim;
+    this.revive = revive;
+    this.healStatus = healStatus;
   }
 
   start() {
@@ -3346,6 +3348,9 @@ export class PokemonHealPhase extends CommonAnimPhase {
 
     const fullHp = pokemon.getHpRatio() >= 1;
 
+    const hasMessage = !!this.message;
+    let lastStatusEffect = StatusEffect.NONE;
+
     if (!fullHp) {
       const hpRestoreMultiplier = new Utils.IntegerHolder(1);
       if (!this.revive)
@@ -3359,6 +3364,10 @@ export class PokemonHealPhase extends CommonAnimPhase {
         if (healAmount.value > this.scene.gameData.gameStats.highestHeal)
           this.scene.gameData.gameStats.highestHeal = healAmount.value;
       }
+      if (this.healStatus && !this.revive && pokemon.status) {
+        lastStatusEffect = pokemon.status.effect;
+        pokemon.resetStatus();
+      }
       pokemon.updateInfo().then(() => super.end());
     } else if (this.showFullHpMessage)
       this.message = getPokemonMessage(pokemon, `'s\nHP is full!`);
@@ -3366,6 +3375,9 @@ export class PokemonHealPhase extends CommonAnimPhase {
     if (this.message)
       this.scene.queueMessage(this.message);
 
+    if (this.healStatus && lastStatusEffect && !hasMessage)
+      this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectHealText(lastStatusEffect)));
+
     if (fullHp)
       super.end();
   }