From b30fcc9563ee8b98e127cdd9a6a98edbcf6f700c Mon Sep 17 00:00:00 2001 From: Niko Date: Thu, 20 Nov 2025 16:22:50 +0000 Subject: [PATCH] Finish set bonuses, add more options to the Avali Trader --- .../ProtogenSentinelGlobalProjectile.cs | 23 ++ .../AvaliVanguardSet/AvaliVanguardCowl.cs | 3 +- .../AvaliVanguardSet/AvaliVanguardHelmet.cs | 3 +- .../AvaliVanguardSet/AvaliVanguardHood.cs | 3 +- .../AvaliVanguardSet/AvaliVanguardMask.cs | 3 +- .../ProtogenOperativeCowl.cs | 3 +- .../ProtogenOperativeHelmet.cs | 3 +- .../ProtogenOperativeHood.cs | 3 +- .../ProtogenOperativeMask.cs | 3 +- .../ProtogenSentinelCowl.cs | 3 +- .../ProtogenSentinelHelmet.cs | 3 +- .../ProtogenSentinelHood.cs | 3 +- .../ProtogenSentinelMask.cs | 3 +- .../Armor/SingularitySet/SingularityHelmet.cs | 5 +- .../TranscendentSet/TranscendentSetEffects.cs | 5 +- Content/NPCs/TownNPCs/AvalianTrader.cs | 194 +++++++++++++--- Content/Players/AvaliVanguardPlayer.cs | 63 +++++ Content/Players/ProtogenOperativePlayer.cs | 71 ++++++ Content/Players/ProtogenSentinelPlayer.cs | 125 ++++++++++ Content/Players/SingularityPlayer.cs | 56 +++++ Content/Players/TranscendentPlayer.cs | 219 +++++++++++++++--- .../Projectiles/Avali/AvaliHuntingDrone.cs | 126 ++++++++++ .../Projectiles/Avali/AvaliHuntingDrone.png | Bin 0 -> 504 bytes .../Avali/AvaliHuntingDroneShot.cs | 52 +++++ .../Avali/AvaliHuntingDroneShot.png | Bin 0 -> 504 bytes .../Protogen/ProtogenOperativeNanite.cs | 115 +++++++++ .../Protogen/ProtogenOperativeNanite.png | Bin 0 -> 504 bytes .../Protogen/ProtogenSentinelCore.cs | 124 ++++++++++ .../Protogen/ProtogenSentinelCore.png | Bin 0 -> 504 bytes .../Protogen/ProtogenSentinelCoreBeam.cs | 50 ++++ .../Protogen/ProtogenSentinelCoreBeam.png | Bin 0 -> 504 bytes .../Protogen/ProtogenSentinelFragment.cs | 87 +++++++ .../Protogen/ProtogenSentinelFragment.png | Bin 0 -> 504 bytes .../Projectiles/Singularity/SingularityOrb.cs | 123 ++++++++++ .../Singularity/SingularityOrb.png | Bin 0 -> 504 bytes .../Singularity/SingularityOrbBeam.cs | 42 ++++ .../Singularity/SingularityOrbBeam.png | Bin 0 -> 504 bytes .../Transcendent/TranscendentAscendantBolt.cs | 76 ++++++ .../TranscendentAscendantBolt.png | Bin 0 -> 504 bytes .../TranscendentAscendantDrone.cs | 130 +++++++++++ .../TranscendentAscendantDrone.png | Bin 0 -> 504 bytes .../Transcendent/TranscendentNaniteShard.cs | 81 +++++++ .../Transcendent/TranscendentNaniteShard.png | Bin 0 -> 504 bytes .../en-US_Mods.ForgottenWawiTech.hjson | 22 ++ 44 files changed, 1744 insertions(+), 81 deletions(-) create mode 100644 Content/GlobalProjectiles/ProtogenSentinelGlobalProjectile.cs create mode 100644 Content/Players/AvaliVanguardPlayer.cs create mode 100644 Content/Players/ProtogenOperativePlayer.cs create mode 100644 Content/Players/ProtogenSentinelPlayer.cs create mode 100644 Content/Players/SingularityPlayer.cs create mode 100644 Content/Projectiles/Avali/AvaliHuntingDrone.cs create mode 100644 Content/Projectiles/Avali/AvaliHuntingDrone.png create mode 100644 Content/Projectiles/Avali/AvaliHuntingDroneShot.cs create mode 100644 Content/Projectiles/Avali/AvaliHuntingDroneShot.png create mode 100644 Content/Projectiles/Protogen/ProtogenOperativeNanite.cs create mode 100644 Content/Projectiles/Protogen/ProtogenOperativeNanite.png create mode 100644 Content/Projectiles/Protogen/ProtogenSentinelCore.cs create mode 100644 Content/Projectiles/Protogen/ProtogenSentinelCore.png create mode 100644 Content/Projectiles/Protogen/ProtogenSentinelCoreBeam.cs create mode 100644 Content/Projectiles/Protogen/ProtogenSentinelCoreBeam.png create mode 100644 Content/Projectiles/Protogen/ProtogenSentinelFragment.cs create mode 100644 Content/Projectiles/Protogen/ProtogenSentinelFragment.png create mode 100644 Content/Projectiles/Singularity/SingularityOrb.cs create mode 100644 Content/Projectiles/Singularity/SingularityOrb.png create mode 100644 Content/Projectiles/Singularity/SingularityOrbBeam.cs create mode 100644 Content/Projectiles/Singularity/SingularityOrbBeam.png create mode 100644 Content/Projectiles/Transcendent/TranscendentAscendantBolt.cs create mode 100644 Content/Projectiles/Transcendent/TranscendentAscendantBolt.png create mode 100644 Content/Projectiles/Transcendent/TranscendentAscendantDrone.cs create mode 100644 Content/Projectiles/Transcendent/TranscendentAscendantDrone.png create mode 100644 Content/Projectiles/Transcendent/TranscendentNaniteShard.cs create mode 100644 Content/Projectiles/Transcendent/TranscendentNaniteShard.png diff --git a/Content/GlobalProjectiles/ProtogenSentinelGlobalProjectile.cs b/Content/GlobalProjectiles/ProtogenSentinelGlobalProjectile.cs new file mode 100644 index 0000000..ec6139d --- /dev/null +++ b/Content/GlobalProjectiles/ProtogenSentinelGlobalProjectile.cs @@ -0,0 +1,23 @@ +using Terraria; +using Terraria.DataStructures; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Projectiles.Protogen; + +namespace ForgottenWawiTech.Content.GlobalProjectiles +{ + public class ProtogenSentinelGlobalProjectile : GlobalProjectile + { + public bool DisableSentinelSplit { get; set; } + + public override bool InstancePerEntity => true; + + public override void OnSpawn(Projectile projectile, IEntitySource source) + { + if (projectile.type == ModContent.ProjectileType() || + projectile.type == ModContent.ProjectileType()) + { + DisableSentinelSplit = true; + } + } + } +} diff --git a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardCowl.cs b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardCowl.cs index 42ba975..c29ec65 100644 --- a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardCowl.cs +++ b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardCowl.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.AvaliSkirmisherSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet { @@ -44,7 +45,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet } player.endurance += 0.10f; - // TODO: Implement hunting drone projectile system + player.GetModPlayer().EnableHuntingDrones(); } public override void AddRecipes() diff --git a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHelmet.cs b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHelmet.cs index c420fcf..7436946 100644 --- a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHelmet.cs +++ b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHelmet.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.AvaliSkirmisherSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet { @@ -45,7 +46,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet } player.endurance += 0.10f; - // TODO: Implement hunting drone projectile system + player.GetModPlayer().EnableHuntingDrones(); } public override void AddRecipes() diff --git a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHood.cs b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHood.cs index 174d519..f97bbf5 100644 --- a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHood.cs +++ b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardHood.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.AvaliSkirmisherSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet { @@ -45,7 +46,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet } player.endurance += 0.10f; - // TODO: Implement hunting drone projectile system + player.GetModPlayer().EnableHuntingDrones(); } public override void AddRecipes() diff --git a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardMask.cs b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardMask.cs index e8280a1..768055c 100644 --- a/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardMask.cs +++ b/Content/Items/Armor/AvaliVanguardSet/AvaliVanguardMask.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.AvaliSkirmisherSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet { @@ -44,7 +45,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.AvaliVanguardSet } player.endurance += 0.10f; - // TODO: Implement hunting drone projectile system + player.GetModPlayer().EnableHuntingDrones(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeCowl.cs b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeCowl.cs index 015b228..11f36c6 100644 --- a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeCowl.cs +++ b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeCowl.cs @@ -4,6 +4,7 @@ using Terraria.ModLoader; using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Items.Armor.ProtogenInitiateSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet { @@ -37,7 +38,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet player.setBonus = "Taking damage releases seeking nanites\nNanites heal you for 50% of damage dealt\n+2 max minions and +18% summon damage"; player.maxMinions += 2; player.GetDamage(DamageClass.Summon) += 0.18f; - // TODO: Implement nanite swarm healing system + player.GetModPlayer().EnableNaniteSwarm(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHelmet.cs b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHelmet.cs index b935407..851c46e 100644 --- a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHelmet.cs +++ b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHelmet.cs @@ -4,6 +4,7 @@ using Terraria.ModLoader; using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Items.Armor.ProtogenInitiateSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet { @@ -38,7 +39,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet player.setBonus = "Taking damage releases seeking nanites\nNanites heal you for 50% of damage dealt\n+12% melee damage and speed"; player.GetDamage(DamageClass.Melee) += 0.12f; player.GetAttackSpeed(DamageClass.Melee) += 0.12f; - // TODO: Implement nanite swarm healing system + player.GetModPlayer().EnableNaniteSwarm(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHood.cs b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHood.cs index 3319827..cf3e85d 100644 --- a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHood.cs +++ b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeHood.cs @@ -4,6 +4,7 @@ using Terraria.ModLoader; using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Items.Armor.ProtogenInitiateSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet { @@ -37,7 +38,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet player.setBonus = "Taking damage releases seeking nanites\nNanites heal you for 50% of damage dealt\n+18% magic damage and -18% mana cost"; player.GetDamage(DamageClass.Magic) += 0.18f; player.manaCost -= 0.18f; - // TODO: Implement nanite swarm healing system + player.GetModPlayer().EnableNaniteSwarm(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeMask.cs b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeMask.cs index 6391f3a..8328fb2 100644 --- a/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeMask.cs +++ b/Content/Items/Armor/ProtogenOperativeSet/ProtogenOperativeMask.cs @@ -4,6 +4,7 @@ using Terraria.ModLoader; using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Items.Armor.ProtogenInitiateSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet { @@ -37,7 +38,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet player.setBonus = "Taking damage releases seeking nanites\nNanites heal you for 50% of damage dealt\n+15% ranged damage and critical strike chance"; player.GetDamage(DamageClass.Ranged) += 0.15f; player.GetCritChance(DamageClass.Ranged) += 15; - // TODO: Implement nanite swarm healing system + player.GetModPlayer().EnableNaniteSwarm(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelCowl.cs b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelCowl.cs index e62fc90..5acee5a 100644 --- a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelCowl.cs +++ b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelCowl.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet { @@ -47,7 +48,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet player.buffImmune[BuffID.Weak] = true; player.buffImmune[BuffID.Silenced] = true; player.buffImmune[BuffID.Cursed] = true; - // TODO: Implement AI core projectile and splitting projectile system + player.GetModPlayer().EnableSentinelCore(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHelmet.cs b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHelmet.cs index 143e370..aa8129f 100644 --- a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHelmet.cs +++ b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHelmet.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet { @@ -48,7 +49,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet player.buffImmune[BuffID.Weak] = true; player.buffImmune[BuffID.Silenced] = true; player.buffImmune[BuffID.Cursed] = true; - // TODO: Implement AI core projectile and splitting projectile system + player.GetModPlayer().EnableSentinelCore(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHood.cs b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHood.cs index a18cc25..f3bd14f 100644 --- a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHood.cs +++ b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelHood.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet { @@ -47,7 +48,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet player.buffImmune[BuffID.Weak] = true; player.buffImmune[BuffID.Silenced] = true; player.buffImmune[BuffID.Cursed] = true; - // TODO: Implement AI core projectile and splitting projectile system + player.GetModPlayer().EnableSentinelCore(); } public override void AddRecipes() diff --git a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelMask.cs b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelMask.cs index 4588ba4..aedee9b 100644 --- a/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelMask.cs +++ b/Content/Items/Armor/ProtogenSentinelSet/ProtogenSentinelMask.cs @@ -5,6 +5,7 @@ using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; using ForgottenWawiTech.Content.Items.Armor.ProtogenOperativeSet; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet { @@ -47,7 +48,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.ProtogenSentinelSet player.buffImmune[BuffID.Weak] = true; player.buffImmune[BuffID.Silenced] = true; player.buffImmune[BuffID.Cursed] = true; - // TODO: Implement AI core projectile and splitting projectile system + player.GetModPlayer().EnableSentinelCore(); } public override void AddRecipes() diff --git a/Content/Items/Armor/SingularitySet/SingularityHelmet.cs b/Content/Items/Armor/SingularitySet/SingularityHelmet.cs index 8e1195f..e2c7926 100644 --- a/Content/Items/Armor/SingularitySet/SingularityHelmet.cs +++ b/Content/Items/Armor/SingularitySet/SingularityHelmet.cs @@ -4,6 +4,7 @@ using Terraria.ModLoader; using ForgottenWawiTech.Content.Items.Materials; using ForgottenWawiTech.Content.Rarities; using ForgottenWawiTech.Content.Tiles.CraftingStations; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.SingularitySet { @@ -40,9 +41,7 @@ namespace ForgottenWawiTech.Content.Items.Armor.SingularitySet player.GetCritChance(DamageClass.Generic) += 20; player.endurance += 0.15f; player.waterWalk = true; - - // TODO: Implement singularity orb minion projectile - // For now, grant increased stats as placeholder + player.GetModPlayer().EnableSingularityOrb(); } public override void AddRecipes() diff --git a/Content/Items/Armor/TranscendentSet/TranscendentSetEffects.cs b/Content/Items/Armor/TranscendentSet/TranscendentSetEffects.cs index 7dab63b..65c546a 100644 --- a/Content/Items/Armor/TranscendentSet/TranscendentSetEffects.cs +++ b/Content/Items/Armor/TranscendentSet/TranscendentSetEffects.cs @@ -2,6 +2,7 @@ using System; using Terraria; using Terraria.ID; using Terraria.ModLoader; +using ForgottenWawiTech.Content.Players; namespace ForgottenWawiTech.Content.Items.Armor.TranscendentSet { @@ -87,7 +88,9 @@ namespace ForgottenWawiTech.Content.Items.Armor.TranscendentSet player.buffImmune[buffId] = true; } - // TODO: Implement Ascendant Swarm minions, sentinel beam volley, and nanite shard projectiles. + player.GetModPlayer().EnableTranscendentSet(); + player.GetModPlayer().EnableSentinelCore(); + player.GetModPlayer().EnableSingularityOrb(); } } } diff --git a/Content/NPCs/TownNPCs/AvalianTrader.cs b/Content/NPCs/TownNPCs/AvalianTrader.cs index 27fd53f..6d10232 100644 --- a/Content/NPCs/TownNPCs/AvalianTrader.cs +++ b/Content/NPCs/TownNPCs/AvalianTrader.cs @@ -1,14 +1,69 @@ using System.Collections.Generic; +using System.Reflection; using Terraria; using Terraria.ID; using Terraria.ModLoader; using Terraria.GameContent.Personalities; using Terraria.Localization; +using ForgottenWawiTech; namespace ForgottenWawiTech.Content.NPCs.TownNPCs { public class AvalianTrader : ModNPC { + private static readonly Condition HasDefeatedAnyMechBoss = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedAnyMechBoss", () => + NPC.downedMechBoss1 || NPC.downedMechBoss2 || NPC.downedMechBoss3); + + private static readonly Condition HasDefeatedLexy = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedLexy", () => + Main.hardMode && WorldDefeatedBosses.Lexy); + + private static readonly Condition HasDefeatedNiko = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedNiko", () => + Main.hardMode && WorldDefeatedBosses.Niko); + + private static readonly Condition HasDefeatedNeptune = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedNeptune", () => + Main.hardMode && WorldDefeatedBosses.Neptune); + + private static readonly Condition HasDefeatedLyyka = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedLyyka", () => + Main.hardMode && WorldDefeatedBosses.Lyyka); + + private static readonly Condition HasDefeatedCorruptedSwarm = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedCorruptedSwarm", () => + Main.hardMode && WorldDefeatedBosses.Niko); // TODO: Replace with Corrupted Swarm flag once implemented. + + private static readonly Condition HasDefeatedExoMechs = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedExoMechs", CheckExoMechsDowned); + + private static readonly Condition HasDefeatedMoonLord = new("Mods.ForgottenWawiTech.Conditions.HasDefeatedMoonLord", () => + NPC.downedMoonlord); + + private static bool CheckExoMechsDowned() + { + if (!ModLoader.TryGetMod("CalamityMod", out Mod calamityMod)) + { + return false; + } + + var calamityWorldType = calamityMod.Code?.GetType("CalamityMod.World.CalamityWorld"); + if (calamityWorldType == null) + { + return false; + } + + const BindingFlags flags = BindingFlags.Public | BindingFlags.Static; + + var field = calamityWorldType.GetField("downedExoMechs", flags); + if (field?.GetValue(null) is bool fieldValue) + { + return fieldValue; + } + + var property = calamityWorldType.GetProperty("downedExoMechs", flags); + if (property?.GetValue(null) is bool propertyValue) + { + return propertyValue; + } + + return false; + } + public override void SetStaticDefaults() { Main.npcFrameCount[NPC.type] = 25; // The amount of frames the NPC has @@ -92,48 +147,119 @@ namespace ForgottenWawiTech.Content.NPCs.TownNPCs public override void AddShops() { + Item rainbowBrick = new(); + rainbowBrick.SetDefaults(ItemID.RainbowBrick); + rainbowBrick.value = Item.buyPrice(silver: 90); + new NPCShop(Type) - .Add(Condition.DownedMoonLord) - .Add(Condition.Hardmode) - .Add(ItemID.RainbowBrick, Condition.Hardmode) // TODO: Force Rainbow Brick to not have No Value. + .Add(HasDefeatedLexy) + .Add(HasDefeatedNiko) + .Add(HasDefeatedNeptune) + .Add(HasDefeatedLyyka) + .Add(HasDefeatedCorruptedSwarm) + .Add(rainbowBrick, Condition.Hardmode) .Add() + .Add(HasDefeatedMoonLord) + .Add(HasDefeatedAnyMechBoss) + .Add(HasDefeatedExoMechs) + .Add(HasDefeatedExoMechs) + .Add(HasDefeatedExoMechs) + .Add(HasDefeatedExoMechs) + .Add(HasDefeatedMoonLord) .Register(); } public override string GetChat() { - switch (Main.rand.Next(15)) + List lines = new() { - case 0: - return "*avalian chirping*"; - case 1: - return "Welcome! I've traveled far from Avalon to bring you the finest technology."; - case 2: - return "These feathers? They're not just for show - they're quite valuable!"; - case 3: - return "RGB technology is all the rage back home. Everything needs more colors!"; - case 4: - return "The visor shards I sell are fragments from our most advanced equipment."; - case 5: - return "My pack may be small, but I've traversed dimensions to get here."; - case 6: - return "*chirps excitedly* Oh, you're interested in our wares?"; - case 7: - return "Avalon sends its regards! And its finest materials."; - case 8: - return "Don't underestimate the power of a well-crafted alloy."; - case 9: - return "The journey here was long, but your world has such fascinating resources!"; - case 10: - return "*preens feathers* Presentation is important in any trade."; - case 11: - return "RGBass - it's not just a material, it's a lifestyle."; - case 12: - return "I wonder what the other traders back on Avalon would think of this place..."; - case 13: - return "My species may be small, but we're excellent engineers!"; - default: - return "Need something? My shop is always open!"; + "*avalian chirping*", + "Welcome! I've traveled far from Avalon to bring you the finest technology.", + "My pack may be small, but I've traversed dimensions to get here.", + "*chirps excitedly* Oh, you're interested in our wares?", + "RGBass - it's not just a material, it's a lifestyle.", + "Those rainbow bricks? Ninety silver each. I have transport costs, you know.", + "The journey here was long, but your world has such fascinating resources!", + "Need something? My shop is always open!", + "Avalian markets never sleep; neither does my inventory.", + "If you see a hover sled parked outside, please don't touch the controls.", + "Try not to lick the stock. Some of it bites back.", + "The atmosphere here is thick, but profits make it worth the acclimation.", + "I keep a thermal blanket under the clothes —these nights get cold even for an Avali.", + "Do you have any idea how much customs paperwork interstellar trade requires?", + "If you hear humming, that's just the containment field. Probably." + }; + + if (HasDefeatedLexy.IsMet()) + { + lines.Add("Defeated Lexy, have you? His visor shards always sell out fast."); + lines.Add("Lexy's visor tech makes for excellent souvenirs. Stock up while I have them."); } + else if (Main.hardMode) + { + lines.Add("Still hunting Lexy? Bring me his visor and I'll unlock some premium stock."); + } + + if (HasDefeatedNiko.IsMet()) + { + lines.Add("Astramora's Fallen Angel Feathers? Delicate, pricey, worth every coin."); + lines.Add("I've reinforced the shipping crates just for Astramora's volatile feathers."); + } + else if (Main.hardMode) + { + lines.Add("I've heard whispers of a rat angel around these parts of the galaxy..."); + } + + if (HasDefeatedNeptune.IsMet()) + { + lines.Add("Neptune leaves quite the mess. Half-eaten fish, anyone?"); + lines.Add("Neptune's half-eaten catches sell faster than I'd like—apparently otter bites add flavor."); + } + else if (Main.hardMode) + { + lines.Add("If you're braving the tides, bring Neptune down and I'll import his catches."); + } + + if (HasDefeatedLyyka.IsMet()) + { + lines.Add("Avalon sends its regards! Lyyka's gear is highly sought after."); + lines.Add("Lyyka's encore was explosive. Her gear? Even more so."); + } + else if (Main.hardMode) + { + lines.Add("Bring down Lyyka and I'll uncrate her signature alloys."); + } + + if (HasDefeatedCorruptedSwarm.IsMet()) + { + lines.Add("Unstable Matter? Just delicate enough for my clientele."); + lines.Add("Careful with the Unstable Matter; Avalon still has a watchful eye on it."); + } + else if (Main.hardMode) + { + lines.Add("Rumor says there is a swarm of corrupted tech out there. If you defeat it, I might have something worth the risk."); + } + + if (HasDefeatedAnyMechBoss.IsMet()) + { + lines.Add("Mechanical husks make great recycling. Fancy some RGB alloys?"); + } + + if (HasDefeatedMoonLord.IsMet()) + { + lines.Add("Moon Lord down? Excellent. His trinkets pair nicely with Avalian engineering."); + } + + if (HasDefeatedExoMechs.IsMet()) + { + lines.Add("The Exo Mechs are no more? Splendid! I've got singularities ready for purchase."); + lines.Add("You walked away from the Exo Mechs? Take these singularities before they destabilize."); + } + else if (ModLoader.TryGetMod("CalamityMod", out _)) + { + lines.Add("Calamity's Exo Mechs still stalk your skies. Ground them and my singularity cache opens."); + } + + return lines.Count > 0 ? lines[Main.rand.Next(lines.Count)] : "Need something? My shop is always open!"; } public override void TownNPCAttackStrength(ref int damage, ref float knockback) diff --git a/Content/Players/AvaliVanguardPlayer.cs b/Content/Players/AvaliVanguardPlayer.cs new file mode 100644 index 0000000..ac903b3 --- /dev/null +++ b/Content/Players/AvaliVanguardPlayer.cs @@ -0,0 +1,63 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Projectiles.Avali; + +namespace ForgottenWawiTech.Content.Players +{ + public class AvaliVanguardPlayer : ModPlayer + { + private const int DroneCount = 3; + private const float SpawnRadius = 72f; + public bool HuntingDronesActive { get; private set; } + + public static int GetDroneBaseDamage(Player player) + { + const int droneBaseDamage = 240; + return (int)player.GetTotalDamage(DamageClass.Summon).ApplyTo(droneBaseDamage); + } + + public override void ResetEffects() + { + HuntingDronesActive = false; + } + + public void EnableHuntingDrones() + { + HuntingDronesActive = true; + } + + public override void PostUpdate() + { + if (!HuntingDronesActive || Player.whoAmI != Main.myPlayer) + { + return; + } + + int droneType = ModContent.ProjectileType(); + int owned = Player.ownedProjectileCounts[droneType]; + if (owned >= DroneCount) + { + return; + } + + var source = Player.GetSource_Misc("AvaliVanguardSet"); + for (int i = owned; i < DroneCount; i++) + { + float rotationOffset = MathHelper.TwoPi * i / DroneCount; + Vector2 spawnPos = Player.Center + rotationOffset.ToRotationVector2() * SpawnRadius; + Projectile projectile = Projectile.NewProjectileDirect( + source, + spawnPos, + Vector2.Zero, + droneType, + 0, + 0f, + Player.whoAmI, + i, + DroneCount); + projectile.originalDamage = GetDroneBaseDamage(Player); + } + } + } +} diff --git a/Content/Players/ProtogenOperativePlayer.cs b/Content/Players/ProtogenOperativePlayer.cs new file mode 100644 index 0000000..78c776f --- /dev/null +++ b/Content/Players/ProtogenOperativePlayer.cs @@ -0,0 +1,71 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Projectiles.Protogen; + +namespace ForgottenWawiTech.Content.Players +{ + public class ProtogenOperativePlayer : ModPlayer + { + private const int NaniteProjectileCount = 3; + private const int NaniteCooldownTicks = 90; + private const int NaniteBaseDamage = 90; + private const float NaniteSpawnRadius = 30f; + + private int naniteCooldownTimer; + + public bool NaniteSwarmActive { get; private set; } + + public override void ResetEffects() + { + NaniteSwarmActive = false; + } + + public override void UpdateDead() + { + NaniteSwarmActive = false; + naniteCooldownTimer = 0; + } + + public override void PostUpdate() + { + if (naniteCooldownTimer > 0) + { + naniteCooldownTimer--; + } + } + + public void EnableNaniteSwarm() + { + NaniteSwarmActive = true; + } + + public override void OnHurt(Player.HurtInfo info) + { + if (!NaniteSwarmActive || info.Damage <= 0 || naniteCooldownTimer > 0 || Player.whoAmI != Main.myPlayer) + { + return; + } + + naniteCooldownTimer = NaniteCooldownTicks; + var source = Player.GetSource_Misc("ProtogenOperativeNanites"); + int damage = (int)Player.GetTotalDamage(DamageClass.Generic).ApplyTo(NaniteBaseDamage); + + for (int i = 0; i < NaniteProjectileCount; i++) + { + float angle = MathHelper.TwoPi * i / NaniteProjectileCount; + Vector2 spawnOffset = angle.ToRotationVector2() * NaniteSpawnRadius; + Vector2 spawnVelocity = spawnOffset.SafeNormalize(Vector2.UnitY) * 6f + Player.velocity; + + Projectile.NewProjectile( + source, + Player.Center + spawnOffset, + spawnVelocity, + ModContent.ProjectileType(), + damage, + 2f, + Player.whoAmI); + } + } + } +} diff --git a/Content/Players/ProtogenSentinelPlayer.cs b/Content/Players/ProtogenSentinelPlayer.cs new file mode 100644 index 0000000..747363c --- /dev/null +++ b/Content/Players/ProtogenSentinelPlayer.cs @@ -0,0 +1,125 @@ +using System; +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Projectiles.Protogen; +using ForgottenWawiTech.Content.GlobalProjectiles; + +namespace ForgottenWawiTech.Content.Players +{ + public class ProtogenSentinelPlayer : ModPlayer + { + private const int CoreProjectileCount = 1; + private const int CoreBaseDamage = 320; + private const float CoreSpawnOffset = -68f; + private const float FragmentSplitChance = 0.20f; + + public bool SentinelCoreActive { get; private set; } + public bool SentinelProjectileSplitActive { get; private set; } + + public override void ResetEffects() + { + SentinelCoreActive = false; + SentinelProjectileSplitActive = false; + } + + public void EnableSentinelCore() + { + SentinelCoreActive = true; + SentinelProjectileSplitActive = true; + } + + public override void PostUpdate() + { + if (!SentinelCoreActive) + { + return; + } + + if (Player.whoAmI != Main.myPlayer) + { + return; + } + + int coreType = ModContent.ProjectileType(); + if (Player.ownedProjectileCounts[coreType] >= CoreProjectileCount) + { + return; + } + + var source = Player.GetSource_Misc("ProtogenSentinelSet"); + Vector2 spawnPos = Player.Center + new Vector2(0f, CoreSpawnOffset); + Projectile.NewProjectile( + source, + spawnPos, + Vector2.Zero, + coreType, + 0, + 0f, + Player.whoAmI); + } + + public static int GetCoreDamage(Player player) + { + return (int)player.GetTotalDamage(DamageClass.Summon).ApplyTo(CoreBaseDamage); + } + + public override void OnHitNPCWithProj(Projectile proj, NPC target, NPC.HitInfo hit, int damageDone) + { + if (!SentinelProjectileSplitActive || Player.whoAmI != Main.myPlayer) + { + return; + } + + if (!proj.friendly || proj.owner != Player.whoAmI) + { + return; + } + + if (proj.GetGlobalProjectile().DisableSentinelSplit) + { + return; + } + + if (proj.type == ModContent.ProjectileType() || proj.type == ModContent.ProjectileType()) + { + return; + } + + if (damageDone <= 0) + { + return; + } + + if (Main.rand.NextFloat() > FragmentSplitChance) + { + return; + } + + SpawnFragments(proj, damageDone); + } + + private void SpawnFragments(Projectile sourceProj, int damageDone) + { + int fragmentType = ModContent.ProjectileType(); + int fragmentCount = 2; + int fragmentDamage = Math.Max(1, damageDone / 2); + var source = Player.GetSource_Misc("ProtogenSentinelSplit"); + + for (int i = 0; i < fragmentCount; i++) + { + float rotation = MathHelper.ToRadians(Main.rand.NextFloat(-18f, 18f)); + Vector2 launch = sourceProj.velocity.SafeNormalize(Vector2.UnitY).RotatedBy(rotation) * 14f; + Projectile fragment = Projectile.NewProjectileDirect( + source, + sourceProj.Center, + launch, + fragmentType, + fragmentDamage, + 2f, + Player.whoAmI); + fragment.GetGlobalProjectile().DisableSentinelSplit = true; + } + } + } +} diff --git a/Content/Players/SingularityPlayer.cs b/Content/Players/SingularityPlayer.cs new file mode 100644 index 0000000..c777aa2 --- /dev/null +++ b/Content/Players/SingularityPlayer.cs @@ -0,0 +1,56 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Projectiles.Singularity; + +namespace ForgottenWawiTech.Content.Players +{ + public class SingularityPlayer : ModPlayer + { + private const int OrbProjectileLimit = 1; + private const int OrbBaseDamage = 420; + private const float OrbSpawnOffset = -80f; + + public bool SingularityOrbActive { get; private set; } + + public override void ResetEffects() + { + SingularityOrbActive = false; + } + + public void EnableSingularityOrb() + { + SingularityOrbActive = true; + } + + public override void PostUpdate() + { + if (!SingularityOrbActive || Player.whoAmI != Main.myPlayer) + { + return; + } + + int orbType = ModContent.ProjectileType(); + if (Player.ownedProjectileCounts[orbType] >= OrbProjectileLimit) + { + return; + } + + var source = Player.GetSource_Misc("SingularitySet"); + Vector2 spawnPos = Player.Center + new Vector2(0f, OrbSpawnOffset); + Projectile.NewProjectile( + source, + spawnPos, + Vector2.Zero, + orbType, + 0, + 0f, + Player.whoAmI); + } + + public static int GetOrbDamage(Player player) + { + return (int)player.GetTotalDamage(DamageClass.Generic).ApplyTo(OrbBaseDamage); + } + } +} diff --git a/Content/Players/TranscendentPlayer.cs b/Content/Players/TranscendentPlayer.cs index 0ff24be..c6afc12 100644 --- a/Content/Players/TranscendentPlayer.cs +++ b/Content/Players/TranscendentPlayer.cs @@ -1,5 +1,7 @@ +using System; using Microsoft.Xna.Framework; using Terraria; +using Terraria.DataStructures; using Terraria.ID; using Terraria.ModLoader; using ForgottenWawiTech.Content.Projectiles.Transcendent; @@ -8,60 +10,215 @@ namespace ForgottenWawiTech.Content.Players { public class TranscendentPlayer : ModPlayer { + private const int AccessoryShardInterval = 90; + private const int SetShardInterval = 45; + private const int AccessoryShardBaseDamage = 220; + private const int SetShardBaseDamage = 320; + private const int AscendantDroneCount = 4; + private const int AscendantDroneBaseDamage = 360; + private const int NaniteShardBaseDamage = 260; + public bool SwarmModuleEquipped; + private Item swarmAccessorySourceItem; private int swarmTimer; + private int naniteShardCooldown; + + private bool transcendentSetActive; public void HandleSwarmAccessory(Item sourceItem) { SwarmModuleEquipped = true; - - if (Player.whoAmI != Main.myPlayer) - return; - - swarmTimer++; - if (swarmTimer >= 90) - { - swarmTimer = 0; - - int baseDamage = 220; - int damage = (int)Player.GetDamage(DamageClass.Summon).ApplyTo(baseDamage); - float knockBack = 3f; - Vector2 velocity = Main.rand.NextVector2Circular(6f, 6f); - if (velocity.LengthSquared() < 0.01f) - { - velocity = new Vector2(0f, -6f); - } - - Projectile.NewProjectile( - Player.GetSource_Accessory(sourceItem), - Player.Center, - velocity, - ModContent.ProjectileType(), - damage, - knockBack, - Player.whoAmI); - } + swarmAccessorySourceItem = sourceItem; } + public void EnableTranscendentSet() + { + transcendentSetActive = true; + } + + public bool HasTranscendentSet => transcendentSetActive; + public override void ResetEffects() { SwarmModuleEquipped = false; + transcendentSetActive = false; + swarmAccessorySourceItem = null; } public override void PostUpdate() { - if (SwarmModuleEquipped) + bool swarmActive = SwarmModuleEquipped || transcendentSetActive; + + if (swarmActive && Player.whoAmI == Main.myPlayer) { - if (Main.rand.NextBool(6)) + int interval = transcendentSetActive ? SetShardInterval : AccessoryShardInterval; + swarmTimer++; + if (swarmTimer >= interval) { - Dust dust = Dust.NewDustDirect(Player.position, Player.width, Player.height, DustID.RainbowTorch, Player.velocity.X * 0.2f, Player.velocity.Y * 0.2f, 150, default, 1.1f); - dust.noGravity = true; + swarmTimer = 0; + SpawnAscendantShards(transcendentSetActive); } } else if (Player.whoAmI == Main.myPlayer && swarmTimer > 0) { swarmTimer = 0; } + + if (naniteShardCooldown > 0) + { + naniteShardCooldown--; + } + + if (transcendentSetActive) + { + MaintainAscendantDrones(); + } + + if (swarmActive) + { + if (Main.rand.NextBool(transcendentSetActive ? 4 : 6)) + { + Dust dust = Dust.NewDustDirect(Player.position, Player.width, Player.height, DustID.RainbowTorch, Player.velocity.X * 0.2f, Player.velocity.Y * 0.2f, 150, default, 1.1f); + dust.noGravity = true; + } + } + } + + private void SpawnAscendantShards(bool fromSet) + { + int baseDamage = fromSet ? SetShardBaseDamage : AccessoryShardBaseDamage; + int damage = (int)Player.GetDamage(DamageClass.Summon).ApplyTo(baseDamage); + float knockBack = fromSet ? 4f : 3f; + float speed = fromSet ? 8f : 6f; + int shardCount = fromSet ? 2 : 1; + + if (!fromSet && swarmAccessorySourceItem == null) + { + swarmAccessorySourceItem = Player.HeldItem; + } + + IEntitySource source = fromSet + ? Player.GetSource_Misc("TranscendentAscendantSwarm") + : Player.GetSource_Accessory(swarmAccessorySourceItem ?? Player.HeldItem); + + for (int i = 0; i < shardCount; i++) + { + Vector2 velocity = Main.rand.NextVector2Circular(speed, speed); + if (velocity.LengthSquared() < 0.01f) + { + velocity = new Vector2(0f, -speed); + } + + Projectile shard = Projectile.NewProjectileDirect( + source, + Player.Center, + velocity, + ModContent.ProjectileType(), + damage, + knockBack, + Player.whoAmI); + shard.originalDamage = damage; + } + } + + private void MaintainAscendantDrones() + { + if (Player.whoAmI != Main.myPlayer) + { + return; + } + + int droneType = ModContent.ProjectileType(); + int owned = Player.ownedProjectileCounts[droneType]; + if (owned >= AscendantDroneCount) + { + return; + } + + var source = Player.GetSource_Misc("TranscendentAscendantDrones"); + for (int i = owned; i < AscendantDroneCount; i++) + { + Projectile.NewProjectile( + source, + Player.Center, + Vector2.Zero, + droneType, + 0, + 0f, + Player.whoAmI, + i, + AscendantDroneCount); + } + } + + public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone) + { + TrySpawnNaniteShards(target.Center, damageDone); + } + + public override void OnHitNPCWithProj(Projectile proj, NPC target, NPC.HitInfo hit, int damageDone) + { + if (proj.owner != Player.whoAmI || !proj.friendly) + { + return; + } + + if (proj.type == ModContent.ProjectileType() || + proj.type == ModContent.ProjectileType() || + proj.type == ModContent.ProjectileType()) + { + return; + } + + TrySpawnNaniteShards(target.Center, damageDone); + } + + private void TrySpawnNaniteShards(Vector2 position, int damageDone) + { + if (!transcendentSetActive || Player.whoAmI != Main.myPlayer) + { + return; + } + + if (damageDone <= 0 || naniteShardCooldown > 0) + { + return; + } + + naniteShardCooldown = 8; + int baseDamage = GetNaniteShardDamage(Player); + int computed = Math.Max(baseDamage, damageDone / 2); + float speed = 13f; + var source = Player.GetSource_Misc("TranscendentNaniteShards"); + + for (int i = 0; i < 2; i++) + { + Vector2 velocity = Main.rand.NextVector2CircularEdge(1f, 1f) * speed; + if (velocity.LengthSquared() < 0.01f) + { + velocity = new Vector2(0f, -speed); + } + + Projectile shard = Projectile.NewProjectileDirect( + source, + position, + velocity, + ModContent.ProjectileType(), + computed, + 2f, + Player.whoAmI); + shard.originalDamage = computed; + } + } + + public static int GetAscendantDroneDamage(Player player) + { + return (int)player.GetTotalDamage(DamageClass.Summon).ApplyTo(AscendantDroneBaseDamage); + } + + public static int GetNaniteShardDamage(Player player) + { + return (int)player.GetTotalDamage(DamageClass.Generic).ApplyTo(NaniteShardBaseDamage); } } } diff --git a/Content/Projectiles/Avali/AvaliHuntingDrone.cs b/Content/Projectiles/Avali/AvaliHuntingDrone.cs new file mode 100644 index 0000000..6750c11 --- /dev/null +++ b/Content/Projectiles/Avali/AvaliHuntingDrone.cs @@ -0,0 +1,126 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.ID; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Players; + +namespace ForgottenWawiTech.Content.Projectiles.Avali +{ + public class AvaliHuntingDrone : ModProjectile + { + private const float OrbitSpeed = 0.045f; + private const float OrbitRadius = 96f; + private const int ShootCooldown = 28; + private const float TargetRange = 920f; + private const float ShotSpeed = 19.5f; + + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionSacrificable[Type] = false; + ProjectileID.Sets.MinionTargettingFeature[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 26; + Projectile.height = 26; + Projectile.friendly = false; + Projectile.minion = true; + Projectile.penetrate = -1; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.DamageType = DamageClass.Summon; + Projectile.netImportant = true; + Projectile.timeLeft = 2; + Projectile.minionSlots = 0f; + Projectile.damage = 0; + } + + public override void AI() + { + Player owner = Main.player[Projectile.owner]; + AvaliVanguardPlayer modPlayer = owner.GetModPlayer(); + if (!owner.active || owner.dead || !modPlayer.HuntingDronesActive) + { + Projectile.Kill(); + return; + } + + Projectile.timeLeft = 2; + + float index = Projectile.ai[0]; + float total = Projectile.ai[1]; + if (total <= 0f) + { + total = 3f; + } + + float rotation = (Main.GameUpdateCount * OrbitSpeed) + index * MathHelper.TwoPi / total; + Vector2 offset = rotation.ToRotationVector2() * OrbitRadius; + Projectile.Center = owner.Center + offset; + Projectile.velocity = owner.velocity; + Projectile.rotation = (owner.Center - Projectile.Center).ToRotation() + MathHelper.PiOver2; + + Lighting.AddLight(Projectile.Center, 0.35f, 0.6f, 1f); + + if (Projectile.owner == Main.myPlayer) + { + Projectile.localAI[0]++; + if (Projectile.localAI[0] >= ShootCooldown) + { + Projectile.localAI[0] = 0f; + NPC target = AcquireTarget(owner, Projectile.Center, TargetRange); + if (target != null) + { + Vector2 velocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.UnitY) * ShotSpeed; + int damage = AvaliVanguardPlayer.GetDroneBaseDamage(owner); + var source = Projectile.GetSource_FromAI(); + Projectile.NewProjectile( + source, + Projectile.Center, + velocity, + ModContent.ProjectileType(), + damage, + 2f, + owner.whoAmI); + SoundEngine.PlaySound(SoundID.Item12 with { Volume = 0.6f }, Projectile.Center); + Projectile.netUpdate = true; + } + } + } + } + + private static NPC AcquireTarget(Player owner, Vector2 center, float range) + { + if (owner.HasMinionAttackTargetNPC) + { + NPC target = Main.npc[owner.MinionAttackTargetNPC]; + if (target.CanBeChasedBy()) + { + return target; + } + } + + NPC closest = null; + float squaredRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float distance = Vector2.DistanceSquared(npc.Center, center); + if (distance <= squaredRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + squaredRange = distance; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Avali/AvaliHuntingDrone.png b/Content/Projectiles/Avali/AvaliHuntingDrone.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Avali/AvaliHuntingDroneShot.cs b/Content/Projectiles/Avali/AvaliHuntingDroneShot.cs new file mode 100644 index 0000000..f5f3e72 --- /dev/null +++ b/Content/Projectiles/Avali/AvaliHuntingDroneShot.cs @@ -0,0 +1,52 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.ID; +using Terraria.ModLoader; + +namespace ForgottenWawiTech.Content.Projectiles.Avali +{ + public class AvaliHuntingDroneShot : ModProjectile + { + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionShot[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 12; + Projectile.height = 12; + Projectile.friendly = true; + Projectile.timeLeft = 120; + Projectile.penetrate = 1; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.DamageType = DamageClass.Summon; + Projectile.usesLocalNPCImmunity = true; + Projectile.localNPCHitCooldown = 10; + Projectile.extraUpdates = 1; + Projectile.alpha = 40; + } + + public override void AI() + { + Projectile.rotation = Projectile.velocity.ToRotation(); + Lighting.AddLight(Projectile.Center, 0.4f, 0.7f, 1.2f); + + if (Main.rand.NextBool(3)) + { + Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.Electric, 0f, 0f, 120, default, 1.1f); + dust.noGravity = true; + } + } + + public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone) + { + if (Main.myPlayer == Projectile.owner) + { + SoundEngine.PlaySound(SoundID.Item93 with { Volume = 0.4f }, Projectile.Center); + } + } + } +} diff --git a/Content/Projectiles/Avali/AvaliHuntingDroneShot.png b/Content/Projectiles/Avali/AvaliHuntingDroneShot.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Protogen/ProtogenOperativeNanite.cs b/Content/Projectiles/Protogen/ProtogenOperativeNanite.cs new file mode 100644 index 0000000..104a667 --- /dev/null +++ b/Content/Projectiles/Protogen/ProtogenOperativeNanite.cs @@ -0,0 +1,115 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace ForgottenWawiTech.Content.Projectiles.Protogen +{ + public class ProtogenOperativeNanite : ModProjectile + { + private const float DetectionRange = 640f; + private const float HomingStrength = 0.14f; + private const float DesiredSpeed = 14f; + + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionShot[Type] = true; + ProjectileID.Sets.MinionSacrificable[Type] = false; + } + + public override void SetDefaults() + { + Projectile.width = 14; + Projectile.height = 14; + Projectile.friendly = true; + Projectile.penetrate = 1; + Projectile.timeLeft = 360; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.DamageType = DamageClass.Generic; + Projectile.usesLocalNPCImmunity = true; + Projectile.localNPCHitCooldown = 20; + Projectile.extraUpdates = 1; + } + + public override void AI() + { + Player owner = Main.player[Projectile.owner]; + if (!owner.active || owner.dead) + { + Projectile.Kill(); + return; + } + + Projectile.rotation += 0.2f * Projectile.direction; + Lighting.AddLight(Projectile.Center, 0.25f, 0.55f, 0.75f); + + NPC target = AcquireTarget(owner, Projectile.Center, DetectionRange); + if (target != null) + { + Vector2 desiredVelocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.Zero) * DesiredSpeed; + Projectile.velocity = Vector2.Lerp(Projectile.velocity, desiredVelocity, HomingStrength); + } + else + { + Projectile.velocity *= 0.98f; + } + + if (Main.rand.NextBool(4)) + { + Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.Electric, 0f, 0f, 100, default, 1.1f); + dust.noGravity = true; + } + } + + public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone) + { + if (Main.myPlayer != Projectile.owner) + { + return; + } + + Player owner = Main.player[Projectile.owner]; + int healAmount = (int)(damageDone * 0.5f); + if (healAmount <= 0) + { + return; + } + + owner.statLife = Utils.Clamp(owner.statLife + healAmount, 0, owner.statLifeMax2); + owner.HealEffect(healAmount); + } + + private static NPC AcquireTarget(Player owner, Vector2 center, float range) + { + if (owner.HasMinionAttackTargetNPC) + { + NPC target = Main.npc[owner.MinionAttackTargetNPC]; + if (target.CanBeChasedBy()) + { + return target; + } + } + + NPC closest = null; + float squaredRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float distance = Vector2.DistanceSquared(npc.Center, center); + if (distance <= squaredRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + squaredRange = distance; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Protogen/ProtogenOperativeNanite.png b/Content/Projectiles/Protogen/ProtogenOperativeNanite.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Protogen/ProtogenSentinelCore.cs b/Content/Projectiles/Protogen/ProtogenSentinelCore.cs new file mode 100644 index 0000000..35ed5d1 --- /dev/null +++ b/Content/Projectiles/Protogen/ProtogenSentinelCore.cs @@ -0,0 +1,124 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.ID; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Players; +using ForgottenWawiTech.Content.GlobalProjectiles; + +namespace ForgottenWawiTech.Content.Projectiles.Protogen +{ + public class ProtogenSentinelCore : ModProjectile + { + private const float HoverRadius = 70f; + private const float HoverSpeed = 0.035f; + private const int FireCooldown = 32; + private const float TargetRange = 950f; + private const float BeamVelocity = 18f; + + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionSacrificable[Type] = false; + ProjectileID.Sets.MinionTargettingFeature[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 36; + Projectile.height = 36; + Projectile.friendly = false; + Projectile.minion = true; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.penetrate = -1; + Projectile.netImportant = true; + Projectile.timeLeft = 2; + Projectile.damage = 0; + Projectile.minionSlots = 0f; + } + + public override void AI() + { + Player owner = Main.player[Projectile.owner]; + ProtogenSentinelPlayer modPlayer = owner.GetModPlayer(); + if (!owner.active || owner.dead || !modPlayer.SentinelCoreActive) + { + Projectile.Kill(); + return; + } + + Projectile.timeLeft = 2; + + float rotation = ((float)Main.GameUpdateCount * HoverSpeed) % MathHelper.TwoPi; + Vector2 desiredOffset = new Vector2(0f, -HoverRadius).RotatedBy(rotation); + Vector2 desiredPosition = owner.Center + desiredOffset; + Projectile.Center = Vector2.Lerp(Projectile.Center, desiredPosition, 0.2f); + Projectile.velocity = owner.velocity; + + Lighting.AddLight(Projectile.Center, 0.2f, 0.6f, 0.9f); + + if (Projectile.owner == Main.myPlayer) + { + Projectile.localAI[0]++; + if (Projectile.localAI[0] >= FireCooldown) + { + Projectile.localAI[0] = 0f; + NPC target = AcquireTarget(owner, Projectile.Center, TargetRange); + if (target != null) + { + FireBeam(owner, target); + } + } + } + } + + private void FireBeam(Player owner, NPC target) + { + Vector2 velocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.UnitY) * BeamVelocity; + int damage = ProtogenSentinelPlayer.GetCoreDamage(owner); + var source = Projectile.GetSource_FromThis(); + Projectile beam = Projectile.NewProjectileDirect( + source, + Projectile.Center, + velocity, + ModContent.ProjectileType(), + damage, + 3f, + owner.whoAmI); + beam.GetGlobalProjectile().DisableSentinelSplit = true; + SoundEngine.PlaySound(SoundID.Item33 with { Volume = 0.7f, Pitch = 0.15f }, Projectile.Center); + } + + private static NPC AcquireTarget(Player owner, Vector2 center, float range) + { + if (owner.HasMinionAttackTargetNPC) + { + NPC target = Main.npc[owner.MinionAttackTargetNPC]; + if (target.CanBeChasedBy()) + { + return target; + } + } + + NPC closest = null; + float squaredRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float distance = Vector2.DistanceSquared(npc.Center, center); + if (distance <= squaredRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + squaredRange = distance; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Protogen/ProtogenSentinelCore.png b/Content/Projectiles/Protogen/ProtogenSentinelCore.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Protogen/ProtogenSentinelCoreBeam.cs b/Content/Projectiles/Protogen/ProtogenSentinelCoreBeam.cs new file mode 100644 index 0000000..1b489e2 --- /dev/null +++ b/Content/Projectiles/Protogen/ProtogenSentinelCoreBeam.cs @@ -0,0 +1,50 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.GlobalProjectiles; + +namespace ForgottenWawiTech.Content.Projectiles.Protogen +{ + public class ProtogenSentinelCoreBeam : ModProjectile + { + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionShot[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 18; + Projectile.height = 18; + Projectile.friendly = true; + Projectile.timeLeft = 120; + Projectile.penetrate = 2; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.DamageType = DamageClass.Summon; + Projectile.extraUpdates = 1; + Projectile.usesLocalNPCImmunity = true; + Projectile.localNPCHitCooldown = 10; + } + + public override void AI() + { + Projectile.rotation = Projectile.velocity.ToRotation(); + Lighting.AddLight(Projectile.Center, 0.35f, 0.9f, 1.2f); + + if (Main.rand.NextBool(3)) + { + Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.BlueCrystalShard, 0f, 0f, 150, default, 1.1f); + dust.noGravity = true; + dust.velocity = Projectile.velocity * 0.2f; + } + } + + public override void OnSpawn(IEntitySource source) + { + Projectile.GetGlobalProjectile().DisableSentinelSplit = true; + } + } +} diff --git a/Content/Projectiles/Protogen/ProtogenSentinelCoreBeam.png b/Content/Projectiles/Protogen/ProtogenSentinelCoreBeam.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Protogen/ProtogenSentinelFragment.cs b/Content/Projectiles/Protogen/ProtogenSentinelFragment.cs new file mode 100644 index 0000000..94a0942 --- /dev/null +++ b/Content/Projectiles/Protogen/ProtogenSentinelFragment.cs @@ -0,0 +1,87 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.DataStructures; +using Terraria.ID; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.GlobalProjectiles; + +namespace ForgottenWawiTech.Content.Projectiles.Protogen +{ + public class ProtogenSentinelFragment : ModProjectile + { + private const float HomingRange = 560f; + private const float HomingStrength = 0.18f; + private const float DesiredSpeed = 15f; + + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionShot[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 14; + Projectile.height = 14; + Projectile.friendly = true; + Projectile.penetrate = 1; + Projectile.timeLeft = 240; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.DamageType = DamageClass.Generic; + Projectile.extraUpdates = 1; + Projectile.usesLocalNPCImmunity = true; + Projectile.localNPCHitCooldown = 15; + } + + public override void AI() + { + Projectile.rotation = Projectile.velocity.ToRotation(); + Lighting.AddLight(Projectile.Center, 0.3f, 0.8f, 1.15f); + + NPC target = AcquireTarget(Projectile.Center, HomingRange); + if (target != null) + { + Vector2 desiredVelocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.Zero) * DesiredSpeed; + Projectile.velocity = Vector2.Lerp(Projectile.velocity, desiredVelocity, HomingStrength); + } + else + { + Projectile.velocity *= 0.99f; + } + + if (Main.rand.NextBool(4)) + { + Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.Electric, 0f, 0f, 120, default, 1.05f); + dust.noGravity = true; + } + } + + public override void OnSpawn(IEntitySource source) + { + Projectile.GetGlobalProjectile().DisableSentinelSplit = true; + } + + private static NPC AcquireTarget(Vector2 center, float range) + { + NPC closest = null; + float squaredRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float distance = Vector2.DistanceSquared(npc.Center, center); + if (distance <= squaredRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + squaredRange = distance; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Protogen/ProtogenSentinelFragment.png b/Content/Projectiles/Protogen/ProtogenSentinelFragment.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Singularity/SingularityOrb.cs b/Content/Projectiles/Singularity/SingularityOrb.cs new file mode 100644 index 0000000..96d4f2f --- /dev/null +++ b/Content/Projectiles/Singularity/SingularityOrb.cs @@ -0,0 +1,123 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.ID; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Players; + +namespace ForgottenWawiTech.Content.Projectiles.Singularity +{ + public class SingularityOrb : ModProjectile + { + private const float OrbitRadius = 90f; + private const float OrbitSpeed = 0.04f; + private const int FireCooldown = 22; + private const float TargetRange = 1000f; + private const float BeamSpeed = 21f; + + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionSacrificable[Type] = false; + ProjectileID.Sets.MinionTargettingFeature[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 32; + Projectile.height = 32; + Projectile.friendly = false; + Projectile.minion = true; + Projectile.DamageType = DamageClass.Generic; + Projectile.penetrate = -1; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.netImportant = true; + Projectile.timeLeft = 2; + Projectile.minionSlots = 0f; + } + + public override void AI() + { + Player owner = Main.player[Projectile.owner]; + SingularityPlayer modPlayer = owner.GetModPlayer(); + if (!owner.active || owner.dead || !modPlayer.SingularityOrbActive) + { + Projectile.Kill(); + return; + } + + Projectile.timeLeft = 2; + + float rotation = ((float)Main.GameUpdateCount * OrbitSpeed) + Projectile.identity * 0.2f; + Vector2 offset = new Vector2(0f, -OrbitRadius).RotatedBy(rotation); + Vector2 desiredPos = owner.Center + offset; + Projectile.Center = Vector2.Lerp(Projectile.Center, desiredPos, 0.28f); + Projectile.velocity = owner.velocity; + Projectile.rotation += 0.03f * Projectile.direction; + + Lighting.AddLight(Projectile.Center, 0.55f, 0.2f, 0.65f); + + if (Projectile.owner == Main.myPlayer) + { + Projectile.localAI[0]++; + if (Projectile.localAI[0] >= FireCooldown) + { + Projectile.localAI[0] = 0f; + NPC target = AcquireTarget(owner, Projectile.Center, TargetRange); + if (target != null) + { + FireBeam(owner, target); + } + } + } + } + + private void FireBeam(Player owner, NPC target) + { + Vector2 velocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.UnitY) * BeamSpeed; + int damage = SingularityPlayer.GetOrbDamage(owner); + var source = Projectile.GetSource_FromThis(); + Projectile.NewProjectile( + source, + Projectile.Center, + velocity, + ModContent.ProjectileType(), + damage, + 2f, + owner.whoAmI); + SoundEngine.PlaySound(SoundID.Item91 with { Volume = 0.7f, Pitch = 0.35f }, Projectile.Center); + } + + private static NPC AcquireTarget(Player owner, Vector2 center, float range) + { + if (owner.HasMinionAttackTargetNPC) + { + NPC forced = Main.npc[owner.MinionAttackTargetNPC]; + if (forced.CanBeChasedBy()) + { + return forced; + } + } + + NPC closest = null; + float sqrRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float dist = Vector2.DistanceSquared(npc.Center, center); + if (dist <= sqrRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + sqrRange = dist; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Singularity/SingularityOrb.png b/Content/Projectiles/Singularity/SingularityOrb.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Singularity/SingularityOrbBeam.cs b/Content/Projectiles/Singularity/SingularityOrbBeam.cs new file mode 100644 index 0000000..5142eb3 --- /dev/null +++ b/Content/Projectiles/Singularity/SingularityOrbBeam.cs @@ -0,0 +1,42 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace ForgottenWawiTech.Content.Projectiles.Singularity +{ + public class SingularityOrbBeam : ModProjectile + { + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionShot[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 16; + Projectile.height = 16; + Projectile.friendly = true; + Projectile.timeLeft = 120; + Projectile.penetrate = 3; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.DamageType = DamageClass.Generic; + Projectile.extraUpdates = 1; + Projectile.usesLocalNPCImmunity = true; + Projectile.localNPCHitCooldown = 8; + } + + public override void AI() + { + Projectile.rotation = Projectile.velocity.ToRotation(); + Lighting.AddLight(Projectile.Center, 0.7f, 0.25f, 0.85f); + + if (Main.rand.NextBool(3)) + { + Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.PinkTorch, Projectile.velocity.X * 0.2f, Projectile.velocity.Y * 0.2f, 150, default, 1.2f); + dust.noGravity = true; + } + } + } +} diff --git a/Content/Projectiles/Singularity/SingularityOrbBeam.png b/Content/Projectiles/Singularity/SingularityOrbBeam.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Transcendent/TranscendentAscendantBolt.cs b/Content/Projectiles/Transcendent/TranscendentAscendantBolt.cs new file mode 100644 index 0000000..5a48a0a --- /dev/null +++ b/Content/Projectiles/Transcendent/TranscendentAscendantBolt.cs @@ -0,0 +1,76 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace ForgottenWawiTech.Content.Projectiles.Transcendent +{ + public class TranscendentAscendantBolt : ModProjectile + { + private const float HomingRange = 720f; + private const float HomingStrength = 0.2f; + private const float DesiredSpeed = 20f; + +/* public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionShot[Type] = true; + } */ + + public override void SetDefaults() + { + Projectile.width = 18; + Projectile.height = 18; + Projectile.friendly = true; + Projectile.timeLeft = 180; + Projectile.penetrate = 2; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.DamageType = DamageClass.Summon; + Projectile.extraUpdates = 1; + Projectile.usesLocalNPCImmunity = true; + Projectile.localNPCHitCooldown = 10; + } + + public override void AI() + { + Projectile.rotation = Projectile.velocity.ToRotation(); + Lighting.AddLight(Projectile.Center, 0.6f, 0.55f, 1.2f); + + NPC target = AcquireTarget(Projectile.Center, HomingRange); + if (target != null) + { + Vector2 desiredVelocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.Zero) * DesiredSpeed; + Projectile.velocity = Vector2.Lerp(Projectile.velocity, desiredVelocity, HomingStrength); + } + + if (Main.rand.NextBool(4)) + { + Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.Enchanted_Pink, Projectile.velocity.X * 0.2f, Projectile.velocity.Y * 0.2f, 150, default, 1.15f); + dust.noGravity = true; + } + } + + private static NPC AcquireTarget(Vector2 center, float range) + { + NPC closest = null; + float sqrRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float dist = Vector2.DistanceSquared(npc.Center, center); + if (dist <= sqrRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + sqrRange = dist; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Transcendent/TranscendentAscendantBolt.png b/Content/Projectiles/Transcendent/TranscendentAscendantBolt.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Transcendent/TranscendentAscendantDrone.cs b/Content/Projectiles/Transcendent/TranscendentAscendantDrone.cs new file mode 100644 index 0000000..9d7971a --- /dev/null +++ b/Content/Projectiles/Transcendent/TranscendentAscendantDrone.cs @@ -0,0 +1,130 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.Audio; +using Terraria.ID; +using Terraria.ModLoader; +using ForgottenWawiTech.Content.Players; + +namespace ForgottenWawiTech.Content.Projectiles.Transcendent +{ + public class TranscendentAscendantDrone : ModProjectile + { + private const float OrbitRadius = 136f; + private const float OrbitSpeed = 0.055f; + private const int FireCooldown = 18; + private const float TargetRange = 1100f; + private const float BoltSpeed = 19f; + + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionSacrificable[Type] = false; + ProjectileID.Sets.MinionTargettingFeature[Type] = true; + //ProjectileID.Sets.Minion[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 28; + Projectile.height = 28; + Projectile.friendly = false; + Projectile.minion = true; + Projectile.penetrate = -1; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.netImportant = true; + Projectile.timeLeft = 2; + Projectile.minionSlots = 0f; + } + + public override void AI() + { + Player owner = Main.player[Projectile.owner]; + TranscendentPlayer modPlayer = owner.GetModPlayer(); + if (!owner.active || owner.dead || !modPlayer.HasTranscendentSet) + { + Projectile.Kill(); + return; + } + + Projectile.timeLeft = 2; + + float index = Projectile.ai[0]; + float total = Projectile.ai[1]; + if (total <= 0f) + { + total = 4f; + } + + float rotation = ((float)Main.GameUpdateCount * OrbitSpeed) + index * MathHelper.TwoPi / total; + Vector2 offset = rotation.ToRotationVector2() * OrbitRadius; + Projectile.Center = owner.Center + offset; + Projectile.velocity = owner.velocity; + Projectile.rotation = (owner.Center - Projectile.Center).ToRotation() + MathHelper.PiOver2; + + Lighting.AddLight(Projectile.Center, 0.45f, 0.6f, 1.05f); + + if (Projectile.owner == Main.myPlayer) + { + Projectile.localAI[0]++; + if (Projectile.localAI[0] >= FireCooldown) + { + Projectile.localAI[0] = 0f; + NPC target = AcquireTarget(owner, Projectile.Center, TargetRange); + if (target != null) + { + FireBolt(owner, target); + } + } + } + } + + private void FireBolt(Player owner, NPC target) + { + Vector2 velocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.UnitY) * BoltSpeed; + int damage = TranscendentPlayer.GetAscendantDroneDamage(owner); + var source = Projectile.GetSource_FromThis(); + Projectile.NewProjectile( + source, + Projectile.Center, + velocity, + ModContent.ProjectileType(), + damage, + 4f, + owner.whoAmI); + SoundEngine.PlaySound(SoundID.Item33 with { Volume = 0.6f, Pitch = 0.2f }, Projectile.Center); + Projectile.netUpdate = true; + } + + private static NPC AcquireTarget(Player owner, Vector2 center, float range) + { + if (owner.HasMinionAttackTargetNPC) + { + NPC forced = Main.npc[owner.MinionAttackTargetNPC]; + if (forced.CanBeChasedBy()) + { + return forced; + } + } + + NPC closest = null; + float sqrRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float dist = Vector2.DistanceSquared(npc.Center, center); + if (dist <= sqrRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + sqrRange = dist; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Transcendent/TranscendentAscendantDrone.png b/Content/Projectiles/Transcendent/TranscendentAscendantDrone.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Content/Projectiles/Transcendent/TranscendentNaniteShard.cs b/Content/Projectiles/Transcendent/TranscendentNaniteShard.cs new file mode 100644 index 0000000..851b938 --- /dev/null +++ b/Content/Projectiles/Transcendent/TranscendentNaniteShard.cs @@ -0,0 +1,81 @@ +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace ForgottenWawiTech.Content.Projectiles.Transcendent +{ + public class TranscendentNaniteShard : ModProjectile + { + private const float HomingRange = 680f; + private const float HomingStrength = 0.22f; + private const float DesiredSpeed = 18f; + + public override void SetStaticDefaults() + { + ProjectileID.Sets.MinionShot[Type] = true; + } + + public override void SetDefaults() + { + Projectile.width = 14; + Projectile.height = 14; + Projectile.friendly = true; + Projectile.penetrate = 1; + Projectile.timeLeft = 220; + Projectile.tileCollide = false; + Projectile.ignoreWater = true; + Projectile.extraUpdates = 1; + Projectile.DamageType = DamageClass.Generic; + Projectile.usesLocalNPCImmunity = true; + Projectile.localNPCHitCooldown = 12; + Projectile.alpha = 60; + } + + public override void AI() + { + Projectile.rotation += 0.35f * Projectile.direction; + Lighting.AddLight(Projectile.Center, 0.45f, 0.8f, 1.15f); + + NPC target = AcquireTarget(Projectile.Center, HomingRange); + if (target != null) + { + Vector2 desiredVelocity = (target.Center - Projectile.Center).SafeNormalize(Vector2.Zero) * DesiredSpeed; + Projectile.velocity = Vector2.Lerp(Projectile.velocity, desiredVelocity, HomingStrength); + } + else + { + Projectile.velocity *= 0.98f; + } + + if (Main.rand.NextBool(5)) + { + Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.Electric, 0f, 0f, 120, default, 1.05f); + dust.noGravity = true; + } + } + + private static NPC AcquireTarget(Vector2 center, float range) + { + NPC closest = null; + float sqrRange = range * range; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!npc.active || npc.friendly || npc.dontTakeDamage || npc.life <= 0) + { + continue; + } + + float dist = Vector2.DistanceSquared(npc.Center, center); + if (dist <= sqrRange && Collision.CanHitLine(center, 0, 0, npc.Center, 0, 0)) + { + sqrRange = dist; + closest = npc; + } + } + + return closest; + } + } +} diff --git a/Content/Projectiles/Transcendent/TranscendentNaniteShard.png b/Content/Projectiles/Transcendent/TranscendentNaniteShard.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0cf81e52f4133dbe85453dd9a37993af17b5be GIT binary patch literal 504 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r53?z4+XPOVBbOU@sT!FNbf{DF-NtsjE_3B;! z|NjSy0D+K@p01v+t9x8zR#D@WS?dlQ`1+YJESG!@x4dC7*`C93=UXkhhvvt!hjSLlJ~RwmnP~Bh_e#*cGOJz5H)U2RZk=*d{>S{o z|7E^K_FWXNcxrjXh&wV*Hfp0Ti%!ljhM&t`NT+&77>M@wN(xASbQfH=B6&8`PK|k& o(pT;d?|57xzrgJ7^Ax@sCjQeEzP*#>W`d&2)78&qol`;+07t#pIRF3v literal 0 HcmV?d00001 diff --git a/Localization/en-US_Mods.ForgottenWawiTech.hjson b/Localization/en-US_Mods.ForgottenWawiTech.hjson index 9cad27d..cc0d1dc 100644 --- a/Localization/en-US_Mods.ForgottenWawiTech.hjson +++ b/Localization/en-US_Mods.ForgottenWawiTech.hjson @@ -896,6 +896,17 @@ Items: { } } +Conditions: { + HasDefeatedAnyMechBoss: Defeated a mechanical boss + HasDefeatedExoMechs: Defeated the Exo Mechs + HasDefeatedLexy: Defeated Lexy + HasDefeatedNiko: Defeated Niko + HasDefeatedNeptune: Defeated Neptune + HasDefeatedLyyka: Defeated Lyyka + HasDefeatedCorruptedSwarm: Defeated the Corrupted Swarm + HasDefeatedMoonLord: Defeated the Moon Lord +} + NPCs: { Lexy.DisplayName: Lexy @@ -992,6 +1003,17 @@ Projectiles: { TranscendentNova.DisplayName: Transcendent Nova TranscendentRailBolt.DisplayName: Transcendent Rail Bolt AvaliThermalDrone.DisplayName: Avali Thermal Drone + AvaliHuntingDrone.DisplayName: Avali Hunting Drone + AvaliHuntingDroneShot.DisplayName: Avali Hunting Drone Pulse + ProtogenOperativeNanite.DisplayName: Operative Nanite + ProtogenSentinelCore.DisplayName: Sentinel AI Core + ProtogenSentinelCoreBeam.DisplayName: Sentinel Core Beam + ProtogenSentinelFragment.DisplayName: Sentinel Split Fragment + SingularityOrb.DisplayName: Singularity Orb + SingularityOrbBeam.DisplayName: Singularity Orb Beam + TranscendentAscendantDrone.DisplayName: Ascendant Swarm Drone + TranscendentAscendantBolt.DisplayName: Ascendant Swarm Bolt + TranscendentNaniteShard.DisplayName: Nanite Shard } Prefixes: {