예제 #1
0
 private void Close()
 {
     UILoader.GetUIState <RichTextBox>().Visible = false;
     RichTextBox.SetData(null, "", "");
     Main.player[Main.myPlayer].talkNPC = -1;
     textState = 0;
 }
예제 #2
0
        public override bool NewRightClick(int i, int j)
        {
            var tile = (Tile)Framing.GetTileSafely(i, j).Clone();

            if (CanOpen(Main.LocalPlayer) && tile.frameX < 32)
            {
                for (int x = 0; x < 2; x++)
                {
                    for (int y = 0; y < 4; y++)
                    {
                        int realX = x + i - tile.frameX / 18;
                        int realY = y + j - tile.frameY / 18;

                        Framing.GetTileSafely(realX, realY).frameX += 36;
                    }
                }

                Loot[] smallLoot = new Loot[5];

                List <Loot> types = Helper.RandomizeList(SmallLootPool);
                for (int k = 0; k < 5; k++)
                {
                    smallLoot[k] = types[k];
                }

                UILoader.GetUIState <LootUI>().SetItems(GoldLootPool[Main.rand.Next(GoldLootPool.Count)], smallLoot);
                UILoader.GetUIState <LootUI>().Visible = true;
                return(true);
            }
            return(false);
        }
예제 #3
0
        public override void RightClick(Player player)
        {
            UpdateBagSlots();

            item.stack++;
            ChefBagUI.openBag = this;
            UILoader.GetUIState <ChefBagUI>().OnInitialize();
        }
예제 #4
0
        private void Bless()
        {
            UILoader.GetUIState <RichTextBox>().Visible = false;
            RichTextBox.SetData(null, "", "");
            Main.player[Main.myPlayer].talkNPC = -1;

            Main.LocalPlayer.AddBuff(BuffType <SpikeImmuneBuff>(), 3600); //TODO: this may need manual sync later? not sure (Remember this is really being called from a UI)
        }
예제 #5
0
        public override string GetChat()
        {
            textState = 0;
            UILoader.GetUIState <RichTextBox>().Visible = true;
            RichTextBox.ClearButtons();

            SetData();
            RichTextBox.AddButton("[]Next", Debug);

            return("");
        }
예제 #6
0
        private void OpenEnchantUI()
        {
            EnchantmentMenu.SetActive(npc.Center + new Vector2(0, -300), this);
            //ZoomHandler.SetZoomAnimation(2.5f, 60);
            Main.LocalPlayer.GetModPlayer <StarlightPlayer>().ScreenMoveTarget = npc.Center + new Vector2(0, -300);
            Main.LocalPlayer.GetModPlayer <StarlightPlayer>().ScreenMoveTime   = 120;
            Main.LocalPlayer.GetModPlayer <StarlightPlayer>().ScreenMoveHold   = true;
            Main.LocalPlayer.talkNPC = -1;
            enchanting = true;

            UILoader.GetUIState <RichTextBox>().Visible = false;
        }
예제 #7
0
        public static void AddButton(string message, Action onClick)
        {
            RichTextButton add = new RichTextButton(message, onClick);

            add.Top.Set(HeightOff, 0);
            add.Left.Set(50 + Main.screenWidth / 2 - 260 + widthOff, 0);
            add.Width.Set(Markdown.GetWidth(message, 1) + 20, 0);
            add.Height.Set(32, 0);

            widthOff += Markdown.GetWidth(message, 1) + 24;
            UILoader.GetUIState <RichTextBox>().Append(add);
        }
예제 #8
0
 public override void OnChatButtonClicked(bool firstButton, ref bool shop)
 {
     if (firstButton)
     {
         shop = true;
     }
     else
     {
         UILoader.GetUIState <TownQuestList>().Visible = true;
         UILoader.GetUIState <TownQuestList>().PopulateList();
     }
 }
예제 #9
0
        public override string GetChat()
        {
            textState = 0;
            UILoader.GetUIState <RichTextBox>().Visible = true;
            RichTextBox.ClearButtons();

            SetData();
            RichTextBox.AddButton("[]Chat", Chat);
            RichTextBox.AddButton("[<color:200, 140, 255>]Enchant", OpenEnchantUI);
            RichTextBox.AddButton("[]Move Altar", PackUp);

            return("");
        }
예제 #10
0
        public override void AI()
        {
            if (RichTextBox.talking == npc)
            {
                SetData();

                if (Main.player[Main.myPlayer].talkNPC != npc.whoAmI)
                {
                    UILoader.GetUIState <RichTextBox>().Visible = false;
                    RichTextBox.SetData(null, "", "");
                }
            }
        }
예제 #11
0
        public override bool NewRightClick(int i, int j)
        {
            var state = UILoader.GetUIState <CookingUI>();

            if (!state.Visible)
            {
                state.Visible = true; Main.PlaySound(SoundID.MenuOpen);
            }
            else
            {
                state.Visible = false; Main.PlaySound(SoundID.MenuClose);
            }
            return(true);
        }
예제 #12
0
        public override bool NewRightClick(int i, int j)
        {
            if (CanOpen(Main.LocalPlayer))
            {
                WorldGen.KillTile(i, j);
                Loot[] smallLoot = new Loot[5];

                List <Loot> types = Helper.RandomizeList(SmallLootPool);
                for (int k = 0; k < 5; k++)
                {
                    smallLoot[k] = types[k];
                }

                UILoader.GetUIState <LootUI>().SetItems(GoldLootPool[Main.rand.Next(GoldLootPool.Count)], smallLoot);
                UILoader.GetUIState <LootUI>().Visible = true;
                return(true);
            }
            return(false);
        }
