diff --git a/src/TerraSharp/Core/Hotkeys.cs b/src/TerraSharp/Core/Hotkeys.cs index 523908b..c3a2e60 100644 --- a/src/TerraSharp/Core/Hotkeys.cs +++ b/src/TerraSharp/Core/Hotkeys.cs @@ -57,6 +57,7 @@ public static class Hotkeys 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), + Toggle("Combat", "AutoDodge", "Auto Dodge", () => Settings.AutoDodge, v => Settings.AutoDodge = 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 3bb3bf7..497d612 100644 --- a/src/TerraSharp/Core/Settings.Json.cs +++ b/src/TerraSharp/Core/Settings.Json.cs @@ -59,6 +59,7 @@ public static partial class Settings [nameof(AimFov)] = "Combat.Aim.CursorRadius", [nameof(AimRange)] = "Combat.Aim.Range", [nameof(MagicBullets)] = "Combat.MagicBullets", + [nameof(AutoDodge)] = "Combat.AutoDodge", [nameof(InstantBreak)] = "Items.InstantBreak", [nameof(InfItems)] = "Items.InfiniteItems", diff --git a/src/TerraSharp/Core/Settings.cs b/src/TerraSharp/Core/Settings.cs index 4f39cc6..49c58a3 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, MagicBullets; + public static bool Aimbot, AimPrediction = true, AimDrawTarget = true, AutoAttack, AimExact = true, ShotPreview, MagicBullets, AutoDodge; public static float AimFov = 250f, AimRange = 60f; public static bool InstantBreak, InfItems, ItemVacuum, AutoSell, BankButtons = true; diff --git a/src/TerraSharp/Features/Combat/AutoDodge.cs b/src/TerraSharp/Features/Combat/AutoDodge.cs new file mode 100644 index 0000000..163dcb7 --- /dev/null +++ b/src/TerraSharp/Features/Combat/AutoDodge.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Terraria; +using TerraSharp.Core; +using TerraSharp.Simulation; + +namespace TerraSharp.Features.Combat; + +// Steers the local player out of predicted projectile and contact hits. +public static class AutoDodge +{ + const int Horizon = 24; + const int MaxThreats = 48; + const float ScanRange = 60f * 16f; + const float Margin = 6f; + const int WallCheckTicks = 6; + + sealed class Threat + { + public readonly Vector2[] Path = new Vector2[Horizon + 1]; + public int Count, Width, Height; + } + + readonly record struct Move(int Horizontal, int Vertical); + + static readonly Move[] Moves = [new(0, 0), new(-1, 0), new(1, 0), new(0, -1), new(-1, -1), new(1, -1), new(0, 1), new(-1, 1), new(1, 1)]; + static readonly Threat[] Pool = new Threat[MaxThreats]; + static readonly List Threats = []; + + static AutoDodge() + { + for (int i = 0; i < Pool.Length; i++) + Pool[i] = new Threat(); + } + + // Player.HorizontalMovement prefix: controls and velocity set here drive this tick's movement. + public static void Steer(Player player) + { + if (!Settings.AutoDodge || player.dead) + return; + + CollectThreats(player); + if (Threats.Count == 0) + return; + + float speed = Settings.Fly ? Settings.FlySpeed : Math.Max(Math.Max(player.maxRunSpeed, player.accRunSpeed), 3f); + float jump = Player.jumpSpeed * 1.5f; + int baseline = FirstHit(player, player.velocity, false); + if (baseline > Horizon) + return; + + Move best = Moves[0]; + int bestHit = baseline; + foreach (Move move in Moves) + { + if (move == Moves[0]) + continue; + + var velocity = new Vector2(move.Horizontal * speed, move.Vertical == 0 ? player.velocity.Y : move.Vertical * jump * player.gravDir); + int hit = FirstHit(player, velocity, true); + if (hit > bestHit) + (best, bestHit) = (move, hit); + } + + if (best == Moves[0]) + return; + + Apply(player, best, speed, jump); + } + + static void Apply(Player player, Move move, float speed, float jump) + { + player.controlLeft = move.Horizontal < 0; + player.controlRight = move.Horizontal > 0; + if (move.Horizontal != 0) + player.velocity.X = move.Horizontal * speed; + + player.controlUp = move.Vertical < 0; + player.controlDown = move.Vertical > 0; + if (move.Vertical != 0) + player.velocity.Y = move.Vertical * jump * player.gravDir; + } + + // First tick a threat overlaps the player moving at this velocity, or Horizon + 1 when none do. + static int FirstHit(Player player, Vector2 velocity, bool checkWalls) + { + Vector2 size = new(player.width, player.height); + if (checkWalls && TileCollision.Sweep(player.Center, player.Center + velocity * WallCheckTicks, player.width, player.height)) + return -1; + + for (int t = 1; t <= Horizon; t++) + { + Vector2 position = player.position + velocity * t; + var bounds = new Rectangle((int)(position.X - Margin), (int)(position.Y - Margin), (int)(size.X + Margin * 2), (int)(size.Y + Margin * 2)); + foreach (Threat threat in Threats) + { + if (t >= threat.Count) + continue; + + Vector2 center = threat.Path[t]; + if (bounds.Intersects(new Rectangle((int)(center.X - threat.Width / 2f), (int)(center.Y - threat.Height / 2f), threat.Width, threat.Height))) + return t; + } + } + + return Horizon + 1; + } + + static void CollectThreats(Player player) + { + Threats.Clear(); + float rangeSq = ScanRange * ScanRange; + for (int i = 0; i < Main.maxProjectiles && Threats.Count < MaxThreats; i++) + { + Projectile projectile = Main.projectile[i]; + if (!projectile.active || !projectile.hostile || projectile.damage <= 0 || Vector2.DistanceSquared(projectile.Center, player.Center) > rangeSq) + continue; + + if (!Ballistics.Current(i, out Vector2 velocity, out int age)) + (velocity, age) = (projectile.velocity * (1 + projectile.extraUpdates), 0); + + Threat threat = Pool[Threats.Count]; + threat.Count = Ballistics.Path(Ballistics.Get(projectile.type), projectile.Center, velocity, age, Horizon, threat.Path); + (threat.Width, threat.Height) = (Math.Max(4, projectile.width), Math.Max(4, projectile.height)); + Threats.Add(threat); + } + + for (int i = 0; i < Main.maxNPCs && Threats.Count < MaxThreats; i++) + { + NPC npc = Main.npc[i]; + if (!Hostility.IsHostile(npc) || npc.damage <= 0 || Vector2.DistanceSquared(npc.Center, player.Center) > rangeSq) + continue; + + Threat threat = Pool[Threats.Count]; + for (int t = 0; t <= Horizon; t++) + threat.Path[t] = npc.Center + NpcMotion.Offset(npc, t); + (threat.Count, threat.Width, threat.Height) = (Horizon + 1, npc.width, npc.height); + Threats.Add(threat); + } + } +} diff --git a/src/TerraSharp/Features/PlayerFeatures.cs b/src/TerraSharp/Features/PlayerFeatures.cs index afd0d92..5782230 100644 --- a/src/TerraSharp/Features/PlayerFeatures.cs +++ b/src/TerraSharp/Features/PlayerFeatures.cs @@ -2,6 +2,7 @@ using System; using Microsoft.Xna.Framework; using Terraria; using TerraSharp.Core; +using TerraSharp.Features.Combat; using TerraSharp.UI; namespace TerraSharp.Features; @@ -102,6 +103,7 @@ public static class PlayerFeatures ApplyGhost(player); ApplyVision(player); + AutoDodge.Steer(player); } // ResetEffects clears both every tick; NPC.TargetClosest and SetTargetTrackingValues read them. diff --git a/src/TerraSharp/UI/MainWindow.Pages.cs b/src/TerraSharp/UI/MainWindow.Pages.cs index 73bad2c..2df3fc3 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("Auto Dodge", ref Settings.AutoDodge); Toggle("Magic Bullets", ref Settings.MagicBullets); Toggle("Shot Preview", ref Settings.ShotPreview); diff --git a/src/TerraSharp/UI/MainWindow.Tooltips.cs b/src/TerraSharp/UI/MainWindow.Tooltips.cs index 0acc12d..583d239 100644 --- a/src/TerraSharp/UI/MainWindow.Tooltips.cs +++ b/src/TerraSharp/UI/MainWindow.Tooltips.cs @@ -29,6 +29,7 @@ public sealed partial class MainWindow ["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 Dodge"] = "Moves you out of the way of incoming projectiles and enemies.", ["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 fbc9dc4..12e5739 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, Settings.MagicBullets)), + 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, Settings.AutoDodge)), 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)),