From 63cb2ae22faeec9c0f537f6febb1ca8ec117b34c Mon Sep 17 00:00:00 2001
From: Flashfyre <flashfireex@gmail.com>
Date: Mon, 15 Jan 2024 23:29:22 -0500
Subject: [PATCH] Implement some moves and update arena tags to include side

---
 README.md                          | 11 -----
 src/arena.ts                       | 29 +++++++----
 src/battle-phases.ts               | 22 ++++++---
 src/data/ability.ts                |  2 +-
 src/data/arena-tag.ts              | 79 ++++++++++++++++++------------
 src/data/battler-tags.ts           | 23 +++++++++
 src/data/enums/arena-tag-type.ts   | 14 ++++++
 src/data/enums/battler-tag-type.ts |  1 +
 src/data/move.ts                   | 77 ++++++++++++++++++++++-------
 src/pokemon.ts                     |  8 ++-
 10 files changed, 190 insertions(+), 76 deletions(-)
 create mode 100644 src/data/enums/arena-tag-type.ts

diff --git a/README.md b/README.md
index 53ebbc17e11..a63d7da6c29 100644
--- a/README.md
+++ b/README.md
@@ -14,26 +14,15 @@
   - Add IV screen
 - Capture logic
   - Critical capture
-- Save data
-  - Update dex format to share attributes
 - Modifiers
-  - PP Up
   - Various mainline game items for various enhancements
-  - IV scanner
   - Valuable items for money
 - Trainers
   - Finish party pools
   - Add dialogue
   - Add reward for gym leader victories
-- Encounters
-  - Add extremely rare chance of Arceus available anywhere with type change
-- Balancing
-  - Biome pools
 - Battle animations
   - Fix broken battle animations (mostly ones with backgrounds)
-  - Add common animations for modifier effects
-- Achievements
-  - Add more achievements
 - Modes
   - Add random mode
   - Add Nuzlocke mode
diff --git a/src/arena.ts b/src/arena.ts
index 69ae007edec..f17f17490a2 100644
--- a/src/arena.ts
+++ b/src/arena.ts
@@ -9,7 +9,8 @@ import { CommonAnimPhase } from "./battle-phases";
 import { CommonAnim } from "./data/battle-anims";
 import { Type } from "./data/type";
 import Move from "./data/move";
-import { ArenaTag, ArenaTagType, getArenaTag } from "./data/arena-tag";
+import { ArenaTag, ArenaTagSide, getArenaTag } from "./data/arena-tag";
+import { ArenaTagType } from "./data/enums/arena-tag-type";
 import { GameMode } from "./game-mode";
 import { TrainerType } from "./data/enums/trainer-type";
 import { BattlerIndex } from "./battle";
@@ -387,21 +388,27 @@ export class Arena {
     }
   }
 
-  applyTags(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, ...args: any[]): void {
-    const tags = typeof tagType === 'number'
+  applyTagsForSide(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, side: ArenaTagSide, ...args: any[]): void {
+    let tags = typeof tagType === 'string'
       ? this.tags.filter(t => t.tagType === tagType)
       : this.tags.filter(t => t instanceof tagType);
-    tags.forEach(t => t.apply(args));
+    if (side !== ArenaTagSide.BOTH)
+      tags = tags.filter(t => t.side === side);
+    tags.forEach(t => t.apply(this, args));
+	}
+  
+  applyTags(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, ...args: any[]): void {
+    this.applyTagsForSide(tagType, ArenaTagSide.BOTH, args);
 	}
 
-  addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex): boolean {
+  addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, targetIndex?: BattlerIndex): boolean {
     const existingTag = this.getTag(tagType);
     if (existingTag) {
       existingTag.onOverlap(this);
       return false;
     }
 
-    const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex);
+    const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
     this.tags.push(newTag);
     newTag.onAdd(this);
 