예제 #13
0
        public override void PickupEffects(Player player)
        {
            AbilityHandler ah = player.GetHandler();

            ah.Shards.Add(Parent.frameX);

            if (ah.ShardCount % 3 == 0)
            {
                UILoader.GetUIState <TextCard>().Display("Stamina Vessel", "Your maximum stamina has increased by 1", null, 240, 0.8f);
            }
            else
            {
                UILoader.GetUIState <TextCard>().Display("Stamina Vessel Shard", "Collect " + (3 - ah.ShardCount % 3) + " more to increase your maximum stamina", null, 240, 0.6f);
            }

            player.GetModPlayer <Core.StarlightPlayer>().MaxPickupTimer = 1;

            Helper.UnlockEntry <StaminaShardEntry>(Main.LocalPlayer);
        }
예제 #14
0
        public override void PickupVisuals(int timer)
        {
            if (timer == 1)
            {
                Main.PlaySound(mod.GetLegacySoundSlot(SoundType.Custom, "Sounds/Pickups/get")); //start the SFX
                Filters.Scene.Deactivate("Shockwave");
            }

            if (timer < 119)
            {
            }

            if (timer == 119)
            {
                string message = "Open the codex from your inventory to learn about the world.";

                UILoader.GetUIState <TextCard>().Display("Starlight Codex", message, null, 240);
                Helper.UnlockEntry <CodexEntry>(Main.LocalPlayer);
            }
        }
        private string SetUpgradeUI(On.Terraria.NPC.orig_GetChat orig, NPC self)
        {
            if (StarlightWorld.TownUpgrades.TryGetValue(self.TypeName, out bool unlocked))
            {
                if (unlocked)
                {
                    UILoader.GetUIState <ChatboxOverUI>().SetState(TownUpgrade.FromString(self.TypeName));
                }
                else
                {
                    UILoader.GetUIState <ChatboxOverUI>().SetState(new LockedUpgrade());
                }
            }
            else
            {
                UILoader.GetUIState <ChatboxOverUI>().SetState(null);
            }

            return(orig(self));
        }
예제 #16
0
        public static void UnlockEntry <type>(Player player)
        {
            CodexHandler mp    = player.GetModPlayer <CodexHandler>();
            CodexEntry   entry = mp.Entries.FirstOrDefault(n => n is type);

            if (entry.RequiresUpgradedBook && mp.CodexState != 2)
            {
                return;                                                   //dont give the player void entries if they dont have the void book
            }
            if (entry.Locked)
            {
                entry.Locked = false;
                entry.New    = true;
                if (mp.CodexState != 0)
                {
                    UILoader.GetUIState <CodexPopup>().TripEntry(entry.Title);
                }
                Main.PlaySound(SoundID.Item30);
            }
        }
예제 #17
0
        public static void UnlockEntry <type>(Player player)
        {
            CodexHandler mp    = player.GetModPlayer <CodexHandler>();
            CodexEntry   entry = mp.Entries.FirstOrDefault(n => n is type);

            if (entry is null || (entry.RequiresUpgradedBook && mp.CodexState != 2))
            {
                return; //dont give the player void entries if they dont have the void book
            }
            if (entry.Locked)
            {
                entry.Locked = false;
                entry.New    = true;

                if (mp.CodexState != 0)
                {
                    UILoader.GetUIState <CodexPopup>().TripEntry(entry.Title, entry.Icon);
                    Main.PlaySound(StarlightRiver.Instance.GetLegacySoundSlot(SoundType.Custom, "Sounds/CodexUnlock"));
                }
            }
        }
예제 #18
0
        public override void PreUpdate()
        {
            if (PickupTarget != null)
            {
                PickupTimer++;

                player.immune        = true;
                player.immuneTime    = 5;
                player.immuneNoBlink = true;

                player.Center = PickupTarget.Center;
                if (PickupTimer >= MaxPickupTimer)
                {
                    PickupTarget = null;
                }
            }
            else
            {
                PickupTimer = 0;
            }

            platformTimer--;

            if (Main.netMode != NetmodeID.Server)
            {
                var staminaState    = UILoader.GetUIState <Stamina>();
                var infusionState   = UILoader.GetUIState <Infusion>();
                var codexState      = UILoader.GetUIState <Content.GUI.Codex>();
                var collectionState = UILoader.GetUIState <Collection>();

                AbilityHandler mp = player.GetHandler();

                staminaState.Visible  = false;
                infusionState.Visible = false;

                if (mp.AnyUnlocked)
                {
                    staminaState.Visible = true;
                }

                if (Main.playerInventory)
                {
                    if (player.chest == -1 && Main.npcShop == 0)
                    {
                        collectionState.Visible         = true;
                        Content.GUI.Codex.ButtonVisible = true;
                        if (mp.AnyUnlocked)
                        {
                            infusionState.Visible = true;
                        }
                    }
                    else
                    {
                        collectionState.Visible         = false;
                        Content.GUI.Codex.ButtonVisible = false;
                        if (mp.AnyUnlocked)
                        {
                            infusionState.Visible = false;
                        }
                    }
                }
                else
                {
                    collectionState.Visible         = false;
                    Collection.ActiveAbility        = null;
                    Content.GUI.Codex.ButtonVisible = false;
                    Content.GUI.Codex.Open          = false;
                    infusionState.Visible           = false;
                }
            }

            if (DarkSlow)
            {
                player.velocity.X *= 0.8f;
            }
            DarkSlow = false;

            if (!player.immune)
            {
                VitricSpike.CollideWithSpikes(player, out int damage);
                if (damage > 0)
                {
                    player.Hurt(PlayerDeathReason.ByCustomReason(player.name + " was impaled by glass shards."), damage, 0);
                }
            }
        }
