From afb8235c08b1f511a1edfb72e7ba40bbafdbd3cb Mon Sep 17 00:00:00 2001 From: LeonardoVAC Date: Fri, 16 Oct 2026 00:39:01 -0300 Subject: [PATCH] feat: Add magic bullets --- src/TerraSharp/Core/Hotkeys.cs | 1 + src/TerraSharp/Core/Settings.Json.cs | 1 + src/TerraSharp/Core/Settings.cs | 2 +- .../Features/Combat/MagicBullets.cs | 69 +++++++++++++++++++ src/TerraSharp/Patches/HookRegistry.cs | 1 + src/TerraSharp/Patches/ProjectilePatches.cs | 22 ++++++ src/TerraSharp/Simulation/Ballistics.cs | 2 +- src/TerraSharp/UI/MainWindow.Pages.cs | 1 + src/TerraSharp/UI/MainWindow.Tooltips.cs | 1 + src/TerraSharp/UI/MainWindow.cs | 2 +- 10 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 src/TerraSharp/Features/Combat/MagicBullets.cs create mode 100644 src/TerraSharp/Patches/ProjectilePatches.cs diff --git a/src/TerraSharp/Core/Hotkeys.cs b/src/TerraSharp/Core/Hotkeys.cs index 27c9b42..523908b 100644 --- a/src/TerraSharp/Core/Hotkeys.cs +++ b/src/TerraSharp/Core/Hotkeys.cs @@ -56,6 +56,7 @@ public static class Hotkeys Toggle("Combat", "FreezeEnemies", "Freeze Enemies", () => Settings.FreezeEnemies, v => Settings.FreezeEnemies = v), Toggle("Combat", "WeakenEnemies", "Weaken Enemies", () => Settings.WeakenEnemies, v => Settings.WeakenEnemies = v), Toggle("Combat", "RapidAttack", "Rapid Attack", () => Settings.RapidAttack, v => Settings.RapidAttack = v), + Toggle("Combat", "MagicBullets", "Magic Bullets", () => Settings.MagicBullets, v => Settings.MagicBullets = v), new("Combat", "GatherAll", "Gather All Enemies", CombatFeatures.GatherAll), new("Combat", "KillAll", "Kill All Enemies", CombatFeatures.KillAll), diff --git a/src/TerraSharp/Core/Settings.Json.cs b/src/TerraSharp/Core/Settings.Json.cs index 6bc6683..3bb3bf7 100644 --- a/src/TerraSharp/Core/Settings.Json.cs +++ b/src/TerraSharp/Core/Settings.Json.cs @@ -58,6 +58,7 @@ public static partial class Settings [nameof(ShotPreview)] = "Combat.Aim.ShotPreview", [nameof(AimFov)] = "Combat.Aim.CursorRadius", [nameof(AimRange)] = "Combat.Aim.Range", + [nameof(MagicBullets)] = "Combat.MagicBullets", [nameof(InstantBreak)] = "Items.InstantBreak", [nameof(InfItems)] = "Items.InfiniteItems", diff --git a/src/TerraSharp/Core/Settings.cs b/src/TerraSharp/Core/Settings.cs index d3ba91f..4f39cc6 100644 --- a/src/TerraSharp/Core/Settings.cs +++ b/src/TerraSharp/Core/Settings.cs @@ -35,7 +35,7 @@ public static partial class Settings public static bool KillAura, HitMagnet, FreezeEnemies, WeakenEnemies; public static float KillAuraRange = 20f, HitMagnetRange = 30f; public static int KillAuraInterval = 10; - public static bool Aimbot, AimPrediction = true, AimDrawTarget = true, AutoAttack, AimExact = true, ShotPreview; + public static bool Aimbot, AimPrediction = true, AimDrawTarget = true, AutoAttack, AimExact = true, ShotPreview, MagicBullets; public static float AimFov = 250f, AimRange = 60f; public static bool InstantBreak, InfItems, ItemVacuum, AutoSell, BankButtons = true; diff --git a/src/TerraSharp/Features/Combat/MagicBullets.cs b/src/TerraSharp/Features/Combat/MagicBullets.cs new file mode 100644 index 0000000..5c191ee --- /dev/null +++ b/src/TerraSharp/Features/Combat/MagicBullets.cs @@ -0,0 +1,69 @@ +using System; +using Microsoft.Xna.Framework; +using Terraria; +using Terraria.ID; +using TerraSharp.Core; +using TerraSharp.Simulation; + +namespace TerraSharp.Features.Combat; + +public static class MagicBullets +{ + const float MinSpeed = 8f; + const int GrappleStyle = 7, YoyoStyle = 99, FlailStyle = 15; + + // Projectile type per slot while steered, 0 otherwise. + static readonly int[] Steered = new int[Main.maxProjectiles]; + + public static bool IsSteered(int slot) => Steered[slot] != 0 && Steered[slot] == Main.projectile[slot].type; + + static bool Eligible(Projectile projectile) => projectile is { friendly: true, hostile: false, minion: false, sentry: false, bobber: false, npcProj: false, damage: > 0 } && projectile.aiStyle is not (GrappleStyle or YoyoStyle or FlailStyle) && !Main.projPet[projectile.type] && !ProjectileID.Sets.IsAWhip[projectile.type] && !Sandbox.Held(projectile); + + // Projectile.AI postfix for the local player's projectiles. + public static void PostAI(Projectile projectile) + { + int slot = projectile.whoAmI; + NPC? target = Eligible(projectile) ? TargetFor(projectile) : null; + if (target is null) + { + if (IsSteered(slot) && ContentSamples.ProjectilesByType.TryGetValue(projectile.type, out Projectile? sample)) + projectile.tileCollide = sample.tileCollide; + Steered[slot] = 0; + return; + } + + Vector2 offset = target.Center - projectile.Center; + if (offset.LengthSquared() < 1f) + return; + + projectile.velocity = Vector2.Normalize(offset) * Math.Max(projectile.velocity.Length(), MinSpeed); + projectile.tileCollide = false; + Steered[slot] = projectile.type; + } + + static NPC? TargetFor(Projectile projectile) + { + if (Aimbot.Target is { } locked && Hostility.IsDamageable(locked)) + return locked; + + Player player = Main.player[projectile.owner]; + float range = Settings.AimRange * 16f, rangeSq = range * range; + NPC? best = null; + float bestDistance = float.MaxValue; + for (int i = 0; i < Main.maxNPCs; i++) + { + NPC npc = Main.npc[i]; + if (!Hostility.IsDamageable(npc) || Vector2.DistanceSquared(npc.Center, player.Center) > rangeSq) + continue; + + float distance = Vector2.DistanceSquared(npc.Center, projectile.Center); + if (distance < bestDistance) + { + bestDistance = distance; + best = npc; + } + } + + return best; + } +} diff --git a/src/TerraSharp/Patches/HookRegistry.cs b/src/TerraSharp/Patches/HookRegistry.cs index 6be9a55..5d381f5 100644 --- a/src/TerraSharp/Patches/HookRegistry.cs +++ b/src/TerraSharp/Patches/HookRegistry.cs @@ -21,6 +21,7 @@ internal sealed class HookRegistry : IDisposable MainPatches.Install(hooks); PlayerPatches.Install(hooks); NpcPatches.Install(hooks); + ProjectilePatches.Install(hooks); DamageNumberPatches.Install(hooks); VisionPatches.Install(hooks); SpawnRatePatch.Install(hooks); diff --git a/src/TerraSharp/Patches/ProjectilePatches.cs b/src/TerraSharp/Patches/ProjectilePatches.cs new file mode 100644 index 0000000..9196d64 --- /dev/null +++ b/src/TerraSharp/Patches/ProjectilePatches.cs @@ -0,0 +1,22 @@ +using System; +using Terraria; +using TerraSharp.Core; +using TerraSharp.Features.Combat; +using TerraSharp.Simulation; + +namespace TerraSharp.Patches; + +// After the AI chose its velocity, before Projectile.Update moves it. +internal static class ProjectilePatches +{ + delegate void Original(Projectile self); + + internal static void Install(HookRegistry hooks) => hooks.Add(typeof(Projectile), nameof(Projectile.AI), (Action)AI); + + static void AI(Original original, Projectile self) + { + original(self); + if (Plugin.Active && Settings.MagicBullets && !Sandbox.Active && self.owner == Main.myPlayer) + Guard.Run("Projectile.AI postfix", MagicBullets.PostAI, self); + } +} diff --git a/src/TerraSharp/Simulation/Ballistics.cs b/src/TerraSharp/Simulation/Ballistics.cs index cdbf5bd..a19dea4 100644 --- a/src/TerraSharp/Simulation/Ballistics.cs +++ b/src/TerraSharp/Simulation/Ballistics.cs @@ -133,7 +133,7 @@ public static class Ballistics track.Age++; // Tile hits, bounces and homing turns show up as sharp changes. - if (!track.Spoiled && track.Velocities.Count > 0 && (velocity - track.LastVelocity).Length() > Math.Max(4f, track.LastVelocity.Length() * 0.5f)) + if (!track.Spoiled && (MagicBullets.IsSteered(i) || (track.Velocities.Count > 0 && (velocity - track.LastVelocity).Length() > Math.Max(4f, track.LastVelocity.Length() * 0.5f)))) track.Spoiled = true; if (!track.Spoiled && track.Velocities.Count < MaxSamples) track.Velocities.Add(velocity); diff --git a/src/TerraSharp/UI/MainWindow.Pages.cs b/src/TerraSharp/UI/MainWindow.Pages.cs index e62de77..73bad2c 100644 --- a/src/TerraSharp/UI/MainWindow.Pages.cs +++ b/src/TerraSharp/UI/MainWindow.Pages.cs @@ -101,6 +101,7 @@ public sealed partial class MainWindow Slider("Max range", ref Settings.AimRange, 5f, 150f, 60f, "{0:0} tiles"); Toggle("Show target", ref Settings.AimDrawTarget); }); + Toggle("Magic Bullets", ref Settings.MagicBullets); Toggle("Shot Preview", ref Settings.ShotPreview); Group("Enemies"); diff --git a/src/TerraSharp/UI/MainWindow.Tooltips.cs b/src/TerraSharp/UI/MainWindow.Tooltips.cs index 8a8fb12..0acc12d 100644 --- a/src/TerraSharp/UI/MainWindow.Tooltips.cs +++ b/src/TerraSharp/UI/MainWindow.Tooltips.cs @@ -28,6 +28,7 @@ public sealed partial class MainWindow ["Prediction"] = "Leads moving enemies and accounts for projectile drop.", ["Exact check"] = "Checks and corrects aim before firing. Auto Attack skips known misses. Orange brackets mean an unconfirmed shot.", ["Shot Preview"] = "Shows your next shot's path, including spread and homing.", + ["Magic Bullets"] = "Your projectiles home in on the aimbot target or the nearest enemy, through blocks.", ["Auto Attack"] = "Swings at enemies in reach and shoots at enemies your weapon can hit.", ["Show target"] = "Shows the cursor radius, target brackets and aim point.", ["Kill Aura"] = "Kills enemies in range and drops their loot.", diff --git a/src/TerraSharp/UI/MainWindow.cs b/src/TerraSharp/UI/MainWindow.cs index 0b97c17..fbc9dc4 100644 --- a/src/TerraSharp/UI/MainWindow.cs +++ b/src/TerraSharp/UI/MainWindow.cs @@ -32,7 +32,7 @@ public sealed partial class MainWindow : Window [ new Page("Player", PlayerPage, () => Count(Settings.GodMode, Settings.InfHealth, Settings.InfMana, Settings.InfBreath, Settings.InfFlight, Settings.InstantRespawn, Settings.AutoHeal, Settings.AutoMana, Settings.GhostMode, Settings.DefenseBoost)), new Page("Movement", MovementPage, () => Count(Settings.Fly, Settings.SuperSpeed, Settings.HighJump, Settings.GravityFlip, Settings.NoFallDamage, Settings.LavaImmune, Settings.WaterWalk, Settings.NoKnockback, Settings.ExtendedReach)), - new Page("Combat", CombatPage, () => Count(Settings.RapidAttack, Settings.AutoSwing, Settings.DamageBoost, Settings.NoManaCost, Settings.NoSpread, Settings.Aimbot, Settings.AutoAttack, Settings.KillAura, Settings.HitMagnet, Settings.FreezeEnemies, Settings.WeakenEnemies)), + new Page("Combat", CombatPage, () => Count(Settings.RapidAttack, Settings.AutoSwing, Settings.DamageBoost, Settings.NoManaCost, Settings.NoSpread, Settings.Aimbot, Settings.AutoAttack, Settings.KillAura, Settings.HitMagnet, Settings.FreezeEnemies, Settings.WeakenEnemies, Settings.MagicBullets)), new Page("Items", ItemsPage, () => Count(Settings.InstantBreak, Settings.InfItems, Settings.ItemVacuum, Settings.AutoSell, Settings.AutoFish, Settings.Sonar)), new Page("Visuals", VisualsPage, () => Count(Settings.Esp, Settings.Spelunker, Settings.Hunter, Settings.Shine, Settings.NightOwl, Settings.DangerSense, Settings.BiomeSight, Settings.FullBright, Settings.ExtendedZoom)), new Page("World", WorldPage, () => Count(Settings.FreezeTime, Settings.SpawnRateBoost, Settings.MapTeleport)),