@@ -409,9 +416,13 @@ export class Arena {
   }
 
   getTag(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }): ArenaTag {
-    return typeof(tagType) === 'number'
-      ? this.tags.find(t => t.tagType === tagType)
-      : this.tags.find(t => t instanceof tagType);
+    return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
+  }
+
+  getTagOnSide(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, side: ArenaTagSide): ArenaTag {
+    return typeof(tagType) === 'string'
+      ? this.tags.find(t => t.tagType === tagType && (t.side === ArenaTagSide.BOTH || t.side === side))
+      : this.tags.find(t => t instanceof tagType && (t.side === ArenaTagSide.BOTH || t.side === side));
   }
 
   lapseTags(): void {
diff --git a/src/battle-phases.ts b/src/battle-phases.ts
index 20f3be907e5..58907179c02 100644
--- a/src/battle-phases.ts
+++ b/src/battle-phases.ts
@@ -28,7 +28,8 @@ import { Starter } from "./ui/starter-select-ui-handler";
 import { Gender } from "./data/gender";
 import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
 import { TempBattleStat } from "./data/temp-battle-stat";
-import { ArenaTagType, ArenaTrapTag, TrickRoomTag } from "./data/arena-tag";
+import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
+import { ArenaTagType } from "./data/enums/arena-tag-type";
 import { Abilities, CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, PostAttackAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
 import { Unlockables, getUnlockableName } from "./system/unlockables";
 import { getBiomeKey } from "./arena";
@@ -2072,7 +2073,9 @@ export class MoveEndPhase extends PokemonPhase {
   start() {
     super.start();
 
-    this.getPokemon().lapseTags(BattlerTagLapseType.AFTER_MOVE);
+    const pokemon = this.getPokemon();
+    if (pokemon.isActive(true))
+      pokemon.lapseTags(BattlerTagLapseType.AFTER_MOVE);
 
     this.end();
   }
@@ -2132,13 +2135,15 @@ export class StatChangePhase extends PokemonPhase {
   private stats: BattleStat[];
   private selfTarget: boolean;
   private levels: integer;
+  private showMessage: boolean;
 
-  constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer) {
+  constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: integer, showMessage: boolean = true) {
     super(scene, battlerIndex);
 
     this.selfTarget = selfTarget;
     this.stats = stats;
     this.levels = levels;
+    this.showMessage = showMessage;
   }
 
   start() {
@@ -2152,6 +2157,9 @@ export class StatChangePhase extends PokemonPhase {
       const cancelled = new Utils.BooleanHolder(false);
 
       if (!this.selfTarget && this.levels < 0)
+        this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
+
+      if (!cancelled.value && !this.selfTarget && this.levels < 0)
         applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled);
       
       return !cancelled.value;
@@ -2164,9 +2172,11 @@ export class StatChangePhase extends PokemonPhase {
     const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats[stat] + levels.value, 6) : Math.max(battleStats[stat] + levels.value, -6)) - battleStats[stat]);
 
     const end = () => {
-      const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels);
-      for (let message of messages)
-        this.scene.queueMessage(message);
+      if (this.showMessage) {
+        const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels);
+        for (let message of messages)
+          this.scene.queueMessage(message);
+      }
 
       for (let stat of filteredStats)
         pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6);
diff --git a/src/data/ability.ts b/src/data/ability.ts
index ad322ca0a64..e1e5019140f 100644
--- a/src/data/ability.ts
+++ b/src/data/ability.ts
@@ -9,7 +9,7 @@ import { BattlerTag } from "./battler-tags";
 import { BattlerTagType } from "./enums/battler-tag-type";
 import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
 import Move, { MoveCategory, MoveFlags, RecoilAttr } from "./move";
-import { ArenaTagType } from "./arena-tag";
+import { ArenaTagType } from "./enums/arena-tag-type";
 import { Stat } from "./pokemon-stat";
 import { PokemonHeldItemModifier } from "../modifier/modifier";
 import { Moves } from "./enums/moves";
diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts
index 71bc7cdf1d4..30cc1da7d14 100644
--- a/src/data/arena-tag.ts
+++ b/src/data/arena-tag.ts
@@ -9,18 +9,12 @@ import { StatusEffect } from "./status-effect";
 import { BattlerTagType } from "./enums/battler-tag-type";
 import { BattlerIndex } from "../battle";
 import { Moves } from "./enums/moves";
+import { ArenaTagType } from "./enums/arena-tag-type";
 