예제 #19
0
        public override void PickupVisuals(int timer)
        {
            Player player = Main.LocalPlayer;

            //blood spray and sounds
            if (timer == 15 || (timer > 120 && timer < 220 && timer % 10 == 8))
            {
                for (int k = 0; k < 20; k++)
                {
                    Dust.NewDustPerfect(player.Center, DustID.Blood, Vector2.UnitY.RotatedBy(timer / 10f - 1.57f).RotatedByRandom(0.25f) * Main.rand.NextFloat(10));
                }

                player.headPosition = Vector2.UnitX.RotatedByRandom(3.14f) * Main.rand.NextFloat(5);
                player.bodyPosition = Vector2.UnitX.RotatedByRandom(3.14f) * Main.rand.NextFloat(5);
                player.legPosition  = Vector2.UnitX.RotatedByRandom(3.14f) * Main.rand.NextFloat(5);

                Helper.PlayPitched("Impale", 1, Main.rand.NextFloat(0.6f, 0.9f), player.Center);
                Main.PlaySound(SoundID.PlayerHit, player.Center);
            }

            if (timer >= 15)
            {
                CreepyVignette.visible     = true;
                CreepyVignette.opacityMult = 0.5f;
                CreepyVignette.holeSize    = Vector2.One * 400;
            }

            if (timer == 360)
            {
                Helper.PlayPitched("Impale", 1, Main.rand.NextFloat(0.6f, 0.9f), player.Center); //placeholder sound
            }

            if (timer >= 360)
            {
                CreepyVignette.offset      = Vector2.SmoothStep(Vector2.Zero, new Vector2(-Main.screenWidth / 2 + 100, -Main.screenHeight / 2 + 300), Math.Min(1, (timer - 360) / 120f));
                CreepyVignette.opacityMult = 0.5f + Math.Min(1, (timer - 360) / 120f) * 0.25f;
                CreepyVignette.holeSize    = Vector2.One * (400 - Math.Min(1, (timer - 360) / 120f) * 64);
            }

            if (timer < 360)
            {
                if (Main.playerInventory)
                {
                    player.controlInv       = true;
                    player.releaseInventory = true;
                    Main.playerInventory    = false;
                }
            }

            if (timer >= 360)
            {
                if (!Main.playerInventory)
                {
                    player.ToggleInv();
                    Main.playerInventory = true;
                }
            }

            if (timer > 530)
            {
                CreepyVignette.visible     = false;
                CreepyVignette.offset      = new Vector2(-Main.screenWidth / 2 + 100, -Main.screenHeight / 2 + 300);
                CreepyVignette.opacityMult = 0.75f - Math.Min(1, (timer - 530) / 30f) * 0.75f;
                CreepyVignette.holeSize    = Vector2.One * (336 + (Math.Min(1, (timer - 530) / 30f) * 512));
            }

            if (timer == 559)
            {
                UILoader.GetUIState <TextCard>().Display("Mysterious Technology", "What has it done to you?", time: 360);
                Helper.UnlockEntry <InfusionEntry>(Main.LocalPlayer);

                player.headPosition = Vector2.Zero;
                player.bodyPosition = Vector2.Zero;
                player.legPosition  = Vector2.Zero;
            }
        }
예제 #20
0
 public static void ClearButtons()
 {
     widthOff = 0;
     UILoader.GetUIState <RichTextBox>().Elements.Clear();
 }
