From f49a3e51273e4b5991f0622082f85d3a7720acdb Mon Sep 17 00:00:00 2001
From: Flashfyre <flashfireex@gmail.com>
Date: Sun, 14 Jan 2024 20:47:08 -0500
Subject: [PATCH] Add field zoom functionality and make final boss larger

---
 src/battle-phases.ts     | 18 +++++++++++++---
 src/battle-scene.ts      | 44 ++++++++++++++++++++++++++++++++++------
 src/data/move.ts         | 13 ++++++------
 src/form-change-phase.ts |  2 +-
 src/pipelines/sprite.ts  | 24 ++++++++++++++--------
 src/pokemon.ts           |  7 +++++--
 6 files changed, 82 insertions(+), 26 deletions(-)

diff --git a/src/battle-phases.ts b/src/battle-phases.ts
index d32b9bf0daa..66cabf787db 100644
--- a/src/battle-phases.ts
+++ b/src/battle-phases.ts
@@ -449,8 +449,8 @@ export class EncounterPhase extends BattlePhase {
 
   doEncounter() {
     this.scene.playBgm(undefined, true);
-
     this.scene.updateModifiers(false);
+    this.scene.setFieldScale(1);
 
     /*if (startingWave > 10) {
       for (let m = 0; m < Math.min(Math.floor(startingWave / 10), 99); m++)
@@ -617,6 +617,8 @@ export class NextEncounterPhase extends EncounterPhase {
   doEncounter(): void {
     this.scene.playBgm(undefined, true);
 
+    this.scene.arenaNextEnemy.setVisible(true);
+
     const enemyField = this.scene.getEnemyField();
     this.scene.tweens.add({
       targets: [ this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer ].flat(),
@@ -626,6 +628,7 @@ export class NextEncounterPhase extends EncounterPhase {
         this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x);
         this.scene.arenaEnemy.setAlpha(1);
         this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300);
+        this.scene.arenaNextEnemy.setVisible(false);
         if (this.scene.lastEnemyTrainer)
           this.scene.lastEnemyTrainer.destroy();
         
@@ -836,7 +839,8 @@ export class SummonPhase extends PartyMemberPokemonPhase {
       this.scene.tweens.add({
         targets: this.scene.trainer,
         x: -36,
-        duration: 1000
+        duration: 1000,
+        onComplete: () => this.scene.trainer.setVisible(false)
       });
       this.scene.time.delayedCall(750, () => this.summon());
     } else {
@@ -1060,6 +1064,8 @@ export class ShowTrainerPhase extends BattlePhase {
   start() {
     super.start();
 
+    this.scene.trainer.setVisible(true)
+
     this.scene.trainer.setTexture('trainer_m');
 
     this.scene.tweens.add({
@@ -2138,7 +2144,12 @@ export class StatChangePhase extends PokemonPhase {
       pokemon.enableMask();
       const pokemonMaskSprite = pokemon.maskSprite;
 
-      const statSprite = this.scene.add.tileSprite((this.player ? 106 : 236) * 6, ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * 6, 156, 316, 'battle_stats', filteredStats.length > 1 ? 'mix' : BattleStat[filteredStats[0]].toLowerCase());
+      const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale;
+      const tileY = ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale;
+      const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale();
+      const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale();
+
+      const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, 'battle_stats', filteredStats.length > 1 ? 'mix' : BattleStat[filteredStats[0]].toLowerCase());
       statSprite.setPipeline(this.scene.fieldSpritePipeline);
       statSprite.setAlpha(0);
       statSprite.setScale(6);
@@ -2388,6 +2399,7 @@ export class DamagePhase extends PokemonPhase {
           this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].firstStageWin, pokemon.name, null, () => {
             this.scene.addEnemyModifier(getModifierType(modifierTypes.MINI_BLACK_HOLE).newModifier(pokemon) as PersistentModifier, false, true);
             pokemon.generateAndPopulateMoveset(1);
+            this.scene.setFieldScale(0.75);
             this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger, false);
             this.scene.currentBattle.double = true;
             const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted());
diff --git a/src/battle-scene.ts b/src/battle-scene.ts
index c09402019e0..25e20b57556 100644
--- a/src/battle-scene.ts
+++ b/src/battle-scene.ts
@@ -48,6 +48,7 @@ import PokemonData from './system/pokemon-data';
 import { Nature } from './data/nature';
 import { SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges } from './data/pokemon-forms';
 import { FormChangePhase, QuietFormChangePhase } from './form-change-phase';
+import { BattleSpec } from './enums/battle-spec';
 
 const enableAuto = true;
 const quickStart = false;
@@ -426,6 +427,14 @@ export default class BattleScene extends Phaser.Scene {
 	}
 
 	launchBattle() {
+		this.arenaBg = this.addFieldSprite(0, 0, 'plains_bg');
+		this.arenaBgTransition = this.addFieldSprite(0, 0, `plains_bg`);
+
+		[ this.arenaBgTransition, this.arenaBg ].forEach(a => {
+			a.setScale(6);
+			a.setOrigin(0);
+		});
+
 		const field = this.add.container(0, 0);
 		field.setScale(6);
 
@@ -494,8 +503,6 @@ export default class BattleScene extends Phaser.Scene {
 
 		this.quickStart = this.quickStart || this.isButtonPressed(Button.QUICK_START);
 
-		this.arenaBg = this.addFieldSprite(0, 0, 'plains_bg');
-		this.arenaBgTransition = this.addFieldSprite(0, 0, `plains_bg`);
 		this.arenaPlayer = new ArenaBase(this, true);
 		this.arenaPlayerTransition = new ArenaBase(this, true);
 		this.arenaEnemy = new ArenaBase(this, false);
@@ -503,8 +510,9 @@ export default class BattleScene extends Phaser.Scene {
 
 		this.arenaBgTransition.setVisible(false);
 		this.arenaPlayerTransition.setVisible(false);
+		this.arenaNextEnemy.setVisible(false);
 
-		[ this.arenaBg, this.arenaBgTransition, this.arenaPlayer, this.arenaPlayerTransition, this.arenaEnemy, this.arenaNextEnemy ].forEach(a => {
+		[ this.arenaPlayer, this.arenaPlayerTransition, this.arenaEnemy, this.arenaNextEnemy ].forEach(a => {
 			if (a instanceof Phaser.GameObjects.Sprite)
 				a.setOrigin(0, 0);
 			field.add(a);
@@ -722,6 +730,7 @@ export default class BattleScene extends Phaser.Scene {
 		this.arenaPlayer.setPosition(300, 0);
 		this.arenaPlayerTransition.setPosition(0, 0);
 		[ this.arenaEnemy, this.arenaNextEnemy ].forEach(a => a.setPosition(-280, 0));
+		this.arenaNextEnemy.setVisible(false);
 
 		this.trainer.setTexture('trainer_m');
 		this.trainer.setPosition(406, 132);
@@ -827,7 +836,7 @@ export default class BattleScene extends Phaser.Scene {
 
 		if (!waveIndex && lastBattle) {
 			const isNewBiome = !(lastBattle.waveIndex % 10);
-			const resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER;
+			const resetArenaState = isNewBiome || this.currentBattle.battleType === BattleType.TRAINER || this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS;
 			this.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy());
 			this.trySpreadPokerus();
 			if (!isNewBiome && (newWaveIndex % 10) == 5)
@@ -876,6 +885,29 @@ export default class BattleScene extends Phaser.Scene {
 		return this.arena;
 	}
 
+	setFieldScale(scale: number, instant: boolean = false): Promise<void> {
+		return new Promise(resolve => {
+			scale *= 6;
+			if (this.field.scale === scale)
+				return resolve();
+
+			const defaultWidth = this.arenaBg.width * 6;
+			const defaultHeight = this.arenaBg.height * 6;
+			const scaledWidth = this.arenaBg.width * scale;
+			const scaledHeight = this.arenaBg.height * scale;
+
+			this.tweens.add({
+				targets: this.field,
+				scale: scale,
+				x: (defaultWidth - scaledWidth) / 2,
+				y: defaultHeight - scaledHeight,
+				duration: !instant ? Utils.fixedInt(Math.abs(this.field.scale - scale) * 200) : 0,
+				ease: 'Sine.easeInOut',
+				onComplete: () => resolve()
+			});
+		});
+	}
+
 	getSpeciesFormIndex(species: PokemonSpecies, gender?: Gender, nature?: Nature, ignoreArena?: boolean): integer {
 		if (!species.forms?.length)
 			return 0;
@@ -1240,9 +1272,9 @@ export default class BattleScene extends Phaser.Scene {
 			duration = 500;
 		if (destroy === undefined)
       destroy = true;
-    const bgm = this.sound.get(this.bgm.key);
+    const bgm = this.sound.getAllPlaying().find(bgm => bgm.key === this.bgm.key);
 		if (bgm) {
-			SoundFade.fadeOut(this, bgm, duration, destroy);
+			SoundFade.fadeOut(this, this.bgm, duration, destroy);
 			return true;
 		}
 
diff --git a/src/data/move.ts b/src/data/move.ts
index ba786539b94..e2c1808f123 100644
--- a/src/data/move.ts
+++ b/src/data/move.ts
@@ -1614,10 +1614,6 @@ export class FaintCountdownAttr extends AddBattlerTagAttr {
 
     return true;
   }
-
-  getCondition(): MoveConditionFunc {
-    return (user, target, move) => super.getCondition()(user, target, move) && !target.isBossImmune();
-  }
 }
 
 export class HitsTagAttr extends MoveAttr {
@@ -2007,6 +2003,8 @@ export class DiscourageFrequentUseAttr extends MoveAttr {
 
 const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY);
 
+const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
+
 export type MoveAttrFilter = (attr: MoveAttr) => boolean;
 
 function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<void> {
@@ -2602,6 +2600,7 @@ export function initMoves() {
       .attr(FaintCountdownAttr)
       .ignoresProtect()
       .soundBased()
+      .condition(failOnBossCondition)
       .target(MoveTarget.ALL),
     new AttackMove(Moves.ICY_WIND, "Icy Wind", Type.ICE, MoveCategory.SPECIAL, 55, 95, 15, 34, "The user attacks with a gust of chilled air. This also lowers opposing Pokémon's Speed stats.", 100, 0, 2)
       .attr(StatChangeAttr, BattleStat.SPD, -1)
@@ -2663,7 +2662,8 @@ export function initMoves() {
     new StatusMove(Moves.SAFEGUARD, "Safeguard (N)", Type.NORMAL, -1, 25, -1, "The user creates a protective field that prevents status conditions for five turns.", -1, 0, 2)
       .target(MoveTarget.USER_SIDE),
     new StatusMove(Moves.PAIN_SPLIT, "Pain Split", Type.NORMAL, -1, 20, -1, "The user adds its HP to the target's HP, then equally shares the combined HP with the target.", -1, 0, 2)
-      .attr(HpSplitAttr),
+      .attr(HpSplitAttr)
+      .condition(failOnBossCondition),
     new AttackMove(Moves.SACRED_FIRE, "Sacred Fire", Type.FIRE, MoveCategory.PHYSICAL, 100, 95, 5, -1, "The target is razed with a mystical fire of great intensity. This may also leave the target with a burn.", 50, 0, 2)
       .attr(StatusEffectAttr, StatusEffect.BURN)
       .makesContact(false),
@@ -2796,7 +2796,8 @@ export function initMoves() {
       .condition((user, target, move) => !target.status),
     new AttackMove(Moves.KNOCK_OFF, "Knock Off (N)", Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, "The user slaps down the target's held item, and that item can't be used in that battle. The move does more damage if the target has a held item.", -1, 0, 3),
     new AttackMove(Moves.ENDEAVOR, "Endeavor", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, "This attack move cuts down the target's HP to equal the user's HP.", -1, 0, 3)
-      .attr(MatchHpAttr),
+      .attr(MatchHpAttr)
+      .condition(failOnBossCondition),
     new AttackMove(Moves.ERUPTION, "Eruption", Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, "The user attacks opposing Pokémon with explosive fury. The lower the user's HP, the lower the move's power.", -1, 0, 3)
       .attr(HpPowerAttr)
       .target(MoveTarget.ALL_NEAR_ENEMIES),
diff --git a/src/form-change-phase.ts b/src/form-change-phase.ts
index 439ffa62263..44b3e2b8e60 100644
--- a/src/form-change-phase.ts
+++ b/src/form-change-phase.ts
@@ -235,7 +235,7 @@ export class QuietFormChangePhase extends BattlePhase {
           this.scene.tweens.add({
             targets: pokemonFormTintSprite,
             delay: 250,
-            scale: 1,
+            scale: this.pokemon.getSpriteScale(),
             ease: 'Cubic.easeInOut',
             duration: 500,
             onComplete: () => {
diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts
index 50cefcab8bd..2a3bf79e67b 100644
--- a/src/pipelines/sprite.ts
+++ b/src/pipelines/sprite.ts
@@ -25,6 +25,7 @@ uniform vec3 duskTint;
 uniform vec3 nightTint;
 uniform int hasShadow;
 uniform int yCenter;
+uniform float fieldScale;
 uniform float vCutoff;
 uniform vec2 relPosition;
 uniform vec2 size;
@@ -115,8 +116,8 @@ void main()
     if (hasShadow == 1) {
         float width = size.x - (yOffset / 2.0);
 
-        float spriteX = ((floor(outPosition.x / 6.0) - relPosition.x) / width) + 0.5;
-        float spriteY = ((floor(outPosition.y / 6.0) - relPosition.y) / size.y);
+        float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5;
+        float spriteY = ((floor(outPosition.y / fieldScale) - relPosition.y) / size.y);
 
         if (yCenter == 1) {
             spriteY += 0.5;
@@ -213,14 +214,19 @@ export default class SpritePipeline extends FieldSpritePipeline {
         const fusionSpriteColors = (ignoreOverride && data['fusionSpriteColorsBase']) || data['fusionSpriteColors'] || [] as number[][];
 
         const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer;
+        const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
+        const fieldScaleRatio = field.scale / 6;
         const position = isEntityObj
             ? [ sprite.parentContainer.x, sprite.parentContainer.y ]
             : [ sprite.x, sprite.y ];
-        position[0] += -(sprite.width - sprite.frame.width) / 2 + sprite.frame.x;
+        position[0] += field.x / field.scale;
+        position[1] += field.y / field.scale;
+        position[0] += -(sprite.width - (sprite.frame.width)) / 2 + sprite.frame.x;
         if (sprite.originY === 0.5)
             position[1] += (sprite.height / 2) * ((isEntityObj ? sprite.parentContainer : sprite).scale - 1);
         this.set1i('hasShadow', hasShadow ? 1 : 0);
         this.set1i('yCenter', sprite.originY === 0.5 ? 1 : 0);
+        this.set1f('fieldScale', field.scale);
         this.set2f('relPosition', position[0], position[1]);
         this.set2f('size', sprite.frame.width, sprite.height);
         this.set1f('yOffset', sprite.height - sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
@@ -247,14 +253,16 @@ export default class SpritePipeline extends FieldSpritePipeline {
         const hasShadow = sprite.pipelineData['hasShadow'] as boolean;
         if (hasShadow) {
             const isEntityObj = sprite.parentContainer instanceof Pokemon || sprite.parentContainer instanceof Trainer;
+            const field = isEntityObj ? sprite.parentContainer.parentContainer : sprite.parentContainer;
+            const fieldScaleRatio = field.scale / 6;
             const baseY = (isEntityObj
                 ? sprite.parentContainer.y
-                : sprite.y + sprite.height) * 6;
-            const bottomPadding = Math.ceil(sprite.height * 0.05) * 6;
-            const yDelta = (baseY - y1) / 6;
+                : sprite.y + sprite.height) * 6 / fieldScaleRatio;
+            const bottomPadding = Math.ceil(sprite.height * 0.05) * 6 / fieldScaleRatio;
+            const yDelta = (baseY - y1) / field.scale;
             y2 = y1 = baseY + bottomPadding;
-            const pixelHeight = ((v1 - v0) / (sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale)));
-            v1 += (yDelta + bottomPadding / 6) * pixelHeight;
+            const pixelHeight = (v1 - v0) / (sprite.frame.height * (isEntityObj ? sprite.parentContainer.scale : sprite.scale));
+            v1 += (yDelta + bottomPadding / field.scale) * pixelHeight;
         }
         
         return super.batchQuad(gameObject, x0, y0, x1, y1, x2, y2, x3, y3, u0, v0, u1, v1, tintTL, tintTR, tintBL, tintBR, tintEffect, texture, unit);
diff --git a/src/pokemon.ts b/src/pokemon.ts
index 688159cc8b5..0cace02c03c 100644
--- a/src/pokemon.ts
+++ b/src/pokemon.ts
@@ -399,6 +399,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
   }
 
   getSpriteScale(): number {
+    if (this.species.speciesId === Species.ETERNATUS && this.formIndex)
+      return 1.5;
     return 1;
   }
 
@@ -1565,8 +1567,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
     if (!this.maskEnabled) {
       this.maskSprite = this.getTintSprite();
       this.maskSprite.setVisible(true);
-      this.maskSprite.setPosition(this.x * 6, this.y * 6);
-      this.maskSprite.setScale(6 * this.getSpriteScale());
+      this.maskSprite.setPosition(this.x * this.parentContainer.scale + this.parentContainer.x,
+                                  this.y * this.parentContainer.scale + this.parentContainer.y);
+      this.maskSprite.setScale(this.getSpriteScale() * this.parentContainer.scale);
       this.maskEnabled = true;
     }
   }