-export enum ArenaTagType {
-  NONE,
-  MUD_SPORT,
-  WATER_SPORT,
-  SPIKES,
-  TOXIC_SPIKES,
-  FUTURE_SIGHT,
-  DOOM_DESIRE,
-  STEALTH_ROCK,
-  TRICK_ROOM,
-  GRAVITY
+export enum ArenaTagSide {
+  BOTH,
+  PLAYER,
+  ENEMY
 }
 
 export abstract class ArenaTag {
@@ -28,22 +22,24 @@ export abstract class ArenaTag {
   public turnCount: integer;
   public sourceMove: Moves;
   public sourceId: integer;
+  public side: ArenaTagSide;
 
-  constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId?: integer) {
+  constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
     this.tagType = tagType;
     this.turnCount = turnCount;
     this.sourceMove = sourceMove;
     this.sourceId = sourceId;
+    this.side = side;
   }
 
-  apply(args: any[]): boolean {
+  apply(arena: Arena, args: any[]): boolean {
     return true;
   }
 
   onAdd(arena: Arena): void { }
 
   onRemove(arena: Arena): void {
-    arena.scene.queueMessage(`${this.getMoveName()}\'s effect wore off.`);
+    arena.scene.queueMessage(`${this.getMoveName()}\'s effect wore off${this.side === ArenaTagSide.PLAYER ? '\non your side' : this.side === ArenaTagSide.ENEMY ? '\non the foe\'s side' : ''}.`);
   }
 
   onOverlap(arena: Arena): void { }
@@ -59,6 +55,27 @@ export abstract class ArenaTag {
   }
 }
 
+export class MistTag extends ArenaTag {
+  constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
+    super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
+  }
+
+  onAdd(arena: Arena): void {
+    super.onAdd(arena);
+
+    const source = arena.scene.getPokemonById(this.sourceId);
+    arena.scene.queueMessage(getPokemonMessage(source, `'s team became\nshrowded in mist!`));
+  }
+
+  apply(arena: Arena, args: any[]): boolean {
+    (args[0] as Utils.BooleanHolder).value = true;
+
+    arena.scene.queueMessage('The mist prevented\nthe lowering of stats!');
+    
+    return true;
+  }
+}
+
 export class WeakenMoveTypeTag extends ArenaTag {
   private weakenedType: Type;
 
@@ -68,7 +85,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
     this.weakenedType = type;
   }
 
-  apply(args: any[]): boolean {
+  apply(arena: Arena, args: any[]): boolean {
     if ((args[0] as Type) === this.weakenedType) {
       (args[1] as Utils.NumberHolder).value *= 0.33;
       return true;
@@ -110,8 +127,8 @@ export class ArenaTrapTag extends ArenaTag {
   public layers: integer;
   public maxLayers: integer;
 
-  constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, maxLayers: integer) {
-    super(tagType, 0, sourceMove, sourceId);
+  constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) {
+    super(tagType, 0, sourceMove, sourceId, side);
 
     this.layers = 1;
     this.maxLayers = maxLayers;
@@ -125,9 +142,9 @@ export class ArenaTrapTag extends ArenaTag {
     }
   }
 
-  apply(args: any[]): boolean { 
+  apply(arena: Arena, args: any[]): boolean { 
     const pokemon = args[0] as Pokemon;
-    if (this.sourceId === pokemon.id || !!(pokemon.scene.getPokemonById(this.sourceId)?.isPlayer()) === pokemon.isPlayer())
+    if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) === pokemon.isPlayer())
       return false;
 
     return this.activateTrap(pokemon);
@@ -139,8 +156,8 @@ export class ArenaTrapTag extends ArenaTag {
 }
 
 class SpikesTag extends ArenaTrapTag {
-  constructor(sourceId: integer) {
-    super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, 3);
+  constructor(sourceId: integer, side: ArenaTagSide) {
+    super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
   }
 
   onAdd(arena: Arena): void {
@@ -165,8 +182,8 @@ class SpikesTag extends ArenaTrapTag {
 }
 
 class ToxicSpikesTag extends ArenaTrapTag {
-  constructor(sourceId: integer) {
-    super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, 2);
+  constructor(sourceId: integer, side: ArenaTagSide) {
+    super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
   }
 
   onAdd(arena: Arena): void {
@@ -211,8 +228,8 @@ class DelayedAttackTag extends ArenaTag {
 }
 
 class StealthRockTag extends ArenaTrapTag {
-  constructor(sourceId: integer) {
-    super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, 1);
+  constructor(sourceId: integer, side: ArenaTagSide) {
+    super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
   }
 
   onAdd(arena: Arena): void {
@@ -263,7 +280,7 @@ export class TrickRoomTag extends ArenaTag {
     super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
   }
 
-  apply(args: any[]): boolean {
+  apply(arena: Arena, args: any[]): boolean {
     const speedReversed = args[0] as Utils.BooleanHolder;
     speedReversed.value = !speedReversed.value;
     return true;
@@ -292,21 +309,23 @@ export class GravityTag extends ArenaTag {
   }
 }
 
-export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex): ArenaTag {
+export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag {
   switch (tagType) {
+    case ArenaTagType.MIST:
+      return new MistTag(turnCount, sourceId, side);
     case ArenaTagType.MUD_SPORT:
       return new MudSportTag(turnCount, sourceId);
     case ArenaTagType.WATER_SPORT:
       return new WaterSportTag(turnCount, sourceId);
     case ArenaTagType.SPIKES:
-      return new SpikesTag(sourceId);
+      return new SpikesTag(sourceId, side);
     case ArenaTagType.TOXIC_SPIKES:
-      return new ToxicSpikesTag(sourceId);
+      return new ToxicSpikesTag(sourceId, side);
     case ArenaTagType.FUTURE_SIGHT:
     case ArenaTagType.DOOM_DESIRE:
       return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex);
     case ArenaTagType.STEALTH_ROCK:
-      return new StealthRockTag(sourceId);
+      return new StealthRockTag(sourceId, side);
     case ArenaTagType.TRICK_ROOM:
       return new TrickRoomTag(turnCount, sourceId);
     case ArenaTagType.GRAVITY:
diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts
index 5d7ce0d9ad6..31412db13da 100644
--- a/src/data/battler-tags.ts
+++ b/src/data/battler-tags.ts
@@ -564,6 +564,27 @@ export class ProtectedTag extends BattlerTag {
   }
 }
 
+export class EnduringTag extends BattlerTag {
+  constructor(sourceMove: Moves) {
+    super(BattlerTagType.ENDURING, BattlerTagLapseType.CUSTOM, 0, sourceMove);
+  }
+
+  onAdd(pokemon: Pokemon): void {
+    super.onAdd(pokemon);
+
+    pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' braced\nitself!'));
+  }
+
+  lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
+    if (lapseType === BattlerTagLapseType.CUSTOM) {
+      pokemon.scene.queueMessage(getPokemonMessage(pokemon, ' endured\nthe hit!'));
+      return true;
+    }
+
+    return super.lapse(pokemon, lapseType);
+  }
+}
+
 export class PerishSongTag extends BattlerTag {
   constructor(turnCount: integer) {
     super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG);
@@ -743,6 +764,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: integer, sourc
       return new MagmaStormTag(turnCount, sourceId);
     case BattlerTagType.PROTECTED:
       return new ProtectedTag(sourceMove);
+    case BattlerTagType.ENDURING:
+      return new EnduringTag(sourceMove);
     case BattlerTagType.PERISH_SONG:
       return new PerishSongTag(turnCount);
     case BattlerTagType.TRUANT:
diff --git a/src/data/enums/arena-tag-type.ts b/src/data/enums/arena-tag-type.ts
new file mode 100644
index 00000000000..90c5b1dc554
--- /dev/null
+++ b/src/data/enums/arena-tag-type.ts
@@ -0,0 +1,14 @@
+
+export enum ArenaTagType {
+  NONE = "NONE",
+  MUD_SPORT = "MUD_SPORT",
+  WATER_SPORT = "WATER_SPORT",
+  SPIKES = "SPIKES",
+  TOXIC_SPIKES = "TOXIC_SPIKES",
+  MIST = "MIST",
+  FUTURE_SIGHT = "FUTURE_SIGHT",
+  DOOM_DESIRE = "DOOM_DESIRE",
+  STEALTH_ROCK = "STEALTH_ROCK",
+  TRICK_ROOM = "TRICK_ROOM",
+  GRAVITY = "GRAVITY"
+}
diff --git a/src/data/enums/battler-tag-type.ts b/src/data/enums/battler-tag-type.ts
index 3e5b30b6236..06e3c4e8186 100644
--- a/src/data/enums/battler-tag-type.ts
+++ b/src/data/enums/battler-tag-type.ts
@@ -21,6 +21,7 @@ export enum BattlerTagType {
   SAND_TOMB = "SAND_TOMB",
   MAGMA_STORM = "MAGMA_STORM",
   PROTECTED = "PROTECTED",
+  ENDURING = "ENDURING",
   PERISH_SONG = "PERISH_SONG",
   TRUANT = "TRUANT",
   SLOW_START = "SLOW_START",
diff --git a/src/data/move.ts b/src/data/move.ts
index ee89447280f..def9f6344a4 100644
--- a/src/data/move.ts
+++ b/src/data/move.ts
@@ -1,7 +1,7 @@
 import { Moves } from "./enums/moves";
 import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
 import { BattleEndPhase, DamagePhase, MovePhase, NewBattlePhase, ObtainStatusEffectPhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../battle-phases";
-import { BattleStat } from "./battle-stat";
+import { BattleStat, getBattleStatName } from "./battle-stat";
 import { EncoreTag } from "./battler-tags";
 import { BattlerTagType } from "./enums/battler-tag-type";
 import { getPokemonMessage } from "../messages";
@@ -10,11 +10,12 @@ import { StatusEffect, getStatusEffectDescriptor } from "./status-effect";
 import { Type } from "./type";
 import * as Utils from "../utils";
 import { WeatherType } from "./weather";
-import { ArenaTagType, ArenaTrapTag } from "./arena-tag";
+import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
+import { ArenaTagType } from "./enums/arena-tag-type";
 import { Abilities, BlockRecoilDamageAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs } from "./ability";
 import { PokemonHeldItemModifier } from "../modifier/modifier";
 import { BattlerIndex } from "../battle";
-import { Stat } from "./pokemon-stat";
+import { Stat, getStatName } from "./pokemon-stat";
 
 export enum MoveCategory {
   PHYSICAL,
@@ -998,7 +999,7 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
           (args[0] as Utils.BooleanHolder).value = true;
           user.scene.queueMessage(getPokemonMessage(user, ` ${this.chargeText.replace('{TARGET}', target.name)}`));
           user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
-          user.scene.arena.addTag(this.tagType, 3, move.id, user.id, target.getBattlerIndex());
+          user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, target.getBattlerIndex());
 
           resolve(true);
         });
@@ -1012,23 +1013,25 @@ export class StatChangeAttr extends MoveEffectAttr {
   public stats: BattleStat[];
   public levels: integer;
   private condition: MoveConditionFunc;
+  private showMessage: boolean;
 
-  constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc) {
+  constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true) {
     super(selfTarget, MoveEffectTrigger.HIT);
     this.stats = typeof(stats) === 'number'
       ? [ stats as BattleStat ]
       : stats as BattleStat[];
     this.levels = levels;
     this.condition = condition || null;
+    this.showMessage = showMessage;
   }
 
-  apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
+  apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
     if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move)))
       return false;
 
     if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) {
       const levels = this.getLevels(user);
-      user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels));
+      user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage));
       return true;
     }
 
@@ -1061,6 +1064,27 @@ export class GrowthStatChangeAttr extends StatChangeAttr {
   }
 }
 