예제 #21
0
        public override void AI()
        {
            //Ticks the timer
            GlobalTimer++;
            AttackTimer++;

            if (Phase != (int)AIStates.Leaving && arena != new Rectangle() && !Main.player.Any(n => n.active && n.statLife > 0 && n.Hitbox.Intersects(arena))) //if no valid players are detected
            {
                GlobalTimer = 0;
                Phase       = (int)AIStates.Leaving; //begone thot!
                crystals.ForEach(n => n.ai[2] = 4);
                crystals.ForEach(n => n.ai[1] = 0);
            }

            switch (Phase)
            {
            //on spawn effects
            case (int)AIStates.SpawnEffects:

                StarlightPlayer mp = Main.LocalPlayer.GetModPlayer <StarlightPlayer>();
                mp.ScreenMoveTarget = npc.Center + new Vector2(0, -850);
                mp.ScreenMoveTime   = 600;
                UILoader.GetUIState <TextCard>().Display(npc.FullName, Main.rand.Next(10000) == 0 ? "Glass tax returns" : "Shattered Sentinel", null, 500); //Screen pan + intro text

                for (int k = 0; k < Main.maxNPCs; k++)                                                                                                      //finds all the large platforms to add them to the list of possible locations for the nuke attack
                {
                    NPC npc = Main.npc[k];
                    if (npc?.active == true && (npc.type == NPCType <VitricBossPlatformUp>() || npc.type == NPCType <VitricBossPlatformDown>()))
                    {
                        crystalLocations.Add(npc.Center + new Vector2(0, -48));
                    }
                }

                ChangePhase(AIStates.SpawnAnimation, true);
                break;

            case (int)AIStates.SpawnAnimation:     //the animation that plays while the boss is spawning and the title card is shown

                if (GlobalTimer == 2)
                {
                    npc.friendly = true;   //so he wont kill you during the animation
                    RandomizeTarget();     //pick a random target so the eyes will follow them
                }

                if (GlobalTimer <= 200)
                {
                    npc.Center += new Vector2(0, -4f);                         //rise up
                }
                if (GlobalTimer > 200 && GlobalTimer <= 300)
                {
                    npc.scale = 0.5f + (GlobalTimer - 200) / 200f; //grow
                }
                if (GlobalTimer > 280)                             //summon crystal babies
                {
                    for (int k = 0; k <= 4; k++)
                    {
                        if (GlobalTimer == 280 + k * 30)
                        {
                            Vector2 target = new Vector2(npc.Center.X + (-100 + k * 50), StarlightWorld.VitricBiome.Top * 16 + 1100);
                            int     index  = NPC.NewNPC((int)target.X, (int)target.Y, NPCType <VitricBossCrystal>(), 0, 2); //spawn in state 2: sandstone forme
                            (Main.npc[index].modNPC as VitricBossCrystal).Parent    = this;
                            (Main.npc[index].modNPC as VitricBossCrystal).StartPos  = target;
                            (Main.npc[index].modNPC as VitricBossCrystal).TargetPos = npc.Center + new Vector2(0, -120).RotatedBy(6.28f / 4 * k);
                            crystals.Add(Main.npc[index]);     //add this crystal to the list of crystals the boss controls
                        }
                    }
                }

                if (GlobalTimer > 620)               //start the fight
                {
                    npc.dontTakeDamage = false;      //make him vulnerable
                    npc.friendly       = false;      //and hurt when touched
                    homePos            = npc.Center; //set the NPCs home so it can return here after attacks
                    int index = NPC.NewNPC((int)npc.Center.X, (int)npc.Center.Y, NPCType <ArenaBottom>());
                    (Main.npc[index].modNPC as ArenaBottom).Parent = this;
                    ChangePhase(AIStates.FirstPhase, true);
                    ResetAttack();

                    const int arenaWidth  = 1408;
                    const int arenaHeight = 900;
                    arena = new Rectangle((int)npc.Center.X - arenaWidth / 2, (int)npc.Center.Y - arenaHeight / 2, arenaWidth, arenaHeight);
                }
                break;

            case (int)AIStates.FirstPhase:

                int healthGateAmount = npc.lifeMax / 7;
                if (npc.life <= npc.lifeMax - (1 + crystals.Count(n => n.ai[0] == 3 || n.ai[0] == 1)) * healthGateAmount && !npc.dontTakeDamage)
                {
                    npc.dontTakeDamage = true;                                                                                         //boss is immune at phase gate
                    npc.life           = npc.lifeMax - (1 + crystals.Count(n => n.ai[0] == 3 || n.ai[0] == 1)) * healthGateAmount - 1; //set health at phase gate
                    Main.PlaySound(SoundID.ForceRoar, npc.Center);
                }

                if (AttackTimer == 1)     //switching out attacks
                {
                    if (npc.dontTakeDamage)
                    {
                        AttackPhase = 0; //nuke attack once the boss turns immortal for a chance to break a crystal
                    }
                    else                 //otherwise proceed with attacking pattern
                    {
                        AttackPhase++;
                        if (AttackPhase > 4)
                        {
                            AttackPhase = 1;
                        }
                    }
                }

                switch (AttackPhase)     //switch for crystal behavior
                {
                case 0: NukePlatforms(); break;

                case 1: CrystalCage(); break;

                case 2: CrystalSmash(); break;

                case 3: RandomSpikes(); break;

                case 4: PlatformDash(); break;
                }
                break;

            case (int)AIStates.Anger:     //the short anger phase attack when the boss loses a crystal
                AngerAttack();
                break;

            case (int)AIStates.FirstToSecond:

                if (GlobalTimer == 2)
                {
                    foreach (NPC crystal in crystals)
                    {
                        crystal.ai[0] = 0;
                        crystal.ai[2] = 5;     //turn the crystals to transform mode
                    }
                }

                if (GlobalTimer == 120)
                {
                    SetFrameX(1);
                    foreach (NPC crystal in crystals)     //kill all the crystals
                    {
                        crystal.Kill();
                    }
                    npc.friendly = true;     //so we wont get contact damage
                }

                if (GlobalTimer > 120)
                {
                    foreach (Player player in Main.player)
                    {
                        if (Abilities.AbilityHelper.CheckDash(player, npc.Hitbox))     //boss should be dashable now, when dashed:
                        {
                            player.immune     = true;
                            player.immuneTime = 60;
                            SetFrameX(2);
                            ChangePhase(AIStates.SecondPhase, true); //go on to the next phase
                            ResetAttack();                           //reset attack
                            foreach (NPC wall in Main.npc.Where(n => n.modNPC is VitricBackdropLeft))
                            {
                                wall.ai[1] = 3;                                                                           //make the walls scroll
                            }
                            foreach (NPC plat in Main.npc.Where(n => n.modNPC is VitricBossPlatformUp))
                            {
                                plat.ai[0] = 1;                                                                             //make the platforms scroll
                            }
                            break;
                        }
                    }
                }

                /*if (GlobalTimer > 900) //after waiting too long, wipe all players
                 * {
                 *  foreach (Player player in Main.player.Where(n => n.Hitbox.Intersects(arena)))
                 *  {
                 *      player.KillMe(Terraria.DataStructures.PlayerDeathReason.ByCustomReason(player.name + " was shattered..."), double.MaxValue, 0);
                 *  }
                 *  ChangePhase(AIStates.Leaving, true);
                 * }*/
                break;

            case (int)AIStates.SecondPhase:

                if (GlobalTimer == 60)
                {
                    npc.dontTakeDamage = false;     //damagable again
                    npc.friendly       = false;
                }

                if (GlobalTimer == 1)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "VortexHasASmallPussy");                       //handles the music transition
                }
                if (GlobalTimer == 2)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "Sounds/Music/GlassBossTransition");
                }
                if (GlobalTimer == 701)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "VortexHasASmallPussy");
                }
                if (GlobalTimer == 702)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "Sounds/Music/GlassBoss2");
                }

                if (GlobalTimer > 702 && GlobalTimer < 760)     //no fadein
                {
                    for (int k = 0; k < Main.musicFade.Length; k++)
                    {
                        if (k == Main.curMusic)
                        {
                            Main.musicFade[k] = 1;
                        }
                    }
                }

                if (AttackTimer == 1)     //switching out attacks
                {
                    AttackPhase++;
                    if (AttackPhase > 3)
                    {
                        AttackPhase = 0;
                    }
                }

                switch (AttackPhase)     //switch for crystal behavior
                {
                case 0: Volley(); break;

                case 1: Mines(); break;

                case 2: Whirl(); break;

                case 3: Rest(); break;
                }
                break;

            case (int)AIStates.Leaving:

                npc.position.Y += 7;

                if (GlobalTimer >= 180)
                {
                    npc.active = false;     //leave
                    foreach (NPC npc in Main.npc.Where(n => n.modNPC is VitricBackdropLeft || n.modNPC is VitricBossPlatformUp))
                    {
                        npc.active = false;                                                                                                              //arena reset
                    }
                }
                break;

            case (int)AIStates.Dying:

                if (GlobalTimer == 1)
                {
                    foreach (NPC npc in Main.npc.Where(n => n.modNPC is VitricBackdropLeft || n.modNPC is VitricBossPlatformUp))
                    {
                        npc.ai[1] = 4;
                    }
                    startPos = npc.Center;
                }

                if (GlobalTimer < 60)
                {
                    npc.Center = Vector2.SmoothStep(startPos, homePos, GlobalTimer / 60f);
                }

                if (GlobalTimer == 60)
                {
                    Main.PlaySound(SoundID.DD2_WinScene, npc.Center);                        //SFX
                }
                if (GlobalTimer > 60 && GlobalTimer < 120)
                {
                    Main.musicFade[Main.curMusic] = 1 - (GlobalTimer - 60) / 60f;
                }

                if (GlobalTimer > 120)
                {
                    Main.musicFade[Main.curMusic] = 0;
                    for (int k = 0; k < 10; k++)
                    {
                        Dust.NewDustPerfect(npc.Center, DustType <Dusts.Sand>(), Vector2.One.RotatedByRandom(6.28f) * Main.rand.NextFloat(10), 40, default, 2);
                    }
                    for (int k = 0; k < 2; k++)
                    {
                        Dust.NewDustPerfect(npc.Center, DustType <Dusts.Starlight>(), Vector2.One.RotatedByRandom(6.28f) * Main.rand.NextFloat(100));
                    }
                }
        public override void AI()
        {
            //Ticks the timer
            GlobalTimer++;
            AttackTimer++;

            //twisting
            if (twistTimer < maxTwistTimer)
            {
                twistTimer++;
            }

            if (twistTimer == maxTwistTimer)
            {
                lastTwistState = twistTarget;
            }

            //Main AI
            body.UpdateBody();                                                                                                                                 //update the physics on the body
            Lighting.AddLight(npc.Center, new Vector3(1, 0.8f, 0.4f));                                                                                         //glow

            if (Phase != (int)AIStates.Leaving && arena != new Rectangle() && !Main.player.Any(n => n.active && n.statLife > 0 && n.Hitbox.Intersects(arena))) //if no valid players are detected
            {
                GlobalTimer = 0;
                Phase       = (int)AIStates.Leaving; //begone thot!
                crystals.ForEach(n => n.ai[2] = 4);
                crystals.ForEach(n => n.ai[1] = 0);
            }

            switch (Phase)
            {
            //on spawn effects
            case (int)AIStates.SpawnEffects:

                StarlightPlayer mp = Main.LocalPlayer.GetModPlayer <StarlightPlayer>();
                mp.ScreenMoveTarget = npc.Center + new Vector2(0, -850);
                mp.ScreenMoveTime   = 600;
                UILoader.GetUIState <TextCard>().Display(npc.FullName, Main.rand.Next(10000) == 0 ? "Glass tax returns" : "Shattered Sentinel", null, 500); //Screen pan + intro text

                for (int k = 0; k < Main.maxNPCs; k++)                                                                                                      //finds all the large platforms to add them to the list of possible locations for the nuke attack
                {
                    NPC npc = Main.npc[k];
                    if (npc?.active == true && (npc.type == NPCType <VitricBossPlatformUp>() || npc.type == NPCType <VitricBossPlatformDown>()))
                    {
                        crystalLocations.Add(npc.Center + new Vector2(0, -48));
                    }
                }

                const int arenaWidth  = 1408;
                const int arenaHeight = 900;
                arena = new Rectangle((int)npc.Center.X - arenaWidth / 2, (int)npc.Center.Y - 800 - arenaHeight / 2, arenaWidth, arenaHeight);

                ChangePhase(AIStates.SpawnAnimation, true);
                break;

            case (int)AIStates.SpawnAnimation:     //the animation that plays while the boss is spawning and the title card is shown

                if (GlobalTimer == 2)
                {
                    npc.friendly = true;   //so he wont kill you during the animation
                    RandomizeTarget();     //pick a random target so the eyes will follow them
                }

                if (GlobalTimer <= 200)
                {
                    npc.Center += new Vector2(0, -4f); //rise up
                }
                if (GlobalTimer > 280)                 //summon crystal babies
                {
                    for (int k = 0; k <= 4; k++)
                    {
                        if (GlobalTimer == 280 + k * 30)
                        {
                            Vector2 target = new Vector2(npc.Center.X + (-100 + k * 50), StarlightWorld.VitricBiome.Top * 16 + 1100);
                            int     index  = NPC.NewNPC((int)target.X, (int)target.Y, NPCType <VitricBossCrystal>(), 0, 2); //spawn in state 2: sandstone forme
                            (Main.npc[index].modNPC as VitricBossCrystal).Parent    = this;
                            (Main.npc[index].modNPC as VitricBossCrystal).StartPos  = target;
                            (Main.npc[index].modNPC as VitricBossCrystal).TargetPos = npc.Center + new Vector2(0, -120).RotatedBy(6.28f / 4 * k);
                            crystals.Add(Main.npc[index]);     //add this crystal to the list of crystals the boss controls
                        }
                    }
                }

                if (GlobalTimer > 620)               //start the fight
                {
                    npc.dontTakeDamage = false;      //make him vulnerable
                    npc.friendly       = false;      //and hurt when touched
                    homePos            = npc.Center; //set the NPCs home so it can return here after attacks
                    int index = NPC.NewNPC((int)npc.Center.X, (int)npc.Center.Y, NPCType <ArenaBottom>());
                    (Main.npc[index].modNPC as ArenaBottom).Parent = this;
                    ChangePhase(AIStates.FirstPhase, true);
                    ResetAttack();
                }
                break;

            case (int)AIStates.FirstPhase:

                int healthGateAmount = npc.lifeMax / 7;
                if (npc.life <= npc.lifeMax - (1 + crystals.Count(n => n.ai[0] == 3 || n.ai[0] == 1)) * healthGateAmount && !npc.dontTakeDamage)
                {
                    npc.dontTakeDamage = true;                                                                                         //boss is immune at phase gate
                    npc.life           = npc.lifeMax - (1 + crystals.Count(n => n.ai[0] == 3 || n.ai[0] == 1)) * healthGateAmount - 1; //set health at phase gate
                    Main.PlaySound(SoundID.ForceRoar, npc.Center);
                }

                if (AttackTimer == 1)     //switching out attacks
                {
                    if (npc.dontTakeDamage)
                    {
                        AttackPhase = 0; //nuke attack once the boss turns immortal for a chance to break a crystal
                    }
                    else                 //otherwise proceed with attacking pattern
                    {
                        AttackPhase++;
                        if (AttackPhase > 4)
                        {
                            AttackPhase = 1;
                        }
                    }
                }

                switch (AttackPhase)     //Attacks
                {
                case 0: NukePlatforms(); break;

                case 1: CrystalCage(); break;

                case 2: CrystalSmash(); break;

                case 3: RandomSpikes(); break;

                case 4: PlatformDash(); break;
                }
                break;

            case (int)AIStates.Anger:     //the short anger phase attack when the boss loses a crystal
                AngerAttack();
                break;

            case (int)AIStates.FirstToSecond:

                Vignette.offset       = (npc.Center - Main.LocalPlayer.Center) * 0.9f;
                Vignette.extraOpacity = 0.5f + (float)Math.Sin(GlobalTimer / 25f) * 0.2f;

                if (GlobalTimer == 2)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "Sounds/Music/GlassBossAmbient");

                    foreach (NPC crystal in crystals)
                    {
                        crystal.ai[0] = 0;
                        crystal.ai[2] = 5;     //turn the crystals to transform mode
                    }
                }

                if (GlobalTimer == 120)
                {
                    SetFrameX(1);
                    foreach (NPC crystal in crystals)     //kill all the crystals
                    {
                        crystal.Kill();
                    }
                    npc.friendly = true;     //so we wont get contact damage

                    StarlightPlayer mp2 = Main.LocalPlayer.GetModPlayer <StarlightPlayer>();
                    mp2.ScreenMoveTarget = npc.Center;
                    mp2.ScreenMoveTime   = 660;
                }

                if (GlobalTimer > 120 && GlobalTimer < 240)
                {
                    npc.Center = Vector2.SmoothStep(homePos, homePos + new Vector2(0, 650), (GlobalTimer - 120) / 120f);
                }

                if (GlobalTimer > 240 && GlobalTimer < 700 && GlobalTimer % 120 == 0)
                {
                    StarlightPlayer mp2 = Main.LocalPlayer.GetModPlayer <StarlightPlayer>();
                    mp2.Shake += (int)(GlobalTimer / 20);
                }

                if (GlobalTimer >= 700 && GlobalTimer < 730)
                {
                    npc.Center = Vector2.SmoothStep(homePos + new Vector2(0, 650), homePos, (GlobalTimer - 700) / 30f);
                }

                if (GlobalTimer == 760)
                {
                    Main.PlaySound(SoundID.Roar, npc.Center);

                    StarlightPlayer mp2 = Main.LocalPlayer.GetModPlayer <StarlightPlayer>();
                    mp2.Shake += 60;
                }

                if (GlobalTimer > 850)
                {
                    SetFrameX(2);
                    ChangePhase(AIStates.SecondPhase, true); //go on to the next phase
                    ResetAttack();                           //reset attack
                    foreach (NPC wall in Main.npc.Where(n => n.modNPC is VitricBackdropLeft))
                    {
                        wall.ai[1] = 3;                                                                           //make the walls scroll
                    }
                    foreach (NPC plat in Main.npc.Where(n => n.modNPC is VitricBossPlatformUp))
                    {
                        plat.ai[0] = 1;                                                                             //make the platforms scroll
                    }
                    Vignette.visible = true;

                    break;
                }

                break;

            case (int)AIStates.SecondPhase:

                Vignette.offset       = (npc.Center - Main.LocalPlayer.Center) * 0.8f;
                Vignette.extraOpacity = 0.3f;

                if (GlobalTimer == 60)
                {
                    npc.dontTakeDamage = false;     //damagable again
                    npc.friendly       = false;
                    Vignette.visible   = true;
                }

                if (GlobalTimer == 1)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "VortexHasASmallPussy");                       //handles the music transition
                }
                if (GlobalTimer == 2)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "Sounds/Music/GlassBoss2");
                }

                if (GlobalTimer > 702 && GlobalTimer < 760)     //no fadein
                {
                    for (int k = 0; k < Main.musicFade.Length; k++)
                    {
                        if (k == Main.curMusic)
                        {
                            Main.musicFade[k] = 1;
                        }
                    }
                }

                if (AttackTimer == 1)     //switching out attacks
                {
                    AttackPhase++;
                    if (AttackPhase > 3)
                    {
                        AttackPhase = 0;
                    }
                }

                switch (AttackPhase)     //switch for crystal behavior
                {
                case 0: Volley(); break;

                case 1: Mines(); break;

                case 2: Whirl(); break;

                case 3: Rest(); break;
                }
                break;

            case (int)AIStates.LastStand:

                if (GlobalTimer == 1)
                {
                    foreach (NPC npc in Main.npc.Where(n => n.modNPC is VitricBackdropLeft || n.modNPC is VitricBossPlatformUp))
                    {
                        npc.ai[1] = 4;
                    }
                    Vignette.extraOpacity = 0;
                    startPos = npc.Center;
                }

                if (GlobalTimer < 60)
                {
                    npc.Center = Vector2.SmoothStep(startPos, homePos + new Vector2(0, -100), GlobalTimer / 60f);
                }

                if (GlobalTimer == 90)
                {
                    Twist(30);
                }

                if (GlobalTimer > 120 && GlobalTimer <= 140)
                {
                    glowColor = Color.Lerp(Color.Transparent, Color.White, (GlobalTimer - 120) / 20f);
                }

                if (GlobalTimer == 140)
                {
                    npc.frame.Y += 160;
                }

                if (GlobalTimer > 140 && GlobalTimer <= 200)
                {
                    glowColor = Color.Lerp(Color.White, Color.Red * 0.5f, (GlobalTimer - 140) / 60f);
                }

                if (GlobalTimer > 200 && GlobalTimer <= 240)
                {
                    glowColor = Color.Lerp(Color.Red * 0.5f, Color.Transparent, (GlobalTimer - 200) / 40f);
                }

                if (GlobalTimer == 300)
                {
                    int i = NPC.NewNPC((int)npc.Center.X - 200, (int)npc.Center.Y - 180, NPCType <GlassMinibossHelpful>());
                    (Main.npc[i].modNPC as GlassMinibossHelpful).parent = this;
                }

                if (GlobalTimer > 660)
                {
                    if (GlobalTimer % 120 == 0)
                    {
                        Main.NewText("ShootFire");
                        Main.PlaySound(SoundID.DD2_BetsyScream);

                        int i = Projectile.NewProjectile(npc.Center, Vector2.Normalize(Main.player[npc.target].Center - npc.Center) * 3, ProjectileType <FinalFire>(), 100, 0, Main.myPlayer);
                        (Main.projectile[i].modProjectile as FinalFire).parent = this;
                    }
                }

                break;

            case (int)AIStates.Leaving:

                npc.position.Y  += 7;
                Vignette.visible = false;

                if (GlobalTimer >= 180)
                {
                    npc.active = false;     //leave
                    foreach (NPC npc in Main.npc.Where(n => n.modNPC is VitricBackdropLeft || n.modNPC is VitricBossPlatformUp))
                    {
                        npc.active = false;                                                                                                              //arena reset
                    }
                }
                break;

            case (int)AIStates.Dying:

                Vignette.offset       = Vector2.Zero;
                Vignette.extraOpacity = 0.5f + Math.Min(GlobalTimer / 60f, 0.5f);

                if (GlobalTimer == 60)
                {
                    Main.PlaySound(mod.GetLegacySoundSlot(SoundType.Custom, "Sounds/GlassBossDeath"));
                }

                if (GlobalTimer > 60 && GlobalTimer < 120)
                {
                    Main.musicFade[Main.curMusic] = 1 - (GlobalTimer - 60) / 60f;
                }

                if (GlobalTimer > 120)
                {
                    Main.musicFade[Main.curMusic] = 0;
                    for (int k = 0; k < 10; k++)
                    {
                        Dust.NewDustPerfect(npc.Center, DustType <Dusts.Sand>(), Vector2.One.RotatedByRandom(6.28f) * Main.rand.NextFloat(10), 40, default, 2);
                    }
                    for (int k = 0; k < 2; k++)
                    {
                        Dust.NewDustPerfect(npc.Center, DustType <Dusts.Starlight>(), Vector2.One.RotatedByRandom(6.28f) * Main.rand.NextFloat(100));
                    }
                }
        public override void AI()
        {
            Lighting.AddLight(npc.Center, new Vector3(1, 1, 0.8f));

            GlobalTimer++;                                    //tick our timer up constantly
            AttackTimer++;                                    //tick up our attack timer

            if (npc.ai[0] == (int)OvergrowBossPhase.Struggle) //when the boss is trapped before spawning the first time
            {
                if (spawnPoint == Vector2.Zero)
                {
                    spawnPoint = npc.Center; //sets the boss' home
                }
                npc.velocity.Y = (float)Math.Sin((GlobalTimer % 120) / 120f * 6.28f) * 0.6f;

                if (!Main.npc.Any(n => n.active && n.type == NPCType <OvergrowBossAnchor>())) //once the chains are broken
                {
                    npc.velocity *= 0;
                    npc.Center    = spawnPoint;
                    GlobalTimer   = 0;

                    StarlightPlayer mp = Main.LocalPlayer.GetModPlayer <StarlightPlayer>();
                    mp.ScreenMoveTime   = 860;
                    mp.ScreenMoveTarget = npc.Center;
                    mp.ScreenMovePan    = npc.Center + new Vector2(0, -100);

                    Phase = (int)OvergrowBossPhase.spawnAnimation;
                }
            }

            if (Phase == (int)OvergrowBossPhase.spawnAnimation) //the boss' spawn animation.
            {
                if (GlobalTimer == 1)
                {
                    music = mod.GetSoundSlot(SoundType.Music, "Sounds/Music/OvergrowBoss");
                }

                if (GlobalTimer <= 120)
                {
                    npc.position.Y--;
                }

                if (GlobalTimer == 120)
                {
                    StarlightWorld.Flag(WorldFlags.OvergrowBossFree);
                }

                if (GlobalTimer == 500)
                {
                    string message = "Faerie Guardian";
                    if (Main.rand.Next(10000) == 0)
                    {
                        message = "Titty Elongator"; // Yep
                    }
                    UILoader.GetUIState <TextCard>().Display("Eggshells", message, null, 220);
                }

                if (GlobalTimer >= 860)
                {
                    Phase = (int)OvergrowBossPhase.Setup;
                }
            }

            if (Phase == (int)OvergrowBossPhase.Setup)
            {
                npc.boss = true;

                int index = NPC.NewNPC((int)npc.Center.X, (int)npc.Center.Y, NPCType <OvergrowBossFlail>()); //spawn the flail after intro
                (Main.npc[index].modNPC as OvergrowBossFlail).parent = this;                                 //set the flail's parent
                flail = Main.npc[index].modNPC as OvergrowBossFlail;                                         //tells the boss what flail it owns

                Phase       = (int)OvergrowBossPhase.FirstAttack;                                            //move on to the first attack phase
                GlobalTimer = 0;                                                                             //reset our timer
                npc.ai[3]   = 0;                                                                             //reset our attack timer
            }

            if (flail == null)
            {
                return;                                      //at this point, our boss should have her flail. if for some reason she dosent, this is a safety check
            }
            if (Phase == (int)OvergrowBossPhase.FirstAttack) //the first attacking phase
            {
                //attack pattern advancement logic
                if (AttackTimer == 1)
                {
                    RandomizeTarget();
                    if (AttackPhase == 1)
                    {
                        AttackPhase++;                   //tick up an additional time so that we dont use 2 alternate attacks in a row. TODO: Should make a cleaner way to do this.
                    }
                    AttackPhase++;
                    if (AttackPhase == 1 && Main.rand.Next(2) == 0)
                    {
                        AttackPhase++;
                    }
                    if (AttackPhase > 6)
                    {
                        AttackPhase = 0;
                    }

                    if (flail.npc.life <= 1) //move to next phase once the flail is depleated
                    {
                        Phase       = (int)OvergrowBossPhase.FirstToss;
                        AttackPhase = 0;
                        ResetAttack();
                        //foreach (Projectile proj in Main.projectile.Where(p => p.type == ProjectileType<Projectiles.Dummies.OvergrowBossPitDummy>())) proj.ai[1] = 1; //opens the pits
                    }
                }
                switch (AttackPhase) //attack pattern
                {
                case 0: Phase1Spin(); break;

                case 1: Phase1Bolts(); break;     //______randonly picks between these two

                case 2: Phase1Trap(); break;      //___|

                case 3: Phase1Toss(); break;

                case 4: Phase1Toss(); break;

                case 5: Phase1Bolts(); break;

                case 6: Phase1Toss(); break;
                }
            }

            if (Phase == (int)OvergrowBossPhase.FirstToss)
            {
                RapidToss();                                            //toss rapidly till thrown into a pit
            }
            if (Phase == (int)OvergrowBossPhase.Stun)
            {
                if (GlobalTimer == 1)
                {
                    npc.alpha = 255;

                    for (int k = 0; k < 100; k++)
                    {
                        Dust d = Dust.NewDustPerfect(npc.Center, 1 /*DustType<>()*/, Vector2.One.RotatedByRandom(Math.PI) * Main.rand.NextFloat(5));
                        d.customData = npc.Center;
                    }

                    npc.Center      = spawnPoint + new Vector2(0, 320);
                    flail.npc.ai[0] = 1;
                }

                if (GlobalTimer >= 120)
                {
                    npc.alpha = 0;
                    if (npc.Hitbox.Intersects(flail.npc.Hitbox))
                    {
                        flail.npc.ai[0]          = 0;
                        flail.npc.ai[3]          = 1;
                        flail.npc.velocity      *= 0;
                        flail.npc.life           = flail.npc.lifeMax;
                        flail.npc.dontTakeDamage = false;
                        flail.npc.friendly       = false;

                        npc.life -= 20000;
                        Phase     = (int)OvergrowBossPhase.SecondAttack;
                        ResetAttack();

                        CombatText.NewText(npc.Hitbox, Color.Red, 20000, true);
                        Main.PlaySound(SoundID.DD2_BetsyScream, npc.Center);
                        Main.LocalPlayer.GetModPlayer <StarlightPlayer>().Shake += 30;

                        for (int k = 0; k < 100; k++)
                        {
                            Dust d = Dust.NewDustPerfect(flail.npc.Center, DustType <Dusts.Stone>(), Vector2.One.RotatedByRandom(Math.PI) * Main.rand.NextFloat(5));
                        }
                    }
                }
            }
        }