From 7ee09f89ad82be177b5c43f2da51b957c979885d Mon Sep 17 00:00:00 2001 From: Jofairden Date: Wed, 23 Aug 2017 20:49:17 +0200 Subject: [PATCH] Motherboard refactor and improvements --- Helper.cs | 16 - Items/InfernoSkull.cs | 2 +- Motherboard_CreeperSpawning.cs | 18 - NPCs/Alchemaster.cs | 2 +- NPCs/Alchemist.cs | 2 +- NPCs/ArabianMerchant.cs | 2 +- NPCs/Barmadillo.cs | 2 +- NPCs/Brutallisk.cs | 2 +- NPCs/CogLord.cs | 2 +- NPCs/CyberKing.cs | 2 +- NPCs/DevourerofPlanets.cs | 2 +- NPCs/Dragon_HeadB.cs | 2 +- NPCs/EvilCorn.cs | 2 +- NPCs/FrostKing.cs | 2 +- NPCs/FungusBeetle.cs | 2 +- NPCs/GhostWarrior.cs | 2 +- NPCs/HeaterOfWorldsHead.cs | 2 +- NPCs/Motherboard.cs | 967 ++++++++++++++--------- NPCs/PixieQueen.cs | 2 +- NPCs/RuinGhost1.cs | 2 +- NPCs/RuinGhost2.cs | 2 +- NPCs/SignalDron.cs | 89 --- NPCs/SignalDrone.cs | 178 +++++ NPCs/{SignalDron.png => SignalDrone.png} | Bin NPCs/SoulofHope.cs | 4 +- NPCs/SoulofTrust.cs | 4 +- NPCs/SoulofTruth.cs | 4 +- NPCs/SpaceWhale.cs | 6 +- NPCs/Startrooper.cs | 6 +- NPCs/StormJellyfish.cs | 2 +- NPCs/TheDarkEmperorTwo.cs | 2 +- NPCs/TikiTotem.cs | 2 +- NPCs/TremorNPC.cs | 6 +- NPCs/Undertaker.cs | 2 +- NPCs/npcVultureKing.cs | 2 +- NovaPillar/NovaPillar.cs | 2 +- Projectiles/projMotherboardLaser.cs | 28 +- Projectiles/projMotherboardSuperLaser.cs | 31 +- RecipeUtils.cs | 1 + Tremor.cs | 40 +- Tremor.csproj | 7 +- TremorUtils.cs | 25 + 42 files changed, 907 insertions(+), 571 deletions(-) delete mode 100644 Motherboard_CreeperSpawning.cs delete mode 100644 NPCs/SignalDron.cs create mode 100644 NPCs/SignalDrone.cs rename NPCs/{SignalDron.png => SignalDrone.png} (100%) diff --git a/Helper.cs b/Helper.cs index 46e6d4c8..8a45ded2 100644 --- a/Helper.cs +++ b/Helper.cs @@ -56,21 +56,6 @@ public static bool NoBiomeNormalSpawn(NPCSpawnInfo spawnInfo) } #endregion - public static void Downed(this TremorWorld.Boss boss, bool state) - => TremorWorld.downedBoss[boss] = state; - - public static bool Downed(this TremorWorld.Boss boss) - => TremorWorld.downedBoss[boss]; - - public static Item SpawnItem(this ModNPC npc, short type, int stack = 1) - => SpawnItem(npc.npc, type, stack); - - public static Item SpawnItem(this NPC npc, short type, int stack = 1) - => Main.item[Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, type, stack)]; - - public static Item SpawnItem(Vector2 position, Vector2 size, short type, int stack = 1) - => Main.item[Item.NewItem((int)position.X, (int)position.Y, (int)size.X, (int)size.Y, type, stack)]; - public static Vector2 RandomPosition(Vector2 pos1, Vector2 pos2) { Random rnd = new Random(); @@ -369,7 +354,6 @@ public static void DropItems(Vector2 position, Vector2 randomBox, params Drop[] } } } - } public struct Drop diff --git a/Items/InfernoSkull.cs b/Items/InfernoSkull.cs index 47e3e4b3..a96b71a4 100644 --- a/Items/InfernoSkull.cs +++ b/Items/InfernoSkull.cs @@ -38,7 +38,7 @@ public override void ModifyTooltips(List tooltips) public override bool CanUseItem(Player player) { - return player.position.Y / 16f > Main.maxTilesY - 200 && TremorWorld.Boss.Trinity.Downed() && !NPC.AnyNPCs(mod.NPCType("Andas")); + return player.position.Y / 16f > Main.maxTilesY - 200 && TremorWorld.Boss.Trinity.IsDowned() && !NPC.AnyNPCs(mod.NPCType("Andas")); } public override void AddRecipes() diff --git a/Motherboard_CreeperSpawning.cs b/Motherboard_CreeperSpawning.cs deleted file mode 100644 index 7bccdb0f..00000000 --- a/Motherboard_CreeperSpawning.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Terraria; -using Terraria.ID; -using Terraria.ModLoader; - -namespace Tremor -{ - public class Motherboard_CreeperSpawning : GlobalNPC - { - public override void AI(NPC npc) - { - if (npc.type == NPCID.Creeper && Main.npc[NPC.crimsonBoss].type == mod.NPCType("Motherboard")) - npc.active = false; - for (int i = 0; i < Main.gore.Length; i++) - if (Main.gore[i].type == 392 || Main.gore[i].type == 393 || Main.gore[i].type == 394 || Main.gore[i].type == 395) - Main.gore[i].active = false; - } - } -} \ No newline at end of file diff --git a/NPCs/Alchemaster.cs b/NPCs/Alchemaster.cs index 4277efd7..2b74a73f 100644 --- a/NPCs/Alchemaster.cs +++ b/NPCs/Alchemaster.cs @@ -271,7 +271,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("BadApple")); } - TremorWorld.Boss.Alchemaster.Downed(true); + TremorWorld.Boss.Alchemaster.Downed(); } } diff --git a/NPCs/Alchemist.cs b/NPCs/Alchemist.cs index 206c73b7..4f6b0974 100644 --- a/NPCs/Alchemist.cs +++ b/NPCs/Alchemist.cs @@ -127,7 +127,7 @@ public override void SetupShop(Chest shop, ref int nextSlot) shop.item[nextSlot].SetDefaults(mod.ItemType("Nitro")); nextSlot++; } - if (TremorWorld.Boss.Alchemaster.Downed()) + if (TremorWorld.Boss.Alchemaster.IsDowned()) { shop.item[nextSlot].SetDefaults(mod.ItemType("Pyro")); nextSlot++; diff --git a/NPCs/ArabianMerchant.cs b/NPCs/ArabianMerchant.cs index ee07db5b..eac985b8 100644 --- a/NPCs/ArabianMerchant.cs +++ b/NPCs/ArabianMerchant.cs @@ -49,7 +49,7 @@ public override void SetDefaults() public override bool CanTownNPCSpawn(int numTownNPCs, int money) { - if (TremorWorld.Boss.Rukh.Downed()) + if (TremorWorld.Boss.Rukh.IsDowned()) { return true; } diff --git a/NPCs/Barmadillo.cs b/NPCs/Barmadillo.cs index b16999ed..bc514170 100644 --- a/NPCs/Barmadillo.cs +++ b/NPCs/Barmadillo.cs @@ -97,7 +97,7 @@ public override float SpawnChance(NPCSpawnInfo spawnInfo) int x = spawnInfo.spawnTileX; int y = spawnInfo.spawnTileY; int tile = Main.tile[x, y].type; - return Main.hardMode && TremorWorld.Boss.Trinity.Downed() && !spawnInfo.player.ZoneDungeon && y > Main.rockLayer ? 0.002f : 0f; + return Main.hardMode && TremorWorld.Boss.Trinity.IsDowned() && !spawnInfo.player.ZoneDungeon && y > Main.rockLayer ? 0.002f : 0f; } } } \ No newline at end of file diff --git a/NPCs/Brutallisk.cs b/NPCs/Brutallisk.cs index 03cdf7b6..eec90a72 100644 --- a/NPCs/Brutallisk.cs +++ b/NPCs/Brutallisk.cs @@ -557,7 +557,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("BrutalliskMask")); } - TremorWorld.Boss.Brutallisk.Downed(true); + TremorWorld.Boss.Brutallisk.Downed(); } } diff --git a/NPCs/CogLord.cs b/NPCs/CogLord.cs index 17a48f19..5fcfbf58 100644 --- a/NPCs/CogLord.cs +++ b/NPCs/CogLord.cs @@ -330,7 +330,7 @@ public void CogMessage(string Message) } public override void NPCLoot() { - TremorWorld.Boss.CogLord.Downed(true); + TremorWorld.Boss.CogLord.Downed(); if (Main.netMode != 1) { if (Main.expertMode) diff --git a/NPCs/CyberKing.cs b/NPCs/CyberKing.cs index 45acb8cb..20c3dd6c 100644 --- a/NPCs/CyberKing.cs +++ b/NPCs/CyberKing.cs @@ -415,7 +415,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("CyberKingMask")); } - TremorWorld.Boss.CyberKing.Downed(true); + TremorWorld.Boss.CyberKing.Downed(); } } } \ No newline at end of file diff --git a/NPCs/DevourerofPlanets.cs b/NPCs/DevourerofPlanets.cs index aa09afc1..a4d364f7 100644 --- a/NPCs/DevourerofPlanets.cs +++ b/NPCs/DevourerofPlanets.cs @@ -69,7 +69,7 @@ public override float SpawnChance(NPCSpawnInfo spawnInfo) int x = spawnInfo.spawnTileX; int y = spawnInfo.spawnTileY; int tile = Main.tile[x, y].type; - return Main.hardMode && TremorWorld.Boss.Trinity.Downed() && !spawnInfo.player.ZoneDungeon && y > Main.rockLayer ? 0.005f : 0f; + return Main.hardMode && TremorWorld.Boss.Trinity.IsDowned() && !spawnInfo.player.ZoneDungeon && y > Main.rockLayer ? 0.005f : 0f; } public override void HitEffect(int hitDirection, double damage) diff --git a/NPCs/Dragon_HeadB.cs b/NPCs/Dragon_HeadB.cs index 0e8b6c81..534ea76c 100644 --- a/NPCs/Dragon_HeadB.cs +++ b/NPCs/Dragon_HeadB.cs @@ -75,7 +75,7 @@ public override void NPCLoot() Item.NewItem(npc.position, npc.Size, mod.ItemType()); } - TremorWorld.Boss.AncientDragon.Downed(true); + TremorWorld.Boss.AncientDragon.Downed(); } diff --git a/NPCs/EvilCorn.cs b/NPCs/EvilCorn.cs index 39bbb947..5c57b4b7 100644 --- a/NPCs/EvilCorn.cs +++ b/NPCs/EvilCorn.cs @@ -676,7 +676,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("CornJavelin"), Main.rand.Next(15, 45)); } - TremorWorld.Boss.EvilCorn.Downed(true); + TremorWorld.Boss.EvilCorn.Downed(); } } diff --git a/NPCs/FrostKing.cs b/NPCs/FrostKing.cs index 13c5124e..56211260 100644 --- a/NPCs/FrostKing.cs +++ b/NPCs/FrostKing.cs @@ -310,7 +310,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("FrostoneOre"), Main.rand.Next(24, 42)); } - TremorWorld.Boss.FrostKing.Downed(true); + TremorWorld.Boss.FrostKing.Downed(); } } } diff --git a/NPCs/FungusBeetle.cs b/NPCs/FungusBeetle.cs index a6354b27..d5ae5e28 100644 --- a/NPCs/FungusBeetle.cs +++ b/NPCs/FungusBeetle.cs @@ -60,7 +60,7 @@ public override void NPCLoot() Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("FungusElement"), Main.rand.Next(10, 23)); } Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, 28, Main.rand.Next(9, 22)); - TremorWorld.Boss.FungusBeetle.Downed(true); + TremorWorld.Boss.FungusBeetle.Downed(); } } diff --git a/NPCs/GhostWarrior.cs b/NPCs/GhostWarrior.cs index d89af280..f1d2e022 100644 --- a/NPCs/GhostWarrior.cs +++ b/NPCs/GhostWarrior.cs @@ -73,7 +73,7 @@ public override float SpawnChance(NPCSpawnInfo spawnInfo) int x = spawnInfo.spawnTileX; int y = spawnInfo.spawnTileY; int tile = Main.tile[x, y].type; - return (Helper.NormalSpawn(spawnInfo) && Helper.NoZoneAllowWater(spawnInfo)) && TremorWorld.Boss.Trinity.Downed() && NPC.downedMoonlord && Main.hardMode && !Main.dayTime && y < Main.worldSurface ? 0.005f : 0f; + return (Helper.NormalSpawn(spawnInfo) && Helper.NoZoneAllowWater(spawnInfo)) && TremorWorld.Boss.Trinity.IsDowned() && NPC.downedMoonlord && Main.hardMode && !Main.dayTime && y < Main.worldSurface ? 0.005f : 0f; } } } \ No newline at end of file diff --git a/NPCs/HeaterOfWorldsHead.cs b/NPCs/HeaterOfWorldsHead.cs index d4e81015..89449d7a 100644 --- a/NPCs/HeaterOfWorldsHead.cs +++ b/NPCs/HeaterOfWorldsHead.cs @@ -251,7 +251,7 @@ private void DropLoot() if (Main.rand.NextBool(10)) npc.SpawnItem((short)mod.ItemType()); - TremorWorld.Boss.HeaterofWorlds.Downed(true); + TremorWorld.Boss.HeaterofWorlds.Downed(); } } } \ No newline at end of file diff --git a/NPCs/Motherboard.cs b/NPCs/Motherboard.cs index 51e53b32..7b8c12e0 100644 --- a/NPCs/Motherboard.cs +++ b/NPCs/Motherboard.cs @@ -1,472 +1,521 @@ using System; using System.Collections.Generic; +using System.Linq; using System.IO; +using System.Reflection; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Content; using Terraria; +using Terraria.Audio; +using Terraria.GameContent; using Terraria.ID; using Terraria.ModLoader; using Tremor.Items; +// TODO: fix Motherboard despawn on first hit +// TODO: motherboard does not spawn in MP +// TODO: rewrite this thing, lol + namespace Tremor.NPCs { - // TODO: fix Motherboard despawn on first hit - // TODO: motherboard does not spawn in MP - // TODO: rewrite this thing, lol - [AutoloadBossHead] - public class Motherboard : ModNPC + // has two stages + // some other bosses do + // the possibility to factor that exists + // each stage has different appearing, disappearing, and following times + + public class Stage + { + public int followPlayerTime; // Time of following player in 1st stage + public int disappearingTime; // Time of disappearing in 1st stage + public int appearingTime; // Time of appearing in 1st stage + public int stateTime; // Stage time + public int appearTime; + + public int GetStateTime => appearingTime + disappearingTime + followPlayerTime; + + protected const int AnimationRate = 6; // Animation rate + protected int _currentFrame; // Current frame + protected int _timeToAnimation = 6; // Animation rate + + protected const int LaserYOffset = 95; // Laser spawn offset by Y value + protected const int LaserDamage = 40; // Laser damage + protected const float LaserKb = 1; // Laser knockback + + protected const int SecondShootCount = 3; + protected const float SecondShootSpeed = 15f; + protected const int SecondShootDamage = 30; + protected const float SecondShootKn = 1.0f; + protected const int SecondShootRate = 60; // The rate of fire of the motherboard's 3 laser shot + protected const int SecondShootSpread = 35; // The random spread of motherboard's 3 laser shot + protected const float SecondShootSpreadMult = 0.05f; + + protected int _secondShootTime = 60; + + public Stage(int followPlayerTime, int disappearingTime, int appearingTime) + { + this.followPlayerTime = followPlayerTime; + this.disappearingTime = disappearingTime; + this.appearingTime = appearingTime; + this.stateTime = appearingTime + disappearingTime + followPlayerTime; + } + + public virtual int FrameOffset => 0; + + public void Animate(Motherboard boss) + { + --_timeToAnimation; + if (_timeToAnimation == 0) + { + _currentFrame = (_currentFrame + 1) % 3; + _timeToAnimation = AnimationRate; + boss.npc.frame = boss.GetFrame(_currentFrame + FrameOffset); + } + } + + public virtual void AI(Motherboard motherboard) { } + public virtual void AdjustHead(Motherboard boss) { } + public virtual void Start(Motherboard boss) { } + } + + // Phase 1 + + // In the first phase after she spawns, she will also spawn in with + // several Signal Drones. The drones will fly around her and will + // occasionally charge at the player. During this time she will chase + // after the player slowly and is completely immune to damage. After + // the drones pass beams between each other, she will fire 3 shadow + // laser toward the player. Every few seconds after destroying some of + // the drones, she will spawn new ones. The phase ends when all of the + // drones are destroyed. + + public class Stage1 : Stage { - #region "Константы" - - private const int StateOneFollowPlayerTime = 120; // Time of following player in 1st stage - private const int StateOneDisappearingTime = 30; // Time of disappearing in 1st stage - private const int StateOneAppearingTime = 30; // Time of appearing in 1st stage - private const int StateSecondFollowPlayerTime = 90; // Time of following player in 2nd stage - private const int StateSecondDisappearingTime = 30; //Time of disappearing in 2nd stage - private const int StateSecondAppearingTime = 30; // Time of appearing in 2nd stage - private const int MaxDrones = 20; // Maximum amount of Drones - private const int DronSpawnAreaX = 300; // Area size in which Drone can spawn by X value - private const int DronSpawnAreaY = 300; // Area size in which Drone can spawn by Y value - private const int StartDronCount = 8; // Initial amount of Drones + public Stage1(int followPlayerTime, int disappearingTime, int appearingTime) : base(followPlayerTime, disappearingTime, appearingTime) { } + + private List _signalDrones = new List(); // ID of Signal Drones + + private const int DroneSpawnAreaX = 300; // Area size in which Drone can spawn by X value + private const int DroneSpawnAreaY = 300; // Area size in which Drone can spawn by Y value + private const int StartDroneCount = 8; // How many drones she starts with + private const int MaxDrones = 20; // How many drones there can be at maximum private const int ShootRate = 150; // Fire rate in ticks - private const int LaserDamage = 40; // Laser damage - private const float LaserKb = 1; // Laser knockback - private const int LaserYOffset = 95; // Laser spawn offset by Y value private const int TimeToLaserRate = 3; // Fire rate (From drones to player) - private const int LaserType = ProjectileID.ShadowBeamHostile; // Laser type - private const int AnimationRate = 6; // Animation rate - private const int SecondShootCount = 3; - private const float SecondShootSpeed = 15f; - private const int SecondShootDamage = 30; - private const float SecondShootKn = 1.0f; - private const int SecondShootRate = 60; - private const int SecondShootSpread = 65; - private const float SecondShootSpreadMult = 0.05f; - #endregion - - #region "Переменные" - - private int _appearTime; - private bool _firstAi = true; // Is it the first time when AI method is called? - private bool _firstState = true; // Is it 1st stage? - private List _signalDrones = new List(); // ID of Signal Drones - private int _lastSignalDron = -1; // Last Drone - private int _stateTime = StateOneAppearingTime + StateOneDisappearingTime + StateOneFollowPlayerTime; // Stage time - private bool _shootNow; // Does the Motherboard shoots right now? + private int _timeToNextDrone = 1; // Time for spawning next Drone private int _timeToShoot = 60; // Time for next shoot private int _timeToLaser = 3; // Time for next shoot (Drones lasers) - private int _currentFrame; // Current frame - private int _timeToAnimation = 6; // Animation rate - private List _clampers = new List(); // Clampers list - private int _secondShootTime = 60; - private int _ai = 0; - - private int GetStateTime => GetAppearingTimeNow + GetDisappearingTimeNow + GetFollowPlayerTimeNow; - //----- - // Get time needed for full cycle of changing states - private int GetTimeToNextDrone => (Main.rand.Next(3, 6) * 60); - // Get time for spawning next Drone - - //----- Methods of getting times of states at the moment - private int GetFollowPlayerTimeNow => (_firstState) ? StateOneFollowPlayerTime : StateSecondFollowPlayerTime; - private int GetDisappearingTimeNow => (_firstState) ? StateOneDisappearingTime : StateSecondDisappearingTime; - private int GetAppearingTimeNow => (_firstState) ? StateOneAppearingTime : StateSecondAppearingTime; - //----- - #endregion - - public override void SetStaticDefaults() - { - DisplayName.SetDefault("Motherboard"); - Main.npcFrameCount[npc.type] = 6; + private int _lastSignalDrone = -1; // Last Drone - NPCID.Sets.MustAlwaysDraw[npc.type] = true; - NPCID.Sets.NeedsExpertScaling[npc.type] = true; - } + //------------------------------------------------ + // private methods + //------------------------------------------------ - public override void SetDefaults() + // Removes all dead Drones from the list + private void RemoveDeadDrones(Motherboard boss) { - npc.lifeMax = 45000; - npc.damage = 30; - npc.knockBackResist = 0f; - npc.defense = 70; - npc.width = 170; - npc.height = 160; - npc.aiStyle = 2; // -1 - npc.npcSlots = 50f; - music = MusicID.Boss3; - - npc.dontTakeDamage = true; - npc.noTileCollide = true; - npc.noGravity = true; - npc.boss = true; - npc.lavaImmune = true; + int lastKnownDrone = _lastSignalDrone != -1 ? _signalDrones[_lastSignalDrone] : -1; - npc.HitSound = SoundID.NPCHit4; - npc.DeathSound = SoundID.NPCDeath10; + _signalDrones = _signalDrones.Where(x => + { + NPC npc = Main.npc[x]; + return npc.active && npc.type == boss.mod.NPCType(); + }).ToList(); - bossBag = mod.ItemType(); + _lastSignalDrone = _signalDrones.FindIndex(x => x == lastKnownDrone); } - public override void ScaleExpertStats(int numPlayers, float bossLifeScale) + private void TrySpawnOneDrone(Motherboard boss) { - npc.lifeMax = (int)(npc.lifeMax * 0.625f * bossLifeScale); - npc.damage = (int)(npc.damage * 0.6f); - } + if (_signalDrones.Count < MaxDrones) + { + --_timeToNextDrone; + if (_timeToNextDrone < 0) + { + _timeToNextDrone = 60 * Main.rand.Next(3, 6); - public override bool UsesPartyHat() => false; + Vector2 spawnPosition = + Helper.RandomPointInArea( + new Vector2(boss.npc.Center.X - DroneSpawnAreaX * .5f, boss.npc.Center.Y - DroneSpawnAreaY * .5f), + new Vector2(boss.npc.Center.X + DroneSpawnAreaX * .5f, boss.npc.Center.Y + DroneSpawnAreaY * .5f)); - // ?? Doesn't seem to fix much - public override void SendExtraAI(BinaryWriter writer) - { - writer.Write(_appearTime); - writer.Write(_firstAi); - writer.Write(_firstState); - writer.Write(_signalDrones.Count); - foreach (int drone in _signalDrones) - { - writer.Write(drone); - } - writer.Write(_lastSignalDron); - writer.Write(_shootNow); - writer.Write(_timeToNextDrone); - writer.Write(_timeToShoot); - writer.Write(_timeToLaser); - writer.Write(_currentFrame); - writer.Write(_timeToAnimation); - writer.Write(_clampers.Count); - foreach (int clamper in _clampers) - { - writer.Write(clamper); + _signalDrones.Add(NPC.NewNPC((int)spawnPosition.X, (int)spawnPosition.Y + LaserYOffset, boss.mod.NPCType(), ai3: boss.npc.whoAmI)); + } } - writer.Write(_secondShootTime); - writer.Write(_ai); } - public override void ReceiveExtraAI(BinaryReader reader) + private void ShootOneLaser(Motherboard boss) { - _appearTime = reader.ReadInt32(); - _firstAi = reader.ReadBoolean(); - _firstState = reader.ReadBoolean(); - int c = reader.ReadInt32(); - _signalDrones = new List(); - for (int i = 0; i < c; i++) + // this code is still poop + try { - _signalDrones[i] = reader.ReadInt32(); + int ai0 = _lastSignalDrone == -1 ? boss.npc.whoAmI : _signalDrones[_lastSignalDrone]; + ++_lastSignalDrone; + var zapSound = new LegacySoundStyle(SoundID.Trackable, TremorUtils.GetIdForSoundName($"dd2_lightning_aura_zap_{Main.rand.Next(4)}")); + Main.PlayTrackedSound(zapSound.WithPitchVariance(Main.rand.NextFloat() * .5f).WithVolume(Main.soundVolume * 1.5f)); + int newProj = Projectile.NewProjectile(boss.npc.Center.X, boss.npc.Center.Y, 0, 0, + boss.mod.ProjectileType("projMotherboardLaser"), + LaserDamage, LaserKb, 0, ai0, _signalDrones[_lastSignalDrone]); + if (_lastSignalDrone == 0) + { + Main.projectile[newProj].localAI[1] = 1; + } } - _lastSignalDron = reader.ReadInt32(); - _shootNow = reader.ReadBoolean(); - _timeToNextDrone = reader.ReadInt32(); - _timeToShoot = reader.ReadInt32(); - _timeToLaser = reader.ReadInt32(); - _currentFrame = reader.ReadInt32(); - _timeToAnimation = reader.ReadInt32(); - c = reader.ReadInt32(); - for (int i = 0; i < c; i++) + catch { - _clampers[i] = reader.ReadInt32(); + // POOP I TELL YOU } - _secondShootTime = reader.ReadInt32(); - _ai = reader.ReadInt32(); } - private void Teleport() + // shoot a beam at target player + private void ShootOneSecondShot(Motherboard boss) { - npc.aiStyle = 2; - npc.position += npc.velocity * 2; + Player target = Main.player[boss.npc.target]; + + // Calculate velocity DIRECTION + float targetAngle = boss.npc.AngleTo(target.Center); + Vector2 velocity = new Vector2((float) Math.Cos(targetAngle), (float) Math.Sin(targetAngle)) * SecondShootSpeed; + velocity += new Vector2(Main.rand.Next(-(int)SecondShootSpeed/2, (int)SecondShootSpeed/2 + 1) * SecondShootSpreadMult); + + // Shoot proj + Projectile.NewProjectile(boss.npc.Center.X, boss.npc.Center.Y, velocity.X, velocity.Y, ProjectileID.ShadowBeamHostile, SecondShootDamage, SecondShootKn); } - public override void AI() + private void ShootDroneLasers(Motherboard boss) // If it is time to shoot { - Animation(); - if (Helper.GetNearestPlayer(npc.position, true) == -1 || Main.dayTime) + // if there's no current drone AND we are not moving, return + // only shoot lasers if there are any and we are in moving phase + --_timeToLaser; + if (_timeToLaser <= 0) { - npc.aiStyle = 11; - npc.damage = 1000; - npc.ai[0] = 2; - } - if (npc.aiStyle == 11) - { - npc.rotation = 0; - return; - } - if (_firstAi) - { - _firstAi = false; - for (int i = 0; i < ((StartDronCount <= 0) ? 1 : StartDronCount); i++) + // Set new shoot time + _timeToLaser = TimeToLaserRate; + ShootOneLaser(boss); + + // If we shot all interdrone lasers, shoot at the player + if (_lastSignalDrone + 1 >= _signalDrones.Count) { - Vector2 spawnPosition = Helper.RandomPointInArea(new Vector2(npc.Center.X - DronSpawnAreaX / 2, npc.Center.Y - DronSpawnAreaY / 2), new Vector2(npc.Center.X + DronSpawnAreaX / 2, npc.Center.Y + DronSpawnAreaY / 2)); - _signalDrones.Add(NPC.NewNPC((int)spawnPosition.X, (int)spawnPosition.Y, mod.NPCType("SignalDron"), 0, 0, 0, 0, npc.whoAmI)); + // shoot N SecondShoots + for (int i = 0; i < SecondShootCount; i++) + { + ShootOneSecondShot(boss); + } + + // setting last Drone to -1 and ending the cycle of shooting + _lastSignalDrone = -1; + _timeToShoot = ShootRate; } } - ChangeAi(); - if (_firstState) + } + + //------------------------------------------------ + // hooks + //------------------------------------------------ + + public override void Start(Motherboard boss) + { + for (int i = 0; i < StartDroneCount; i++) { - Main.npcHeadBossTexture[NPCID.Sets.BossHeadTextures[npc.type]] = mod.GetTexture("NPCs/Motherboard_Head_Boss"); - Drones(); - npc.dontTakeDamage = true; + _timeToNextDrone = -1; // forcefully spawn + TrySpawnOneDrone(boss); } - else + } + + public override void AdjustHead(Motherboard boss) + { + Main.npcHeadBossTexture[boss.headTexture] = boss.mod.GetTexture("NPCs/Motherboard_Head_Boss"); + } + + public override void AI(Motherboard boss) + { + RemoveDeadDrones(boss); + TrySpawnOneDrone(boss); + + if (_signalDrones.Count > 0) // we have drones, we can shoot { - Main.npcHeadBossTexture[NPCID.Sets.BossHeadTextures[npc.type]] = mod.GetTexture("NPCs/Motherboard_Head_Boss2"); - Teleport(); - if (_ai == 1) + --_timeToShoot; + if (_timeToShoot < 0) { - npc.TargetClosest(true); - Vector2 vector142 = new Vector2(npc.Center.X, npc.Center.Y); - float num1243 = Main.player[npc.target].Center.X - vector142.X; - float num1244 = Main.player[npc.target].Center.Y - vector142.Y; - float num1245 = (float)Math.Sqrt(num1243 * num1243 + num1244 * num1244); - if (npc.ai[1] == 0f) - { - if (Main.netMode != 1) - { - npc.localAI[1] += 1f; - if (npc.localAI[1] >= 120 + Main.rand.Next(200)) - { - npc.localAI[1] = 0f; - npc.TargetClosest(true); - int num1249 = 0; - int num1250; - int num1251; - while (true) - { - num1249++; - num1250 = (int)Main.player[npc.target].Center.X / 16; - num1251 = (int)Main.player[npc.target].Center.Y / 16; - num1250 += Main.rand.Next(-50, 51); - num1251 += Main.rand.Next(-50, 51); - if (!WorldGen.SolidTile(num1250, num1251) && Collision.CanHit(new Vector2(num1250 * 16, num1251 * 16), 1, 1, Main.player[npc.target].position, Main.player[npc.target].width, Main.player[npc.target].height)) - { - break; - } - if (num1249 > 100) - { - return; - } - } - npc.ai[1] = 1f; - npc.ai[2] = num1250; - npc.ai[3] = num1251; - npc.netUpdate = true; - return; - } - } - } - else if (npc.ai[1] == 1f) + // only shoot lasers if there are any and we are in moving phase + if (boss.npc.ai[0] == -1) { - npc.alpha += 3; - if (npc.alpha >= 255) - { - npc.alpha = 255; - npc.position.X = npc.ai[2] * 16f - npc.width / 2; - npc.position.Y = npc.ai[3] * 16f - npc.height / 2; - npc.ai[1] = 2f; - return; - } - } - else if (npc.ai[1] == 2f) - { - npc.alpha -= 3; - if (npc.alpha <= 0) - { - npc.alpha = 0; - npc.ai[1] = 0f; - return; - } + ShootDroneLasers(boss); } } - CheckClampers(); - SecondShoot(); - npc.dontTakeDamage = false; - return; } - ChangeStady(); + else // no drones, advance stage + { + boss.stage = boss.stage2; + boss.stage.Start(boss); + } + } + } + + // Phase 2 + + // Once all of the drones are destroyed, she will be vulnerable to + // attacks. She will replace the drones with 4 new minions called + // Clampers, which will be attached to her. They will chase after the + // player while Motherboard moves aimlessly around you and + // teleporting. After about 80% of her health is gone she will detach + // the Clampers and begin to aggressively chase you. + + public class Stage2 : Stage + { + public Stage2(int followPlayerTime, int disappearingTime, int appearingTime) : base(followPlayerTime, disappearingTime, appearingTime) { } + + private List _clampers = new List(); // Clampers list + + public override int FrameOffset => 3; + + public override void AdjustHead(Motherboard boss) + { + Main.npcHeadBossTexture[boss.headTexture] = boss.mod.GetTexture("NPCs/Motherboard_Head_Boss"); } - private void Animation() + public override void Start(Motherboard boss) { - if (--_timeToAnimation <= 0) + _clampers = new List { + NPC.NewNPC((int) boss.npc.Center.X - 15, (int) boss.npc.Center.Y + 25, boss.mod.NPCType("Clamper"), 0, 0, 0, 0, boss.npc.whoAmI), + NPC.NewNPC((int) boss.npc.Center.X - 10, (int) boss.npc.Center.Y + 25, boss.mod.NPCType("Clamper"), 0, 0, 0, 0, boss.npc.whoAmI), + NPC.NewNPC((int) boss.npc.Center.X + 10, (int) boss.npc.Center.Y + 25, boss.mod.NPCType("Clamper"), 0, 0, 0, 0, boss.npc.whoAmI), + NPC.NewNPC((int) boss.npc.Center.X + 15, (int) boss.npc.Center.Y + 25, boss.mod.NPCType("Clamper"), 0, 0, 0, 0, boss.npc.whoAmI) + }; - if (++_currentFrame > 3) - _currentFrame = 1; - _timeToAnimation = AnimationRate; - npc.frame = GetFrame(_currentFrame + ((_firstState) ? 0 : 3)); + for (int i = 0; i <= 3; i++) + { + Main.npc[_clampers[i]].localAI[1] = i + 1; } - } - private Rectangle GetFrame(int number) - { - return new Rectangle(0, npc.frame.Height * (number - 1), npc.frame.Width, npc.frame.Height); + boss.npc.dontTakeDamage = false; + boss.npc.aiStyle = 2; + + Main.PlaySound(15, (int)boss.npc.position.X, (int)boss.npc.position.Y, 2, pitchOffset: Main.rand.NextFloat()); // high tonal boss screech + Main.PlaySound(SoundID.DD2_LightningBugDeath.AsSound().WithPitchVariance(Main.rand.NextFloat()).WithVolume(Main.soundVolume * 1.5f), boss.npc.position); } - public override void HitEffect(int hitDirection, double damage) + private void CheckClampers(Motherboard boss) { - if (npc.life <= 0) + _clampers = _clampers.Where(x => { - for (int k = 0; k < 20; k++) + NPC npc = Main.npc[x]; + return npc.active && npc.type == boss.mod.NPCType("Clamper"); + }).ToList(); + + // Spawn clamper strings (laser) + if (Main.netMode != NetmodeID.MultiplayerClient) + { + foreach (int clamper in _clampers) { - Dust.NewDust(npc.position, npc.width, npc.height, 151, 2.5f * hitDirection, -2.5f, 0, default(Color), 0.7f); + int id = Projectile.NewProjectile(boss.npc.Center.X, boss.npc.Center.Y + LaserYOffset, 0, 0, + boss.mod.ProjectileType("projClamperLaser"), LaserDamage, LaserKb, 0, boss.npc.whoAmI, clamper); + Main.projectile[id].localAI[1] = stateTime; } - Gore.NewGore(npc.position, npc.velocity, mod.GetGoreSlot("Gores/MotherboardGore1"), 1f); - Gore.NewGore(npc.position, npc.velocity, mod.GetGoreSlot("Gores/MotherboardGore2"), 1f); - Gore.NewGore(npc.position, npc.velocity, mod.GetGoreSlot("Gores/MotherboardGore2"), 1f); - Gore.NewGore(npc.position, npc.velocity, mod.GetGoreSlot("Gores/MotherboardGore3"), 1f); - Gore.NewGore(npc.position, npc.velocity, mod.GetGoreSlot("Gores/MotherboardGore4"), 1f); } } - private void SecondShoot() + protected void SecondShoot(Motherboard boss) { - for (int i = (int)npc.position.X - 8; i < (npc.position.X + 8 + npc.width); i += 8) - for (int l = (int)npc.Center.Y + 90; l < (npc.Center.Y + 106); l += 8) - if (WorldGen.SolidTile(i / 16, l / 16)) - return; - if (--_secondShootTime <= 0) + if (!boss.isInsideTerrain()) { - _secondShootTime = SecondShootRate; - Projectile.NewProjectile(npc.Center.X, npc.Center.Y + 95, 0, 0, mod.ProjectileType("projMotherboardSuperLaser"), SecondShootDamage, SecondShootKn, 0, npc.whoAmI, 0); - Projectile.NewProjectile(npc.Center.X, npc.Center.Y + 95, 0, 0, mod.ProjectileType("projMotherboardSuperLaser"), SecondShootDamage, SecondShootKn, 0, npc.whoAmI, 1); + --_secondShootTime; } - } - private void ChangeStady() // Trying change stage - { - CheckDrones(); // Checking for Drones - if (_signalDrones.Count <= 0) // If there are no Drones alive + if (_secondShootTime <= 0) { - _firstState = false; // Toggling off 1st Stage - _clampers = new List + _secondShootTime = SecondShootRate; + for (int i = 0; i < 2; i++) { - NPC.NewNPC((int) npc.Center.X - 15, (int) npc.Center.Y + 25, mod.NPCType("Clamper"), 0, 0, 0, 0, npc.whoAmI), - NPC.NewNPC((int) npc.Center.X - 10, (int) npc.Center.Y + 25, mod.NPCType("Clamper"), 0, 0, 0, 0, npc.whoAmI), - NPC.NewNPC((int) npc.Center.X + 10, (int) npc.Center.Y + 25, mod.NPCType("Clamper"), 0, 0, 0, 0, npc.whoAmI), - NPC.NewNPC((int) npc.Center.X + 15, (int) npc.Center.Y + 25, mod.NPCType("Clamper"), 0, 0, 0, 0, npc.whoAmI) - }; - Main.npc[_clampers[0]].localAI[1] = 1; - Main.npc[_clampers[1]].localAI[1] = 2; - Main.npc[_clampers[2]].localAI[1] = 3; - Main.npc[_clampers[3]].localAI[1] = 4; + Main.PlaySound(SoundID.Item113.WithPitchVariance(Main.rand.NextFloat()), boss.npc.position); + if (Main.netMode != NetmodeID.MultiplayerClient) + Projectile.NewProjectile(boss.npc.Center.X, boss.npc.Center.Y + 95, 0, 0, + boss.mod.ProjectileType("projMotherboardSuperLaser"), SecondShootDamage, SecondShootKn, 0, boss.npc.whoAmI, i); + } } } - private void ChangeAi() // Changes state (Following/disappearing/appearing) + protected void ChangeAlpha(Motherboard boss, float difference) { - if (_firstState) + boss.npc.alpha = (int)MathHelper.Clamp(boss.npc.alpha + difference, 0, 255); + } + + public override void AI(Motherboard boss) + { + boss.Move(); + boss.npc.TargetClosest(true); + + // this was never actually executed + // not sure what is meant to do, or where it is supposed to go + // for (int i = 0; i < _clampers.Count; i++) + // Main.npc[_clampers[i]].ai[2] = 1; + + // following + if (boss.npc.ai[1] == 0f) { - --_stateTime; // Lowering states time - if (_stateTime <= 0) // If state time < or = 0 then update a variable - _stateTime = GetStateTime; // Updating - for (int i = 0; i < _clampers.Count; i++) - Main.npc[_clampers[i]].ai[2] = 1; - if (_stateTime <= GetAppearingTimeNow) // If it is appearing state + // runs only SP/server side + if (Main.netMode != 1) { - npc.ai[0] = -3; // Then appear - return; // Ending the method + // increment the something timer + boss.npc.localAI[1] += 1f; + + // if the timer is due, plus some random amount of ticks + if (boss.npc.localAI[1] >= 120 + Main.rand.Next(200)) + { + boss.npc.localAI[1] = 0f; + boss.npc.TargetClosest(true); + + // attempt to find coords somewhere around the target (max 100 tries) + // break as soon as we find a place around the player that we can move to + for (int attempts = 0; attempts < 100; attempts++) + { + Player target = Main.player[boss.npc.target]; + int coordX = (int)target.Center.X / 16 + Main.rand.Next(-50, 51); + int coordY = (int)target.Center.Y / 16 + Main.rand.Next(-50, 51); + + if (!WorldGen.SolidTile(coordX, coordY) + && Collision.CanHit(new Vector2(coordX, coordY).ToWorldCoordinates(), 1, 1, + target.position, + target.width, + target.height)) + { + boss.npc.teleportTime = 1f; + boss.npc.ai[1] = 1f; + boss.npc.ai[2] = coordX; + boss.npc.ai[3] = coordY; + boss.npc.netUpdate = true; + break; + } + } + + return; + } } - if (_stateTime <= GetAppearingTimeNow + GetDisappearingTimeNow) // If it is disappearing state + } + // disappearing + else if (boss.npc.ai[1] == 1f) + { + ChangeAlpha(boss, 3); + + // finished disappearing + if (boss.npc.alpha >= 255) { - npc.ai[0] = -2; // Then disappear - return; // Ending the method + boss.npc.teleportTime = 0f; + boss.npc.position.X = boss.npc.ai[2] * 16f - boss.npc.width / 2; + boss.npc.position.Y = boss.npc.ai[3] * 16f - boss.npc.height / 2; + boss.npc.ai[1] = 2f; + // Motherboard screech + var screech = new LegacySoundStyle(SoundID.Trackable, TremorUtils.GetIdForSoundName($"dd2_lightning_bug_death_{Main.rand.Next(3)}")); + Main.PlayTrackedSound(screech.WithPitchVariance(Main.rand.NextFloat())); + //Main.PlaySound(SoundID.DD2_LightningBugDeath.WithPitchVariance(Main.rand.NextFloat()).WithVolume(Main.soundVolume * 2.5f), boss.npc.position); + Main.PlaySound(SoundID.Item78.WithVolume(Main.soundVolume * 1.15f), boss.npc.position); // tp + return; } } - // This will toggle if only it is following state - if (npc.ai[0] == -2) - _appearTime = GetAppearingTimeNow; - if (--_appearTime > 0) + // appearing + else if (boss.npc.ai[1] == 2f) { - npc.ai[0] = -3; - return; - } - npc.ai[0] = -1; // Follow the player - } + ChangeAlpha(boss, -3); - private void CheckClampers() - { - for (int index = 0; index < _clampers.Count; index++) // Passing through each element of array with ID of clampers - if (!Main.npc[_clampers[index]].active || Main.npc[_clampers[index]].type != mod.NPCType("Clamper")) // If - // NPC with ID from array isn't a Clamper or is dead then... + // finished appearing + if (boss.npc.alpha <= 0) { - _clampers.RemoveAt(index); // Remove ID of this NPC from Clamper list - --index; // Lowering index by 1 in order not to miss 1 element in array of IDs + boss.npc.ai[1] = 0f; + return; } - foreach (int ID in _clampers) - { - int id = Projectile.NewProjectile(npc.Center.X, npc.Center.Y + LaserYOffset, 0, 0, mod.ProjectileType("projClamperLaser"), LaserDamage, LaserKb, 0, npc.whoAmI, ID); - Main.projectile[id].localAI[1] = _stateTime; } + + // not finished appearing, disappearing, or didn't find a new place to move to....? + CheckClampers(boss); + SecondShoot(boss); } + } - private void Drones() // Drones in 1st Stage + // BOSS CODE + + [AutoloadBossHead] + public class Motherboard : ModNPC + { + public Stage stage0 = new Stage(120, 30, 30); + public Stage stage1 = new Stage1(120, 30, 30); + public Stage stage2 = new Stage2(90, 30, 30); + + public Stage stage; + + public int headTexture = 0; + + // private int _stateTime = StateOneAppearingTime + StateOneDisappearingTime + StateOneFollowPlayerTime; // Stage time + + // private int GetStateTime => GetAppearingTimeNow + GetDisappearingTimeNow + GetFollowPlayerTimeNow; + + // private int GetFollowPlayerTimeNow => (stage == stage1) ? stage1.followPlayerTime : stage2.followPlayerTime; + private int GetDisappearingTimeNow => (stage == stage1) ? stage1.disappearingTime : stage2.disappearingTime; + private int GetAppearingTimeNow => (stage == stage1) ? stage1.appearingTime : stage2.appearingTime; + + private float _teleportTime; + + public bool IsTeleporting + => _teleportTime > 0f; + + private float teleportStyle = 0f; + + // public override bool UsesPartyHat() => false; + + public override void SetStaticDefaults() { - CheckDrones(); // Removes dead Drones from the listУдаляет из списка всех мёртвых дронов - SpawnDrones(); // Spawns Drones - ShootDrones(); // Shoots lasers + DisplayName.SetDefault("Motherboard"); + Main.npcFrameCount[npc.type] = 6; + + NPCID.Sets.MustAlwaysDraw[npc.type] = true; + NPCID.Sets.NeedsExpertScaling[npc.type] = true; } - private void CheckDrones() // Removes all dead Drones from the list + public override void SetDefaults() { - for (int index = 0; index < _signalDrones.Count; index++) // Passing through each element of array with ID of clampers - if (!Main.npc[_signalDrones[index]].active || Main.npc[_signalDrones[index]].type != mod.NPCType("SignalDron")) // If - // NPC with ID from array isn't a Drone or is dead then... - { - _signalDrones.RemoveAt(index); // Remove ID of this NPC from Drones list - --index; // Lowering index by 1 in order not to miss 1 element in array of IDs - } + npc.lifeMax = 45000; + npc.damage = 30; + npc.knockBackResist = 0f; + npc.defense = 70; + npc.width = 170; + npc.height = 160; + npc.aiStyle = 2; + npc.npcSlots = 50f; + music = MusicID.Boss3; + + npc.dontTakeDamage = true; + npc.noTileCollide = true; + npc.noGravity = true; + npc.boss = true; + npc.lavaImmune = true; + + npc.HitSound = SoundID.NPCHit4; + npc.DeathSound = SoundID.NPCDeath14; + + bossBag = mod.ItemType(); + headTexture = NPCID.Sets.BossHeadTextures[npc.type]; + + stage = stage0; } - private void SpawnDrones() // If it is time to spawn a Drone + public override void ScaleExpertStats(int numPlayers, float bossLifeScale) { - if (_signalDrones.Count >= MaxDrones) // If the current amount of Drones = or > maximum amount of drones then... - return; // End the method - if (--_timeToNextDrone <= 0) // Lowering the time of spawning next Drone. If the time < or = 0 then... - { - _timeToNextDrone = GetTimeToNextDrone; // Setting new time of spawning Drones - Vector2 spawnPosition = Helper.RandomPointInArea(new Vector2(npc.Center.X - DronSpawnAreaX / 2, npc.Center.Y - DronSpawnAreaY / 2), new Vector2(npc.Center.X + DronSpawnAreaX / 2, npc.Center.Y + DronSpawnAreaY / 2)); - // Defining random position around the boss (Via Helper) and write it into Var 01 - _signalDrones.Add(NPC.NewNPC((int)spawnPosition.X, (int)spawnPosition.Y + LaserYOffset, mod.NPCType("SignalDron"), 0, 0, 0, 0, npc.whoAmI)); - // Spawning Drone with coordinates from Var 01 and with ID in ai[3] - } + npc.lifeMax = (int)(npc.lifeMax * 0.625f * bossLifeScale); + npc.damage = (int)(npc.damage * 0.6f); } - private void ShootDrones() // If it is time to shoot + public override void HitEffect(int hitDirection, double damage) { - if (_signalDrones.Count <= 0) // If there're no Drones then... - return; // Ending the method - if (--_timeToShoot <= 0 || _shootNow) // If it is time to shoot or if the boss is already shooting then... + if (npc.life <= 0) { - if (_lastSignalDron == -1 && npc.ai[0] != -1) - return; - _timeToShoot = ShootRate; // Setting new shoot time - _shootNow = true; // Shooting - if (--_timeToLaser <= 0) // If it is time to shoot Drones lasers then... + for (int k = 0; k < 20; k++) { - _timeToLaser = TimeToLaserRate; // Set new shoot time - if (_lastSignalDron == -1) // If there's no last Drone shooting then... - { - _lastSignalDron = 0; // Take new Drone from the array - Main.projectile[Projectile.NewProjectile(npc.Center.X, npc.Center.Y, 0, 0, mod.ProjectileType("projMotherboardLaser"), LaserDamage, LaserKb, 0, npc.whoAmI, _signalDrones[_lastSignalDron])].localAI[1] = 1; - // Shoot the Drone from the boss - return; // Ending the method - } - ++_lastSignalDron; // Taking new Drone - if (_lastSignalDron < _signalDrones.Count) // Checking for exiting the bounds of array - Projectile.NewProjectile(npc.Center.X, npc.Center.Y, 0, 0, mod.ProjectileType("projMotherboardLaser"), LaserDamage, LaserKb, 0, _signalDrones[_lastSignalDron - 1], _signalDrones[_lastSignalDron]); - // Shoot laser - if (_lastSignalDron + 1 >= _signalDrones.Count) // If it is last drone then... - { - Vector2 vel = Helper.VelocityToPoint(Main.npc[_signalDrones[_signalDrones.Count - 1]].Center, Main.player[npc.target].Center, 15f); - for (int i = 0; i < SecondShootCount; i++) - { - Vector2 velocity = Helper.VelocityToPoint(Main.npc[_signalDrones[_signalDrones.Count - 1]].Center, Main.player[npc.target].Center, SecondShootSpeed); - velocity.X = velocity.X + Main.rand.Next(-SecondShootSpread, SecondShootSpread + 1) * SecondShootSpreadMult; - velocity.Y = velocity.Y + Main.rand.Next(-SecondShootSpread, SecondShootSpread + 1) * SecondShootSpreadMult; - Projectile.NewProjectile(npc.Center.X, npc.Center.Y, velocity.X, velocity.Y, LaserType, SecondShootDamage, SecondShootKn); - } - _lastSignalDron = -1; - _shootNow = false; - // Shooting the player with anotherl laser, setting last Drone to -1 and ending the cycle of shooting - } + Dust.NewDust(npc.position, npc.width, npc.height, 151, 2.5f * hitDirection, -2.5f, 0, default(Color), 0.7f); + } + + const int goreAmount = 4; + for (int i = 0; i < goreAmount; i++) + { + Gore.NewGore(npc.position, npc.velocity, mod.GetGoreSlot($"Gores/MotherboardGore{i + 1}")); } } } @@ -475,7 +524,7 @@ public override void NPCLoot() { NPC.downedMechBossAny = true; NPC.downedMechBoss1 = true; - TremorWorld.Boss.Motherboard.Downed(true); + TremorWorld.Boss.Motherboard.Downed(); if (Main.expertMode) { @@ -483,10 +532,18 @@ public override void NPCLoot() } else { - this.SpawnItem((short)mod.ItemType(), Main.rand.Next(20, 41)); - this.SpawnItem(ItemID.GreaterHealingPotion, Main.rand.Next(5, 16)); - this.SpawnItem(ItemID.HallowedBar, Main.rand.Next(15, 36)); - + if (Main.rand.NextBool()) + { + this.SpawnItem((short)mod.ItemType(), Main.rand.Next(20, 40)); + } + if (Main.rand.NextBool()) + { + this.SpawnItem(ItemID.GreaterHealingPotion, Main.rand.Next(5, 15)); + } + if (Main.rand.NextBool()) + { + this.SpawnItem(ItemID.HallowedBar, Main.rand.Next(15, 35)); + } if (Main.rand.Next(7) == 0) { this.SpawnItem((short)mod.ItemType()); @@ -505,11 +562,175 @@ public override void NPCLoot() { this.SpawnItem((short)mod.ItemType()); } - if (NPC.downedMoonlord - && Main.rand.NextBool(TremorWorld.Boss.Tremode.Downed() ? 1 : 2)) + + if (NPC.downedMoonlord && Main.rand.NextBool()) + { + this.SpawnItem((short)mod.ItemType(), Main.rand.Next(6, 12)); + } + } + + // AI + public void Move() + { + if (!IsTeleporting) + npc.position += npc.velocity * 2; + } + + private void UpdateTeleportVisuals() + { + if (_teleportTime > 0f) + { + if (teleportStyle == 0f) + { + _teleportTime -= 0.02f; + if (Main.rand.Next(100) >= 25f * _teleportTime) + { + Dust dust = Dust.NewDustDirect(npc.position, npc.width, npc.height, DustID.CrystalPulse); + dust.noGravity = true; + dust.scale = 1.2f; + dust.fadeIn = 0.4f; + } + } + _teleportTime -= 0.005f; + } + } + + public Rectangle GetFrame(int number) + { + return new Rectangle(0, npc.frame.Height * number, npc.frame.Width, npc.frame.Height); + } + + public bool isInsideTerrain() + { + for (int i = (int)npc.position.X - 8; i < (npc.position.X + 8 + npc.width); i += 8) + for (int l = (int)npc.Center.Y + 90; l < (npc.Center.Y + 106); l += 8) + if (WorldGen.SolidTile(i / 16, l / 16)) + return true; + return false; + } + + // Changes phase (Following/disappearing/appearing) + // this code should be revised + private void CyclePhases() + { + --(stage.stateTime); // Lowering states time + + if (stage.stateTime <= 0) // If state time < or = 0 then update a variable + stage.stateTime = stage.GetStateTime; // Updating + + // If it is time to appear + if (stage.stateTime <= GetAppearingTimeNow) + { + npc.ai[0] = -3; // Then appear + return; // Ending the method + } + + // If it is time to disappear + if (stage.stateTime <= GetAppearingTimeNow + GetDisappearingTimeNow) + { + npc.ai[0] = -2; // Then disappear + return; // Ending the method + } + + // Otherwise it's time to follow..... maybe? stage2? + if (npc.ai[0] == -2) + stage.appearTime = GetAppearingTimeNow; + + --(stage.appearTime); + if (stage.appearTime > 0) { - this.SpawnItem((short)mod.ItemType(), Main.rand.Next(6, 13)); + npc.ai[0] = -3; + return; } + + // else + npc.ai[0] = -1; // Follow the player } + + public override void AI() + { + stage.Animate(this); + stage.AdjustHead(this); + + // fly away/enrage/whatever if needed (skeletron aiStyle) + if (Helper.GetNearestPlayer(npc.position, true) == -1 || Main.dayTime) + { + npc.aiStyle = 11; + npc.damage = 1000; + npc.ai[0] = 2; + } + + // if flying away + if (npc.aiStyle == 11) + { + npc.rotation = 0; + return; + } + + // ini initialising + if (stage == stage0) + { + stage = stage1; + stage.Start(this); + } + + UpdateTeleportVisuals(); + + // move between phases + CyclePhases(); + + // execute the stage's AI + stage.AI(this); + } + + // // ?? Doesn't seem to fix much + // public override void SendExtraAI(BinaryWriter writer) + // { + // writer.Write(_appearTime); + // writer.Write(AIStage); + // writer.Write(_signalDrones.Count); + // foreach (int drone in _signalDrones) + // { + // writer.Write(drone); + // } + // writer.Write(_lastSignalDrone); + // writer.Write(_shootNow); + // writer.Write(_timeToNextDrone); + // writer.Write(_timeToShoot); + // writer.Write(_timeToLaser); + // writer.Write(_currentFrame); + // writer.Write(_timeToAnimation); + // writer.Write(_clampers.Count); + // foreach (int clamper in _clampers) + // { + // writer.Write(clamper); + // } + // writer.Write(_secondShootTime); + // } + + // public override void ReceiveExtraAI(BinaryReader reader) + // { + // _appearTime = reader.ReadInt32(); + // AIStage = reader.ReadInt32(); + // int c = reader.ReadInt32(); + // _signalDrones = new List(); + // for (int i = 0; i < c; i++) + // { + // _signalDrones[i] = reader.ReadInt32(); + // } + // _lastSignalDrone = reader.ReadInt32(); + // _shootNow = reader.ReadBoolean(); + // _timeToNextDrone = reader.ReadInt32(); + // _timeToShoot = reader.ReadInt32(); + // _timeToLaser = reader.ReadInt32(); + // _currentFrame = reader.ReadInt32(); + // _timeToAnimation = reader.ReadInt32(); + // c = reader.ReadInt32(); + // for (int i = 0; i < c; i++) + // { + // _clampers[i] = reader.ReadInt32(); + // } + // _secondShootTime = reader.ReadInt32(); + // } } } diff --git a/NPCs/PixieQueen.cs b/NPCs/PixieQueen.cs index f2ed7414..044f40e9 100644 --- a/NPCs/PixieQueen.cs +++ b/NPCs/PixieQueen.cs @@ -790,7 +790,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("ChaosBar"), Main.rand.Next(25, 30)); } - TremorWorld.Boss.PixieQueen.Downed(true); + TremorWorld.Boss.PixieQueen.Downed(); } } } diff --git a/NPCs/RuinGhost1.cs b/NPCs/RuinGhost1.cs index 73dd9a31..1fabe8e9 100644 --- a/NPCs/RuinGhost1.cs +++ b/NPCs/RuinGhost1.cs @@ -37,7 +37,7 @@ public override void SetDefaults() public override float SpawnChance(NPCSpawnInfo spawnInfo) { int[] TileArray2 = { mod.TileType("RuinAltar"), mod.TileType("RuinChest"), 120 }; - return TileArray2.Contains(Main.tile[spawnInfo.spawnTileX, spawnInfo.spawnTileY].type) && TremorWorld.Boss.TikiTotem.Downed() ? 45f : 0f; + return TileArray2.Contains(Main.tile[spawnInfo.spawnTileX, spawnInfo.spawnTileY].type) && TremorWorld.Boss.TikiTotem.IsDowned() ? 45f : 0f; } public override void HitEffect(int hitDirection, double damage) diff --git a/NPCs/RuinGhost2.cs b/NPCs/RuinGhost2.cs index 3c9f5c2c..825369b3 100644 --- a/NPCs/RuinGhost2.cs +++ b/NPCs/RuinGhost2.cs @@ -37,7 +37,7 @@ public override void SetDefaults() public override float SpawnChance(NPCSpawnInfo spawnInfo) { int[] TileArray2 = { mod.TileType("RuinAltar"), mod.TileType("RuinChest"), 120 }; - return TileArray2.Contains(Main.tile[spawnInfo.spawnTileX, spawnInfo.spawnTileY].type) && TremorWorld.Boss.TikiTotem.Downed() ? 45f : 0f; + return TileArray2.Contains(Main.tile[spawnInfo.spawnTileX, spawnInfo.spawnTileY].type) && TremorWorld.Boss.TikiTotem.IsDowned() ? 45f : 0f; } public override void HitEffect(int hitDirection, double damage) diff --git a/NPCs/SignalDron.cs b/NPCs/SignalDron.cs deleted file mode 100644 index 1d9105e7..00000000 --- a/NPCs/SignalDron.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using Microsoft.Xna.Framework; -using Terraria; -using Terraria.ID; -using Terraria.ModLoader; - -namespace Tremor.NPCs -{ - public class SignalDron : ModNPC - { - public override void SetStaticDefaults() - { - DisplayName.SetDefault("Signal Drone"); - } - - public override void SetDefaults() - { - npc.noTileCollide = true; - npc.noGravity = true; - npc.lifeMax = 1500; - npc.damage = 75; - npc.defense = 18; - npc.knockBackResist = 0f; - npc.width = 90; - npc.height = 90; - npc.aiStyle = 0; - npc.HitSound = SoundID.NPCHit4; - npc.DeathSound = SoundID.NPCDeath14; - } - - public override void AI() - { - if (Main.npc[(int)npc.ai[3]].type != mod.NPCType("Motherboard") || !Main.npc[(int)npc.ai[3]].active) - { - npc.active = false; - npc.netUpdate = true; - } - else if (npc.ai[0] == 0.0) - { - Vector2 vector2 = new Vector2(npc.Center.X, npc.Center.Y); - float num1 = Main.npc[(int)npc.ai[3]].Center.X - vector2.X; - float num2 = Main.npc[(int)npc.ai[3]].Center.Y - vector2.Y; - float num3 = (float)Math.Sqrt(num1 * (double)num1 + num2 * (double)num2); - if (num3 > 90.0) - { - float num4 = 8f / num3; - float num5 = num1 * num4; - float num6 = num2 * num4; - npc.velocity.X = (float)((npc.velocity.X * 15.0 + num5) / 16.0); - npc.velocity.Y = (float)((npc.velocity.Y * 15.0 + num6) / 16.0); - } - else - { - if (Math.Abs(npc.velocity.X) + (double)Math.Abs(npc.velocity.Y) < 8.0) - { - npc.velocity.Y *= 1.05f; - npc.velocity.X *= 1.05f; - } - if (Main.netMode == 1 || (!Main.expertMode || Main.rand.Next(100) != 0) && Main.rand.Next(200) != 0) - return; - npc.TargetClosest(true); - vector2 = new Vector2(npc.Center.X, npc.Center.Y); - float num4 = Main.player[npc.target].Center.X - vector2.X; - float num5 = Main.player[npc.target].Center.Y - vector2.Y; - float num6 = 8f / (float)Math.Sqrt(num4 * (double)num4 + num5 * (double)num5); - npc.velocity.X = num4 * num6; - npc.velocity.Y = num5 * num6; - npc.ai[0] = 1f; - npc.netUpdate = true; - } - } - else - { - if (Main.expertMode) - { - Vector2 vector2 = Main.player[npc.target].Center - npc.Center; - vector2.Normalize(); - npc.velocity = (npc.velocity * 99f + vector2 * 9f) / 100f; - } - Vector2 vector2_1 = new Vector2(npc.Center.X, npc.Center.Y); - float num1 = Main.npc[(int)npc.ai[3]].Center.X - vector2_1.X; - float num2 = Main.npc[(int)npc.ai[3]].Center.Y - vector2_1.Y; - if (Math.Sqrt(num1 * (double)num1 + num2 * (double)num2) <= 700.0 && !npc.justHit) - return; - npc.ai[0] = 0.0f; - } - } - } -} \ No newline at end of file diff --git a/NPCs/SignalDrone.cs b/NPCs/SignalDrone.cs new file mode 100644 index 00000000..90c2d3a0 --- /dev/null +++ b/NPCs/SignalDrone.cs @@ -0,0 +1,178 @@ +using System; +using System.Linq; +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace Tremor.NPCs +{ + public class SignalDrone : ModNPC + { + public override void SetStaticDefaults() + { + DisplayName.SetDefault("Signal Drone"); + } + + public override void SetDefaults() + { + npc.noTileCollide = true; + npc.noGravity = true; + npc.lifeMax = 1500; + npc.damage = 75; + npc.defense = 18; + npc.knockBackResist = 0.5f; + npc.width = 90; + npc.height = 90; + npc.aiStyle = 0; + npc.HitSound = SoundID.NPCHit4; + npc.DeathSound = SoundID.NPCDeath14; + } + + private void UpdateDroneImmunities() + { + foreach (NPC drone in Main.npc.Where(x => x.type == npc.type && npc.active)) + { + var signalDrone = drone.modNPC as SignalDrone; + if (signalDrone != null) + { + signalDrone._immuneTime = 180; + signalDrone.npc.netUpdate = true; + } + } + } + + protected void UpdateMyImmunity() + { + _immuneTime = Math.Max(0, _immuneTime - 1); + } + + public override void OnHitPlayer(Player target, int damage, bool crit) + { + UpdateDroneImmunities(); + } + + public override bool CanHitPlayer(Player target, ref int cooldownSlot) + => _immuneTime <= 0; + + + public override void AI() + { + CheckParent(); + SharknadoStyledAI(); + UpdateMyImmunity(); + } + + public override bool CheckActive() + => false; + + + private void CheckParent() + { + if (Motherboard.type != mod.NPCType("Motherboard") + || !Motherboard.active) + { + npc.active = false; + npc.netUpdate = true; + } + } + + private float _immuneTime + { + get { return npc.ai[1]; } + set { npc.ai[1] = value; } + } + private NPC Motherboard => Main.npc[(int)npc.ai[3]]; + + private void SharknadoStyledAI() + { + // The amount of spacing + // Default: 0.1f + const float spacingValue = 0.25f; + float spacingTreshold = (float)npc.width * 2f; + + // Some movement control + // This adds spacing between units + // So that they dont all stack on the same position because they have the same AI + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC otherNPC = Main.npc[i]; + if (i != npc.whoAmI // not us + && otherNPC.active // active + && otherNPC.type == npc.type // same type + && + // Basically this makes sure the padding movement code + // only kicks in if the units are within treshold range + Math.Abs(npc.position.X - otherNPC.position.X) // absolute x position difference + + Math.Abs(npc.position.Y - otherNPC.position.Y) // absolute y position difference + < spacingTreshold) // differences added up are lower than treshold + { + // Control X + if (npc.position.X < otherNPC.position.X) + { + npc.velocity.X = npc.velocity.X - spacingValue; + } + else + { + npc.velocity.X = npc.velocity.X + spacingValue; + } + + // Control Y + if (npc.position.Y < otherNPC.position.Y) + { + npc.velocity.Y = npc.velocity.Y - spacingValue; + } + else + { + npc.velocity.Y = npc.velocity.Y + spacingValue; + } + } + } + + npc.TargetClosest(); + + Entity target = + npc.target != -1 + && !Main.player[npc.target].dead + ? (Entity)Main.player[npc.target] + : (Entity)Motherboard; + + float velocityMultiplier = + (target as NPC) != null + ? 16f : 10f; // 16f is pretty much as fast as the player + + Vector2 distanceToTarget = target.Center - npc.Center + new Vector2(0f, -20f); + + if (Math.Abs(distanceToTarget.X) > 40f + || Math.Abs(distanceToTarget.Y) > 10f) + { + distanceToTarget.Normalize(); + distanceToTarget *= velocityMultiplier; + distanceToTarget *= new Vector2(1.25f, 0.65f); + npc.velocity = (npc.velocity * 20f + distanceToTarget) / 21f; + } + else + { + if (npc.velocity.X == 0f && npc.velocity.Y == 0f) + { + npc.velocity.X = -0.15f; + npc.velocity.Y = -0.05f; + } + npc.velocity *= 1.01f; + } + + // Rotation + npc.rotation = npc.velocity.X * 0.05f; + + // Sprite direction + if (npc.velocity.X > 0f) + { + npc.spriteDirection = (npc.direction = -1); + } + else if (npc.velocity.X < 0f) + { + npc.spriteDirection = (npc.direction = 1); + } + } + } +} \ No newline at end of file diff --git a/NPCs/SignalDron.png b/NPCs/SignalDrone.png similarity index 100% rename from NPCs/SignalDron.png rename to NPCs/SignalDrone.png diff --git a/NPCs/SoulofHope.cs b/NPCs/SoulofHope.cs index c10addd2..e0551f3b 100644 --- a/NPCs/SoulofHope.cs +++ b/NPCs/SoulofHope.cs @@ -322,7 +322,7 @@ public override void NPCLoot() Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("TrueEssense"), Main.rand.Next(10, 25)); } - if (!TremorWorld.Boss.Trinity.Downed()) + if (!TremorWorld.Boss.Trinity.IsDowned()) { Main.NewText("This world has been enlightened with Angelite!", 0, 191, 255); Main.NewText("This world has been attacked with Collapsium!", 255, 20, 147); @@ -336,7 +336,7 @@ public override void NPCLoot() { WorldGen.TileRunner(WorldGen.genRand.Next(0, Main.maxTilesX), WorldGen.genRand.Next((int)(Main.maxTilesY * .3f), (int)(Main.maxTilesY * .65f)), WorldGen.genRand.Next(9, 15), WorldGen.genRand.Next(9, 15), mod.TileType("AngeliteOreTile"), false, 0f, 0f, false, true); } - TremorWorld.Boss.Trinity.Downed(true); + TremorWorld.Boss.Trinity.Downed(); } } diff --git a/NPCs/SoulofTrust.cs b/NPCs/SoulofTrust.cs index cb8e184a..e8c2c20d 100644 --- a/NPCs/SoulofTrust.cs +++ b/NPCs/SoulofTrust.cs @@ -346,7 +346,7 @@ public override void NPCLoot() Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("TrueEssense"), Main.rand.Next(10, 25)); } - if (!TremorWorld.Boss.Trinity.Downed()) + if (!TremorWorld.Boss.Trinity.IsDowned()) { Main.NewText("This world has been enlightened with Angelite!", 0, 191, 255); Main.NewText("This world has been attacked with Collapsium!", 255, 20, 147); @@ -359,7 +359,7 @@ public override void NPCLoot() { WorldGen.TileRunner(WorldGen.genRand.Next(0, Main.maxTilesX), WorldGen.genRand.Next((int)(Main.maxTilesY * .3f), (int)(Main.maxTilesY * .65f)), WorldGen.genRand.Next(9, 15), WorldGen.genRand.Next(9, 15), mod.TileType("AngeliteOreTile"), false, 0f, 0f, false, true); } - TremorWorld.Boss.Trinity.Downed(true); + TremorWorld.Boss.Trinity.Downed(); } } diff --git a/NPCs/SoulofTruth.cs b/NPCs/SoulofTruth.cs index 064cc203..f09ca9e7 100644 --- a/NPCs/SoulofTruth.cs +++ b/NPCs/SoulofTruth.cs @@ -320,7 +320,7 @@ public override void NPCLoot() Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("TrueEssense"), Main.rand.Next(10, 25)); } - if (!TremorWorld.Boss.Trinity.Downed()) + if (!TremorWorld.Boss.Trinity.IsDowned()) { Main.NewText("This world has been enlightened with Angelite!", 0, 191, 255); Main.NewText("This world has been attacked with Collapsium!", 255, 20, 147); @@ -334,7 +334,7 @@ public override void NPCLoot() { WorldGen.TileRunner(WorldGen.genRand.Next(0, Main.maxTilesX), WorldGen.genRand.Next((int)(Main.maxTilesY * .3f), (int)(Main.maxTilesY * .65f)), WorldGen.genRand.Next(9, 15), WorldGen.genRand.Next(9, 15), mod.TileType("AngeliteOreTile"), false, 0f, 0f, false, true); } - TremorWorld.Boss.Trinity.Downed(true); + TremorWorld.Boss.Trinity.Downed(); } } diff --git a/NPCs/SpaceWhale.cs b/NPCs/SpaceWhale.cs index 1d097a82..6b0a6bbd 100644 --- a/NPCs/SpaceWhale.cs +++ b/NPCs/SpaceWhale.cs @@ -790,19 +790,19 @@ public override void NPCLoot() int halfLength = npc.width / 2 / 16 + 1; - if (!TremorWorld.Boss.SpaceWhale.Downed()) + if (!TremorWorld.Boss.SpaceWhale.IsDowned()) { Main.NewText("A comet has struck the ground!", 117, 187, 253); TremorWorld.dropComet(); //return; } - if (TremorWorld.Boss.SpaceWhale.Downed() && Main.rand.Next(3) == 0) + if (TremorWorld.Boss.SpaceWhale.IsDowned() && Main.rand.Next(3) == 0) { Main.NewText("A comet has struck the ground!", 117, 187, 253); TremorWorld.dropComet(); //return; } - TremorWorld.Boss.SpaceWhale.Downed(true); + TremorWorld.Boss.SpaceWhale.Downed(); if (!Main.expertMode && Main.rand.Next(7) == 0) { diff --git a/NPCs/Startrooper.cs b/NPCs/Startrooper.cs index 53ccedda..e7e5758b 100644 --- a/NPCs/Startrooper.cs +++ b/NPCs/Startrooper.cs @@ -49,7 +49,7 @@ public override void SetDefaults() public override bool CanTownNPCSpawn(int numTownNPCs, int money) { - if (TremorWorld.Boss.SpaceWhale.Downed()) + if (TremorWorld.Boss.SpaceWhale.IsDowned()) { return true; } @@ -136,12 +136,12 @@ public override void SetupShop(Chest shop, ref int nextSlot) shop.item[nextSlot].SetDefaults(mod.ItemType("StartrooperFlameburstPistol")); nextSlot++; } - if (TremorWorld.Boss.Trinity.Downed()) + if (TremorWorld.Boss.Trinity.IsDowned()) { shop.item[nextSlot].SetDefaults(mod.ItemType("WartimeRocketLauncher")); nextSlot++; } - if (TremorWorld.Boss.Trinity.Downed() && !Main.dayTime) + if (TremorWorld.Boss.Trinity.IsDowned() && !Main.dayTime) { shop.item[nextSlot].SetDefaults(mod.ItemType("CosmicAssaultRifle")); nextSlot++; diff --git a/NPCs/StormJellyfish.cs b/NPCs/StormJellyfish.cs index 0b133b85..85694ce4 100644 --- a/NPCs/StormJellyfish.cs +++ b/NPCs/StormJellyfish.cs @@ -165,7 +165,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("StormJellyfishTrophy")); } - TremorWorld.Boss.StormJellyfish.Downed(true); + TremorWorld.Boss.StormJellyfish.Downed(); } } diff --git a/NPCs/TheDarkEmperorTwo.cs b/NPCs/TheDarkEmperorTwo.cs index efa4375e..c56603ed 100644 --- a/NPCs/TheDarkEmperorTwo.cs +++ b/NPCs/TheDarkEmperorTwo.cs @@ -427,7 +427,7 @@ public override void NPCLoot() { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("SoulofFight"), Main.rand.Next(20, 30)); } - TremorWorld.Boss.DarkEmperor.Downed(true); + TremorWorld.Boss.DarkEmperor.Downed(); } } } diff --git a/NPCs/TikiTotem.cs b/NPCs/TikiTotem.cs index 3aa72e07..0d7cdf26 100644 --- a/NPCs/TikiTotem.cs +++ b/NPCs/TikiTotem.cs @@ -475,7 +475,7 @@ public override void NPCLoot() Item.NewItem((int)npc.Center.X, (int)npc.Center.Y, npc.width, npc.height, mod.ItemType("TikiTotemTrophy")); } - TremorWorld.Boss.TikiTotem.Downed(true); + TremorWorld.Boss.TikiTotem.Downed(); string msg = "Ghosts are returning to ruins..."; Main.NewText(msg, 193, 139, 77); diff --git a/NPCs/TremorNPC.cs b/NPCs/TremorNPC.cs index d6332f6b..dcc8c70e 100644 --- a/NPCs/TremorNPC.cs +++ b/NPCs/TremorNPC.cs @@ -287,7 +287,7 @@ public override void NPCLoot(NPC npc) mod.ItemType("Arachnophobia")); } - if (!TremorWorld.Boss.Motherboard.Downed() && Main.hardMode && Main.rand.Next(2500) == 0) + if (!TremorWorld.Boss.Motherboard.IsDowned() && Main.hardMode && Main.rand.Next(2500) == 0) { Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("MechanicalBrain")); @@ -877,7 +877,7 @@ public override void NPCLoot(NPC npc) if (npc.type == 398) { - if (!TremorWorld.Boss.Tremode.Downed()) // ਬ ६ + if (!TremorWorld.Boss.Tremode.IsDowned()) // ਬ ६ { Main.NewText("Nightmares became reality!", 90, 0, 157); Main.NewText("The moon slowly drifts towards the Earth...", 0, 255, 255); @@ -913,7 +913,7 @@ public override void NPCLoot(NPC npc) } } - TremorWorld.Boss.Tremode.Downed(true); // 㡠 ६ + TremorWorld.Boss.Tremode.Downed(); // 㡠 ६ } } diff --git a/NPCs/Undertaker.cs b/NPCs/Undertaker.cs index 7b957cec..dedd81a9 100644 --- a/NPCs/Undertaker.cs +++ b/NPCs/Undertaker.cs @@ -48,7 +48,7 @@ public override void SetDefaults() public override bool CanTownNPCSpawn(int numTownNPCs, int money) { - if (TremorWorld.Boss.Trinity.Downed()) + if (TremorWorld.Boss.Trinity.IsDowned()) { return true; } diff --git a/NPCs/npcVultureKing.cs b/NPCs/npcVultureKing.cs index 4500f7ce..94827d52 100644 --- a/NPCs/npcVultureKing.cs +++ b/NPCs/npcVultureKing.cs @@ -249,7 +249,7 @@ public override void NPCLoot() Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, mod.ItemType("SandstoneBar"), Main.rand.Next(10, 18)); } - TremorWorld.Boss.Rukh.Downed(true); + TremorWorld.Boss.Rukh.Downed(); } } diff --git a/NovaPillar/NovaPillar.cs b/NovaPillar/NovaPillar.cs index a4ab004b..3e44c2b6 100644 --- a/NovaPillar/NovaPillar.cs +++ b/NovaPillar/NovaPillar.cs @@ -205,7 +205,7 @@ public override void NPCLoot() Item.NewItem((int)npc.position.X + Main.rand.Next(npc.width), (int)npc.position.Y + Main.rand.Next(npc.height), 2, 2, mod.ItemType("NovaFragment"), Main.rand.Next(1, 4)); } - TremorWorld.Boss.NovaPillar.Downed(true); + TremorWorld.Boss.NovaPillar.Downed(); NovaHandler.TowerX = -1; NovaHandler.TowerY = -1; if (NPC.LunarApocalypseIsUp) diff --git a/Projectiles/projMotherboardLaser.cs b/Projectiles/projMotherboardLaser.cs index de5a53b8..a9b20413 100644 --- a/Projectiles/projMotherboardLaser.cs +++ b/Projectiles/projMotherboardLaser.cs @@ -1,18 +1,19 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Terraria; +using Terraria.Audio; +using Terraria.ID; using Terraria.ModLoader; namespace Tremor.Projectiles { public class projMotherboardLaser : ModProjectile { - const float YOffset = 95f; // �������� �� Y ����� ������ ������ - Color LaserColor = Color.Purple; // ���� ������ + const float YOffset = 95f; + Color LaserColor = Color.Purple; public override void SetDefaults() { - projectile.width = 2; projectile.height = 2; projectile.timeLeft = 30; @@ -26,10 +27,8 @@ public override void SetDefaults() public override void SetStaticDefaults() { DisplayName.SetDefault("Motherboard Laser"); - } - - + public override void AI() { projectile.Center = new Vector2(Main.npc[(int)projectile.ai[0]].Center.X, Main.npc[(int)projectile.ai[0]].Center.Y + ((projectile.localAI[1] == 1) ? YOffset : 0)); @@ -52,6 +51,23 @@ public override void AI() return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(), projectile.Center, endPoint, 4f, ref point); } + public override bool OnTileCollide(Vector2 oldVelocity) + { + PlayZapSound(); + return base.OnTileCollide(oldVelocity); + } + + public override void OnHitPlayer(Player target, int damage, bool crit) + { + PlayZapSound(); + } + + private void PlayZapSound() + { + var zapSound = new LegacySoundStyle(SoundID.Trackable, TremorUtils.GetIdForSoundName($"dd2_lightning_bug_zap_{Main.rand.Next(3)}")); + Main.PlayTrackedSound(zapSound.WithPitchVariance(Main.rand.NextFloat() * .5f).WithVolume(Main.soundVolume * 0.5f)); + } + public override bool PreDraw(SpriteBatch spriteBatch, Color lightColor) { Vector2 endPoint = Main.npc[(int)projectile.ai[1]].Center; diff --git a/Projectiles/projMotherboardSuperLaser.cs b/Projectiles/projMotherboardSuperLaser.cs index 6c01b1fb..574400c9 100644 --- a/Projectiles/projMotherboardSuperLaser.cs +++ b/Projectiles/projMotherboardSuperLaser.cs @@ -1,6 +1,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Terraria; +using Terraria.Audio; using Terraria.ID; using Terraria.ModLoader; @@ -31,7 +32,6 @@ Vector2 endPoint public override void SetDefaults() { - projectile.width = 16; projectile.height = 16; projectile.timeLeft = 2; @@ -46,10 +46,8 @@ public override void SetDefaults() public override void SetStaticDefaults() { DisplayName.SetDefault("Motherboard Super Laser"); - } - - + bool flag = true; public override void AI() { @@ -59,8 +57,8 @@ public override void AI() XOffsetNow = (projectile.ai[1] != 0) ? XOffsetMax : -XOffsetMax; } projectile.Center = new Vector2(Main.npc[(int)projectile.ai[0]].Center.X - 4, Main.npc[(int)projectile.ai[0]].Center.Y + 88f); - for (int i = 0; i < DustCount; i++) - Dust.NewDust(new Vector2(endPoint.X - 10, endPoint.Y + 10), 20, 20, DustID.Shadowflame); + //for (int i = 0; i < DustCount; i++) + // Dust.NewDust(new Vector2(endPoint.X - 10, endPoint.Y + 10), 20, 20, DustID.Shadowflame); if (projectile.ai[1] != 0) { XOffsetNow -= XOffsetStep; @@ -83,6 +81,27 @@ public override void AI() return Collision.CheckAABBvLineCollision(targetHitbox.TopLeft(), targetHitbox.Size(), projectile.Center, endPoint, 4f, ref point); } + public override bool OnTileCollide(Vector2 oldVelocity) + { + for (int i = 0; i < DustCount; i++) + Dust.NewDust(new Vector2(endPoint.X - 10, endPoint.Y + 10), 20, 20, DustID.Shadowflame); + + PlayCollidingSound(); + + return base.OnTileCollide(oldVelocity); + } + + public override void OnHitPlayer(Player target, int damage, bool crit) + { + PlayCollidingSound(); + } + + private void PlayCollidingSound() + { + var zapSound = new LegacySoundStyle(SoundID.Trackable, TremorUtils.GetIdForSoundName($"dd2_sky_dragons_fury_circle_{Main.rand.Next(3)}")); + Main.PlayTrackedSound(zapSound.WithPitchVariance(Main.rand.NextFloat()).WithVolume(Main.soundVolume * 1.5f)); + } + public override bool PreDraw(SpriteBatch spriteBatch, Color lightColor) { Vector2 unit = endPoint - projectile.Center; diff --git a/RecipeUtils.cs b/RecipeUtils.cs index 83c04263..2cc36f36 100644 --- a/RecipeUtils.cs +++ b/RecipeUtils.cs @@ -32,6 +32,7 @@ public static void AddRecipes(Mod mod) #region AddRecipes // Quick Recipe example. Too lazy to change all + // Ideally, there is separation between what holds data and what processes it QuickRecipe(mod, ItemID.MagicMirror, new int[,] { diff --git a/Tremor.cs b/Tremor.cs index cb7b062a..f8420c4a 100644 --- a/Tremor.cs +++ b/Tremor.cs @@ -159,26 +159,26 @@ public override void PostSetupContent() Mod bossChecklist = ModLoader.GetMod("BossChecklist"); if (bossChecklist != null) { - bossChecklist.Call("AddBossWithInfo", "Rukh", 2.7f, (Func)(() => TremorWorld.Boss.Rukh.Downed()), "Use a [i:" + ItemType("DesertCrown") + "] in Desert"); - bossChecklist.Call("AddBossWithInfo", "Tiki Totem", 3.3f, (Func)(() => TremorWorld.Boss.TikiTotem.Downed()), "Use a [i:" + ItemType("MysteriousDrum") + "] in Jungle at night after beating Eye of Cthulhu"); - bossChecklist.Call("AddBossWithInfo", "Evil Corn", 3.4f, (Func)(() => TremorWorld.Boss.EvilCorn.Downed()), "Use a [i:" + ItemType("CursedPopcorn") + "] at night"); - bossChecklist.Call("AddBossWithInfo", "Storm Jellyfish", 3.5f, (Func)(() => TremorWorld.Boss.StormJellyfish.Downed()), "Use a [i:" + ItemType("StormJelly") + "]"); - bossChecklist.Call("AddBossWithInfo", "Ancient Dragon", 3.6f, (Func)(() => TremorWorld.Boss.AncientDragon.Downed()), "Use a [i:" + ItemType("RustyLantern") + "] in Ruins after pressing with RMB on Ruin Altar and getting Ruin Powers buff"); - bossChecklist.Call("AddBossWithInfo", "Fungus Beetle", 5.5f, (Func)(() => TremorWorld.Boss.FungusBeetle.Downed()), "Use a [i:" + ItemType("MushroomCrystal") + "]"); - bossChecklist.Call("AddBossWithInfo", "Heater of Worlds", 5.6f, (Func)(() => TremorWorld.Boss.HeaterofWorlds.Downed()), "Use a [i:" + ItemType("MoltenHeart") + "] in Underworld"); - bossChecklist.Call("AddBossWithInfo", "Alchemaster", 6.5f, (Func)(() => TremorWorld.Boss.Alchemaster.Downed()), "Use a [i:" + ItemType("AncientMosaic") + "] at night"); - bossChecklist.Call("AddBossWithInfo", "Motherboard (Destroyer alt)", 8.01f, (Func)(() => TremorWorld.Boss.Motherboard.Downed()), "Use a [i:" + ItemType("MechanicalBrain") + "] at night"); - bossChecklist.Call("AddBossWithInfo", "Pixie Queen", 9.6f, (Func)(() => TremorWorld.Boss.PixieQueen.Downed()), "Use a [i:" + ItemType("PixieinaJar") + "] in Hallow at night"); - bossChecklist.Call("AddBossWithInfo", "Wall of Shadows", 10.7f, (Func)(() => TremorWorld.Boss.WallOfShadow.Downed()), "Throw a [i:" + ItemType("ShadowRelic") + "] into lava in Underworld after beating Plantera and having the Dryad alive "); - bossChecklist.Call("AddBossWithInfo", "Frost King", 10.6f, (Func)(() => TremorWorld.Boss.FrostKing.Downed()), "Use a [i:" + ItemType("FrostCrown") + "] in Snow"); - bossChecklist.Call("AddBossWithInfo", "Cog Lord", 11.4f, (Func)(() => TremorWorld.Boss.CogLord.Downed()), "Use a [i:" + ItemType("ArtifactEngine") + "] at night"); - bossChecklist.Call("AddBossWithInfo", "Cyber King", 11.5f, (Func)(() => TremorWorld.Boss.CyberKing.Downed()), "Use a [i:" + ItemType("AdvancedCircuit") + "] at night to summon a Mothership which will spawn Cyber King on death"); - bossChecklist.Call("AddBossWithInfo", "Nova Pillar", 13.5f, (Func)(() => TremorWorld.Boss.NovaPillar.Downed()), "Kill the Lunatic Cultist outside the dungeon post-Golem"); - bossChecklist.Call("AddBossWithInfo", "The Dark Emperor", 14.4f, (Func)(() => TremorWorld.Boss.DarkEmperor.Downed()), "Use a [i:" + ItemType("EmperorCrown") + "]"); - bossChecklist.Call("AddBossWithInfo", "Brutallisk", 14.5f, (Func)(() => TremorWorld.Boss.Brutallisk.Downed()), "Use a [i:" + ItemType("RoyalEgg") + "] in Desert"); - bossChecklist.Call("AddBossWithInfo", "Space Whale", 14.6f, (Func)(() => TremorWorld.Boss.SpaceWhale.Downed()), "Use a [i:" + ItemType("CosmicKrill") + "]"); - bossChecklist.Call("AddBossWithInfo", "The Trinity", 14.7f, (Func)(() => TremorWorld.Boss.Trinity.Downed()), "Use a [i:" + ItemType("StoneofKnowledge") + "] at night"); - bossChecklist.Call("AddBossWithInfo", "Andas", 14.8f, (Func)(() => TremorWorld.Boss.Andas.Downed()), "Use a [i:" + ItemType("InfernoSkull") + "] at Underworld"); + bossChecklist.Call("AddBossWithInfo", "Rukh", 2.7f, (Func)(() => TremorWorld.Boss.Rukh.IsDowned()), "Use a [i:" + ItemType("DesertCrown") + "] in Desert"); + bossChecklist.Call("AddBossWithInfo", "Tiki Totem", 3.3f, (Func)(() => TremorWorld.Boss.TikiTotem.IsDowned()), "Use a [i:" + ItemType("MysteriousDrum") + "] in Jungle at night after beating Eye of Cthulhu"); + bossChecklist.Call("AddBossWithInfo", "Evil Corn", 3.4f, (Func)(() => TremorWorld.Boss.EvilCorn.IsDowned()), "Use a [i:" + ItemType("CursedPopcorn") + "] at night"); + bossChecklist.Call("AddBossWithInfo", "Storm Jellyfish", 3.5f, (Func)(() => TremorWorld.Boss.StormJellyfish.IsDowned()), "Use a [i:" + ItemType("StormJelly") + "]"); + bossChecklist.Call("AddBossWithInfo", "Ancient Dragon", 3.6f, (Func)(() => TremorWorld.Boss.AncientDragon.IsDowned()), "Use a [i:" + ItemType("RustyLantern") + "] in Ruins after pressing with RMB on Ruin Altar and getting Ruin Powers buff"); + bossChecklist.Call("AddBossWithInfo", "Fungus Beetle", 5.5f, (Func)(() => TremorWorld.Boss.FungusBeetle.IsDowned()), "Use a [i:" + ItemType("MushroomCrystal") + "]"); + bossChecklist.Call("AddBossWithInfo", "Heater of Worlds", 5.6f, (Func)(() => TremorWorld.Boss.HeaterofWorlds.IsDowned()), "Use a [i:" + ItemType("MoltenHeart") + "] in Underworld"); + bossChecklist.Call("AddBossWithInfo", "Alchemaster", 6.5f, (Func)(() => TremorWorld.Boss.Alchemaster.IsDowned()), "Use a [i:" + ItemType("AncientMosaic") + "] at night"); + bossChecklist.Call("AddBossWithInfo", "Motherboard (Destroyer alt)", 8.01f, (Func)(() => TremorWorld.Boss.Motherboard.IsDowned()), "Use a [i:" + ItemType("MechanicalBrain") + "] at night"); + bossChecklist.Call("AddBossWithInfo", "Pixie Queen", 9.6f, (Func)(() => TremorWorld.Boss.PixieQueen.IsDowned()), "Use a [i:" + ItemType("PixieinaJar") + "] in Hallow at night"); + bossChecklist.Call("AddBossWithInfo", "Wall of Shadows", 10.7f, (Func)(() => TremorWorld.Boss.WallOfShadow.IsDowned()), "Throw a [i:" + ItemType("ShadowRelic") + "] into lava in Underworld after beating Plantera and having the Dryad alive "); + bossChecklist.Call("AddBossWithInfo", "Frost King", 10.6f, (Func)(() => TremorWorld.Boss.FrostKing.IsDowned()), "Use a [i:" + ItemType("FrostCrown") + "] in Snow"); + bossChecklist.Call("AddBossWithInfo", "Cog Lord", 11.4f, (Func)(() => TremorWorld.Boss.CogLord.IsDowned()), "Use a [i:" + ItemType("ArtifactEngine") + "] at night"); + bossChecklist.Call("AddBossWithInfo", "Cyber King", 11.5f, (Func)(() => TremorWorld.Boss.CyberKing.IsDowned()), "Use a [i:" + ItemType("AdvancedCircuit") + "] at night to summon a Mothership which will spawn Cyber King on death"); + bossChecklist.Call("AddBossWithInfo", "Nova Pillar", 13.5f, (Func)(() => TremorWorld.Boss.NovaPillar.IsDowned()), "Kill the Lunatic Cultist outside the dungeon post-Golem"); + bossChecklist.Call("AddBossWithInfo", "The Dark Emperor", 14.4f, (Func)(() => TremorWorld.Boss.DarkEmperor.IsDowned()), "Use a [i:" + ItemType("EmperorCrown") + "]"); + bossChecklist.Call("AddBossWithInfo", "Brutallisk", 14.5f, (Func)(() => TremorWorld.Boss.Brutallisk.IsDowned()), "Use a [i:" + ItemType("RoyalEgg") + "] in Desert"); + bossChecklist.Call("AddBossWithInfo", "Space Whale", 14.6f, (Func)(() => TremorWorld.Boss.SpaceWhale.IsDowned()), "Use a [i:" + ItemType("CosmicKrill") + "]"); + bossChecklist.Call("AddBossWithInfo", "The Trinity", 14.7f, (Func)(() => TremorWorld.Boss.Trinity.IsDowned()), "Use a [i:" + ItemType("StoneofKnowledge") + "] at night"); + bossChecklist.Call("AddBossWithInfo", "Andas", 14.8f, (Func)(() => TremorWorld.Boss.Andas.IsDowned()), "Use a [i:" + ItemType("InfernoSkull") + "] at Underworld"); } } diff --git a/Tremor.csproj b/Tremor.csproj index 7ec12160..f0a25299 100644 --- a/Tremor.csproj +++ b/Tremor.csproj @@ -188,6 +188,8 @@ + + @@ -240,7 +242,6 @@ - @@ -1910,7 +1911,6 @@ - @@ -2140,7 +2140,7 @@ - + @@ -6134,7 +6134,6 @@ - diff --git a/TremorUtils.cs b/TremorUtils.cs index 2336c6c5..7a97345a 100644 --- a/TremorUtils.cs +++ b/TremorUtils.cs @@ -2,6 +2,7 @@ using Microsoft.Xna.Framework.Graphics; using Terraria; using Terraria.DataStructures; +using Terraria.ID; using Terraria.ModLoader; using Terraria.Utilities; @@ -9,6 +10,30 @@ namespace Tremor { public static class TremorUtils { + public static void Downed(this TremorWorld.Boss boss, bool state = true) + => TremorWorld.downedBoss[boss] = state; + + public static bool IsDowned(this TremorWorld.Boss boss) + => TremorWorld.downedBoss[boss]; + + public static Item SpawnItem(this ModNPC npc, short type, int stack = 1) + => SpawnItem(npc.npc, type, stack); + + public static Item SpawnItem(this NPC npc, short type, int stack = 1) + => Main.item[Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, type, stack)]; + + public static Item SpawnItem(Vector2 position, Vector2 size, short type, int stack = 1) + => Main.item[Item.NewItem((int)position.X, (int)position.Y, (int)size.X, (int)size.Y, type, stack)]; + + // Get an ID for a sound name + public static int GetIdForSoundName(string soundName) + { + for (int i = 0; i < SoundID.TrackableLegacySoundCount; i++) + if (SoundID.GetTrackableLegacySoundPath(i).EndsWith(soundName)) + return i; + return 0; + } + public static bool NextBool(this UnifiedRandom rand, int total) => rand.Next(total) == 0;