+export class HalfHpStatMaxAttr extends StatChangeAttr {
+  constructor(stat: BattleStat) {
+    super(stat, 12, true, null, false);
+  }
+
+  apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
+    return new Promise<boolean>(resolve => {
+      user.damage(Math.floor(user.getMaxHp() / 2));
+      user.updateInfo().then(() => {
+        const ret = super.apply(user, target, move, args);
+        user.scene.queueMessage(getPokemonMessage(user, ` cut its own hp\nand maximized its ${getBattleStatName(this.stats[0])}!`));
+        resolve(ret);
+      });
+    });
+  }
+
+  getCondition(): MoveConditionFunc {
+    return (user, target, move) => user.getHpRatio() > 0.5 || user.summonData.battleStats[this.stats[0]] >= 6;
+  }
+}
+
 export class HpSplitAttr extends MoveEffectAttr {
   apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
     return new Promise(resolve => {
@@ -1568,8 +1592,8 @@ export class TrapAttr extends AddBattlerTagAttr {
 }
 
 export class ProtectAttr extends AddBattlerTagAttr {
-  constructor() {
-    super(BattlerTagType.PROTECTED, true);
+  constructor(tagType: BattlerTagType = BattlerTagType.PROTECTED) {
+    super(tagType, true);
   }
 
   getCondition(): MoveConditionFunc {
@@ -1577,7 +1601,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
       let timesUsed = 0;
       const moveHistory = user.getLastXMoves();
       let turnMove: TurnMove;
-      while (moveHistory.length && (turnMove = moveHistory.shift()).move === move.id && turnMove.result === MoveResult.SUCCESS)
+      while (moveHistory.length && allMoves[(turnMove = moveHistory.shift()).move].getAttrs(ProtectAttr).find(pa => (pa as ProtectAttr).tagType === this.tagType) && turnMove.result === MoveResult.SUCCESS)
         timesUsed++;
       if (timesUsed)
         return !user.randSeedInt(Math.pow(2, timesUsed));
@@ -1586,6 +1610,12 @@ export class ProtectAttr extends AddBattlerTagAttr {
   }
 }
 
+export class EndureAttr extends ProtectAttr {
+  constructor() {
+    super(BattlerTagType.ENDURING);
+  }
+}
+
 export class IgnoreAccuracyAttr extends AddBattlerTagAttr {
   constructor() {
     super(BattlerTagType.IGNORE_ACCURACY, true, false, 1);
@@ -1635,12 +1665,14 @@ export class HitsTagAttr extends MoveAttr {
 export class AddArenaTagAttr extends MoveEffectAttr {
   public tagType: ArenaTagType;
   public turnCount: integer;
+  private failOnOverlap: boolean;
 
-  constructor(tagType: ArenaTagType, turnCount?: integer) {
+  constructor(tagType: ArenaTagType, turnCount?: integer, failOnOverlap: boolean = false) {
     super(true);
 
     this.tagType = tagType;
     this.turnCount = turnCount;
+    this.failOnOverlap = failOnOverlap;
   }
 
   apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@@ -1648,12 +1680,18 @@ export class AddArenaTagAttr extends MoveEffectAttr {
       return false;
 
     if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) {
-      user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id);
+      user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
       return true;
     }
 
     return false;
   }
+
+  getCondition(): MoveConditionFunc {
+    return this.failOnOverlap
+      ? (user, target, move) => !user.scene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)
+      : null;
+  }
 }
 
 export class AddArenaTrapTagAttr extends AddArenaTagAttr {
@@ -1738,8 +1776,10 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
     };
   }
 
-  getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
-    return -100; // Overridden in switch logic
+  getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
+    if (this.batonPass)
+      return -100; // Overridden in switch logic
+    return this.user ? Math.floor(user.getHpRatio() * 20) : super.getUserBenefitScore(user, target, move);
   }
 }
 
@@ -2249,7 +2289,8 @@ export function initMoves() {
       .attr(StatusEffectAttr, StatusEffect.BURN),
     new AttackMove(Moves.FLAMETHROWER, "Flamethrower", Type.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 125, "The target is scorched with an intense blast of fire. This may also leave the target with a burn.", 10, 0, 1)
       .attr(StatusEffectAttr, StatusEffect.BURN),
-    new StatusMove(Moves.MIST, "Mist (N)", Type.ICE, -1, 30, -1, "The user cloaks itself and its allies in a white mist that prevents any of their stats from being lowered for five turns.", -1, 0, 1)
+    new StatusMove(Moves.MIST, "Mist", Type.ICE, -1, 30, -1, "The user cloaks itself and its allies in a white mist that prevents any of their stats from being lowered for five turns.", -1, 0, 1)
+      .attr(AddArenaTagAttr, ArenaTagType.MIST, 5, true)
       .target(MoveTarget.USER_SIDE),
     new AttackMove(Moves.WATER_GUN, "Water Gun", Type.WATER, MoveCategory.SPECIAL, 40, 100, 25, -1, "The target is blasted with a forceful shot of water.", -1, 0, 1),
     new AttackMove(Moves.HYDRO_PUMP, "Hydro Pump", Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, 142, "The target is blasted by a huge volume of water launched under great pressure.", -1, 0, 1),
@@ -2582,7 +2623,8 @@ export function initMoves() {
     new AttackMove(Moves.FEINT_ATTACK, "Feint Attack", Type.DARK, MoveCategory.PHYSICAL, 60, -1, 20, -1, "The user approaches the target disarmingly, then throws a sucker punch. This attack never misses.", -1, 0, 2),
     new StatusMove(Moves.SWEET_KISS, "Sweet Kiss", Type.FAIRY, 75, 10, -1, "The user kisses the target with a sweet, angelic cuteness that causes confusion.", -1, 0, 2)
       .attr(ConfuseAttr),
-    new SelfStatusMove(Moves.BELLY_DRUM, "Belly Drum (N)", Type.NORMAL, -1, 10, -1, "The user maximizes its Attack stat in exchange for HP equal to half its max HP.", -1, 0, 2),
+    new SelfStatusMove(Moves.BELLY_DRUM, "Belly Drum", Type.NORMAL, -1, 10, -1, "The user maximizes its Attack stat in exchange for HP equal to half its max HP.", -1, 0, 2)
+      .attr(HalfHpStatMaxAttr, BattleStat.ATK),
     new AttackMove(Moves.SLUDGE_BOMB, "Sludge Bomb", Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 148, "Unsanitary sludge is hurled at the target. This may also poison the target.", 30, 0, 2)
       .attr(StatusEffectAttr, StatusEffect.POISON)
       .ballBombMove(),
@@ -2627,7 +2669,8 @@ export function initMoves() {
       .target(MoveTarget.BOTH_SIDES),
     new AttackMove(Moves.GIGA_DRAIN, "Giga Drain", Type.GRASS, MoveCategory.SPECIAL, 75, 100, 10, 111, "A nutrient-draining attack. The user's HP is restored by half the damage taken by the target.", -1, 0, 2)
       .attr(HitHealAttr),
-    new SelfStatusMove(Moves.ENDURE, "Endure (N)", Type.NORMAL, -1, 10, 47, "The user endures any attack with at least 1 HP. Its chance of failing rises if it is used in succession.", -1, 4, 2),
+    new SelfStatusMove(Moves.ENDURE, "Endure", Type.NORMAL, -1, 10, 47, "The user endures any attack with at least 1 HP. Its chance of failing rises if it is used in succession.", -1, 4, 2)
+      .attr(EndureAttr),
     new StatusMove(Moves.CHARM, "Charm", Type.FAIRY, 100, 20, 2, "The user gazes at the target rather charmingly, making it less wary. This harshly lowers the target's Attack stat.", -1, 0, 2)
       .attr(StatChangeAttr, BattleStat.ATK, -2),
     new AttackMove(Moves.ROLLOUT, "Rollout", Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, "The user continually rolls into the target over five turns. It becomes more powerful each time it hits.", -1, 0, 2)
diff --git a/src/pokemon.ts b/src/pokemon.ts
index 3584cb6130f..b7c58d596e9 100644
--- a/src/pokemon.ts
+++ b/src/pokemon.ts
@@ -22,7 +22,8 @@ import { BattlerTagType } from "./data/enums/battler-tag-type";
 import { Species } from './data/enums/species';
 import { WeatherType } from './data/weather';
 import { TempBattleStat } from './data/temp-battle-stat';
-import { ArenaTagType, WeakenMoveTypeTag } from './data/arena-tag';
+import { WeakenMoveTypeTag } from './data/arena-tag';
+import { ArenaTagType } from "./data/enums/arena-tag-type";
 import { Biome } from "./data/enums/biome";
 import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
 import PokemonData from './system/pokemon-data';
@@ -1115,7 +1116,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
 
     if (this.hp > 1 && this.hp - damage <= 0 && !preventEndure) {
       const surviveDamage = new Utils.BooleanHolder(false);
-      this.scene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage);
+      if (this.lapseTag(BattlerTagType.ENDURING))
+        surviveDamage.value = true;
+      if (!surviveDamage.value)
+        this.scene.applyModifiers(SurviveDamageModifier, this.isPlayer(), this, surviveDamage);
       if (surviveDamage.value)
         damage = this.hp - 1;
     }