feat: Add configurable stats overlay
This commit is contained in:
1 parent
6701dd0305
commit
d68de62cf5
12 files changed
+312
-7
No files matched your search
@@ -30,6 +30,7 @@ public static class Hotkeys
|
|||||||
new(InterfaceGroup, "BuffBrowser", "Buff Browser", () => WindowManager.Toggle(WindowManager.Buffs)),
|
new(InterfaceGroup, "BuffBrowser", "Buff Browser", () => WindowManager.Toggle(WindowManager.Buffs)),
|
||||||
new(InterfaceGroup, "NpcSpawner", "NPC Spawner", () => WindowManager.Toggle(WindowManager.Npcs)),
|
new(InterfaceGroup, "NpcSpawner", "NPC Spawner", () => WindowManager.Toggle(WindowManager.Npcs)),
|
||||||
new(InterfaceGroup, "ItemEditor", "Item Editor", () => WindowManager.Toggle(WindowManager.Editor)),
|
new(InterfaceGroup, "ItemEditor", "Item Editor", () => WindowManager.Toggle(WindowManager.Editor)),
|
||||||
|
new(InterfaceGroup, "Stats", "Stats Overlay", () => WindowManager.Toggle(WindowManager.Stats)),
|
||||||
|
|
||||||
Toggle("Player", "GodMode", "God Mode", () => Settings.GodMode, v => Settings.GodMode = v),
|
Toggle("Player", "GodMode", "God Mode", () => Settings.GodMode, v => Settings.GodMode = v),
|
||||||
Toggle("Player", "GhostMode", "Ghost Mode", () => Settings.GhostMode, v => Settings.GhostMode = v),
|
Toggle("Player", "GhostMode", "Ghost Mode", () => Settings.GhostMode, v => Settings.GhostMode = v),
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ public static partial class Settings
|
|||||||
[nameof(GhostMode)] = "Player.GhostMode",
|
[nameof(GhostMode)] = "Player.GhostMode",
|
||||||
[nameof(DefenseBoost)] = "Player.DefenseBoost.Enabled",
|
[nameof(DefenseBoost)] = "Player.DefenseBoost.Enabled",
|
||||||
[nameof(DefenseMult)] = "Player.DefenseBoost.Multiplier",
|
[nameof(DefenseMult)] = "Player.DefenseBoost.Multiplier",
|
||||||
|
[nameof(ShowStats)] = "Player.Stats.Enabled",
|
||||||
|
[nameof(StatLines)] = "Player.Stats.Lines",
|
||||||
|
[nameof(StatIcons)] = "Player.Stats.Icons",
|
||||||
|
|
||||||
[nameof(Fly)] = "Movement.Fly.Enabled",
|
[nameof(Fly)] = "Movement.Fly.Enabled",
|
||||||
[nameof(FlySpeed)] = "Movement.Fly.Speed",
|
[nameof(FlySpeed)] = "Movement.Fly.Speed",
|
||||||
|
|||||||
@@ -19,12 +19,35 @@ public enum EspBoxStyle
|
|||||||
Filled,
|
Filled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum StatLine
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Dps = 1 << 0,
|
||||||
|
PeakDps = 1 << 1,
|
||||||
|
Damage = 1 << 2,
|
||||||
|
BestHit = 1 << 3,
|
||||||
|
Kills = 1 << 4,
|
||||||
|
Time = 1 << 5,
|
||||||
|
Deaths = 1 << 6,
|
||||||
|
Minions = 1 << 7,
|
||||||
|
Sentries = 1 << 8,
|
||||||
|
Defense = 1 << 9,
|
||||||
|
DamageReduction = 1 << 10,
|
||||||
|
Crit = 1 << 11,
|
||||||
|
LifeRegen = 1 << 12,
|
||||||
|
Speed = 1 << 13,
|
||||||
|
Luck = 1 << 14,
|
||||||
|
}
|
||||||
|
|
||||||
// Public mutable fields are persisted through Settings.Json.cs.
|
// Public mutable fields are persisted through Settings.Json.cs.
|
||||||
public static partial class Settings
|
public static partial class Settings
|
||||||
{
|
{
|
||||||
public static bool GodMode, InfHealth, InfMana, InfBreath, InfFlight, InstantRespawn;
|
public static bool GodMode, InfHealth, InfMana, InfBreath, InfFlight, InstantRespawn;
|
||||||
public static bool AutoHeal, AutoMana, GhostMode, DefenseBoost;
|
public static bool AutoHeal, AutoMana, GhostMode, DefenseBoost;
|
||||||
public static float AutoHealAt = 40f, AutoManaAt = 25f, BuffMinutes = 10f, DefenseMult = 3f;
|
public static float AutoHealAt = 40f, AutoManaAt = 25f, BuffMinutes = 10f, DefenseMult = 3f;
|
||||||
|
public static bool ShowStats, StatIcons = true;
|
||||||
|
public static StatLine StatLines = StatLine.Dps | StatLine.Damage | StatLine.Kills | StatLine.Minions | StatLine.Sentries | StatLine.Defense;
|
||||||
|
|
||||||
public static bool Fly, SuperSpeed, HighJump, NoFallDamage, LavaImmune, WaterWalk, NoKnockback, GravityFlip, ExtendedReach;
|
public static bool Fly, SuperSpeed, HighJump, NoFallDamage, LavaImmune, WaterWalk, NoKnockback, GravityFlip, ExtendedReach;
|
||||||
public static float FlySpeed = 12f, SpeedMult = 3f;
|
public static float FlySpeed = 12f, SpeedMult = 3f;
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using Terraria;
|
||||||
|
|
||||||
|
namespace TerraSharp.Features;
|
||||||
|
|
||||||
|
// Damage is the life the local player removed from NPCs, read from per-tick life changes.
|
||||||
|
public static class Stats
|
||||||
|
{
|
||||||
|
const int DpsTicks = 180;
|
||||||
|
|
||||||
|
static readonly int[] LastLife = new int[Main.maxNPCs], LastType = new int[Main.maxNPCs];
|
||||||
|
static readonly int[] Recent = new int[DpsTicks];
|
||||||
|
static int _recentIndex, _recentSum;
|
||||||
|
static int _world = -1;
|
||||||
|
static bool _wasDead;
|
||||||
|
|
||||||
|
public static long Damage { get; private set; }
|
||||||
|
|
||||||
|
public static int BestHit { get; private set; }
|
||||||
|
|
||||||
|
public static int Kills { get; private set; }
|
||||||
|
|
||||||
|
public static int Deaths { get; private set; }
|
||||||
|
|
||||||
|
public static int Ticks { get; private set; }
|
||||||
|
|
||||||
|
public static float Dps => _recentSum * 60f / DpsTicks;
|
||||||
|
|
||||||
|
public static float PeakDps { get; private set; }
|
||||||
|
|
||||||
|
public static void Reset()
|
||||||
|
{
|
||||||
|
Array.Clear(Recent, 0, Recent.Length);
|
||||||
|
(_recentIndex, _recentSum) = (0, 0);
|
||||||
|
(Damage, BestHit, Kills, Deaths, Ticks, PeakDps) = (0, 0, 0, 0, 0, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player.Update postfix for the local player.
|
||||||
|
public static void Update(Player player)
|
||||||
|
{
|
||||||
|
if (Main.worldID != _world)
|
||||||
|
{
|
||||||
|
_world = Main.worldID;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ticks++;
|
||||||
|
if (player.dead && !_wasDead)
|
||||||
|
Deaths++;
|
||||||
|
_wasDead = player.dead;
|
||||||
|
|
||||||
|
int dealt = 0;
|
||||||
|
for (int i = 0; i < Main.maxNPCs; i++)
|
||||||
|
{
|
||||||
|
NPC npc = Main.npc[i];
|
||||||
|
int life = npc.active ? Math.Max(0, npc.life) : 0;
|
||||||
|
bool killed = !npc.active && npc.life <= 0;
|
||||||
|
// Worm segments share one life pool owned by the head.
|
||||||
|
bool counts = LastType[i] != 0 && npc.type == LastType[i] && npc.lastInteraction == Main.myPlayer && (npc.realLife < 0 || npc.realLife == i) && (npc.active || killed);
|
||||||
|
if (counts && life < LastLife[i])
|
||||||
|
{
|
||||||
|
int hit = LastLife[i] - life;
|
||||||
|
dealt += hit;
|
||||||
|
BestHit = Math.Max(BestHit, hit);
|
||||||
|
if (life == 0 && !npc.friendly && !npc.townNPC && npc.lifeMax > 5)
|
||||||
|
Kills++;
|
||||||
|
}
|
||||||
|
|
||||||
|
(LastType[i], LastLife[i]) = npc.active ? (npc.type, life) : (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Damage += dealt;
|
||||||
|
_recentSum += dealt - Recent[_recentIndex];
|
||||||
|
Recent[_recentIndex] = dealt;
|
||||||
|
_recentIndex = (_recentIndex + 1) % DpsTicks;
|
||||||
|
PeakDps = Math.Max(PeakDps, Dps);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ internal static class PlayerUpdatePatch
|
|||||||
{
|
{
|
||||||
Clock.Advance();
|
Clock.Advance();
|
||||||
PlayerFeatures.PostUpdate(player);
|
PlayerFeatures.PostUpdate(player);
|
||||||
|
Stats.Update(player);
|
||||||
CombatFeatures.PostPlayerUpdate(player);
|
CombatFeatures.PostPlayerUpdate(player);
|
||||||
Ballistics.Observe();
|
Ballistics.Observe();
|
||||||
NpcMotion.Track();
|
NpcMotion.Track();
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ public sealed partial class MainWindow
|
|||||||
case 0: PlayerFeatures.FullHeal(); break;
|
case 0: PlayerFeatures.FullHeal(); break;
|
||||||
case 1: PlayerFeatures.TeleportSpawn(); break;
|
case 1: PlayerFeatures.TeleportSpawn(); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Group("Stats");
|
||||||
|
if (Toggle("Stats Overlay", ref Settings.ShowStats) && Buttons("Configure Overlay") == 0)
|
||||||
|
WindowManager.StatsSetup.ShowBeside(Bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MovementPage()
|
void MovementPage()
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ public sealed partial class MainWindow
|
|||||||
["Auto Mana"] = "Uses Quick Mana below the set mana level.",
|
["Auto Mana"] = "Uses Quick Mana below the set mana level.",
|
||||||
["Ghost Mode"] = "Enemies stop chasing you. Bosses and some flying enemies still attack.",
|
["Ghost Mode"] = "Enemies stop chasing you. Bosses and some flying enemies still attack.",
|
||||||
["Defense Multiplier"] = "Multiplies your total defense.",
|
["Defense Multiplier"] = "Multiplies your total defense.",
|
||||||
|
["Stats Overlay"] = "Shows the selected stats on screen. Damage, kills and deaths count from when you enter the world.",
|
||||||
|
["Configure Overlay"] = "Choose the lines the overlay shows.",
|
||||||
["Fly"] = "Move through blocks with your movement keys. Up or Jump rises; Down descends.",
|
["Fly"] = "Move through blocks with your movement keys. Up or Jump rises; Down descends.",
|
||||||
["Gravity Flip"] = "Walk on the ceiling.",
|
["Gravity Flip"] = "Walk on the ceiling.",
|
||||||
["Lava Immune"] = "Also protects against hot blocks.",
|
["Lava Immune"] = "Also protects against hot blocks.",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public sealed partial class MainWindow : Window
|
|||||||
Open = true;
|
Open = true;
|
||||||
_pages =
|
_pages =
|
||||||
[
|
[
|
||||||
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("Player", PlayerPage, () => Count(Settings.GodMode, Settings.InfHealth, Settings.InfMana, Settings.InfBreath, Settings.InfFlight, Settings.InstantRespawn, Settings.AutoHeal, Settings.AutoMana, Settings.GhostMode, Settings.DefenseBoost, Settings.ShowStats)),
|
||||||
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("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, Settings.AutoDodge)),
|
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("Items", ItemsPage, () => Count(Settings.InstantBreak, Settings.InfItems, Settings.ItemVacuum, Settings.AutoSell, Settings.AutoFish, Settings.Sonar)),
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Terraria;
|
||||||
|
using TerraSharp.Core;
|
||||||
|
using TerraSharp.Features;
|
||||||
|
|
||||||
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
|
public sealed class StatsConfig() : Window("statsConfig", "Stats Overlay", Vector2.Zero, 300, 640)
|
||||||
|
{
|
||||||
|
const int RowHeight = 30;
|
||||||
|
|
||||||
|
public override bool Popup => true;
|
||||||
|
|
||||||
|
public void ShowBeside(Rectangle anchor)
|
||||||
|
{
|
||||||
|
Pos = new Vector2(anchor.Right + 8, anchor.Y);
|
||||||
|
WindowManager.Show(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawBody(Rectangle body)
|
||||||
|
{
|
||||||
|
int y = body.Y;
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, "Drag the overlay while the menu is open.", new Rectangle(body.X + 2, y, body.Width, 22), Widgets.Dim * 0.8f, 0.7f);
|
||||||
|
y += 26;
|
||||||
|
|
||||||
|
Settings.StatIcons = Widgets.ToggleRow(new Rectangle(body.X, y, body.Width, 28), "Icons", Settings.StatIcons);
|
||||||
|
y += RowHeight;
|
||||||
|
Widgets.Separator(body.X, y + 2, body.Width);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
foreach ((StatLine line, string label, _) in StatsWindow.Lines)
|
||||||
|
{
|
||||||
|
bool shown = Widgets.ToggleRow(new Rectangle(body.X, y, body.Width, 28), label, (Settings.StatLines & line) != 0);
|
||||||
|
Settings.StatLines = shown ? Settings.StatLines | line : Settings.StatLines & ~line;
|
||||||
|
y += RowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Widgets.Button(new Rectangle(body.X, body.Bottom - 32, body.Width, 32), "Reset Stats", false, 0.82f))
|
||||||
|
Stats.Reset();
|
||||||
|
if (Widgets.Hot(new Rectangle(body.X, body.Bottom - 32, body.Width, 32)))
|
||||||
|
Widgets.Tooltip = "Clears damage, kills, deaths and session time.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Terraria;
|
||||||
|
using Terraria.ID;
|
||||||
|
using TerraSharp.Core;
|
||||||
|
using TerraSharp.Features;
|
||||||
|
|
||||||
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
|
// Overlay without a frame; it only takes the mouse, to be dragged, while the menu is open.
|
||||||
|
public sealed class StatsWindow() : Window("stats", "", new Vector2(20, 280), PanelWidth, RowHeight)
|
||||||
|
{
|
||||||
|
const int PanelWidth = 240, RowHeight = 24, IconSize = 20;
|
||||||
|
const float TextScale = 0.8f;
|
||||||
|
|
||||||
|
// Main.DrawInfoAccs converts velocity to the stopwatch's miles per hour this way.
|
||||||
|
const float MilesPerHour = 216000f / 42240f;
|
||||||
|
|
||||||
|
// Icons are the accessories and items that show or affect each stat in vanilla.
|
||||||
|
public static readonly (StatLine Line, string Label, int Icon)[] Lines =
|
||||||
|
[
|
||||||
|
(StatLine.Dps, "DPS", ItemID.DPSMeter),
|
||||||
|
(StatLine.PeakDps, "Peak DPS", ItemID.DestroyerEmblem),
|
||||||
|
(StatLine.Damage, "Damage dealt", ItemID.WarriorEmblem),
|
||||||
|
(StatLine.BestHit, "Best hit", ItemID.AvengerEmblem),
|
||||||
|
(StatLine.Kills, "Kills", ItemID.TallyCounter),
|
||||||
|
(StatLine.Time, "Session time", ItemID.GoldWatch),
|
||||||
|
(StatLine.Deaths, "Deaths", ItemID.Tombstone),
|
||||||
|
(StatLine.Minions, "Minions", ItemID.SlimeStaff),
|
||||||
|
(StatLine.Sentries, "Sentries", ItemID.QueenSpiderStaff),
|
||||||
|
(StatLine.Defense, "Defense", ItemID.CobaltShield),
|
||||||
|
(StatLine.DamageReduction, "Damage reduction", ItemID.WormScarf),
|
||||||
|
(StatLine.Crit, "Critical chance", ItemID.EyeoftheGolem),
|
||||||
|
(StatLine.LifeRegen, "Life regen", ItemID.BandofRegeneration),
|
||||||
|
(StatLine.Speed, "Speed", ItemID.Stopwatch),
|
||||||
|
(StatLine.Luck, "Luck", ItemID.LuckPotion),
|
||||||
|
];
|
||||||
|
|
||||||
|
public override bool Open { get => Settings.ShowStats; set => Settings.ShowStats = value; }
|
||||||
|
|
||||||
|
public override bool Frameless => true;
|
||||||
|
|
||||||
|
public override bool Interactive => WindowManager.Menu.Open && !Main.gameMenu;
|
||||||
|
|
||||||
|
public override void DrawBody(Rectangle body)
|
||||||
|
{
|
||||||
|
if (Main.gameMenu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Interactive)
|
||||||
|
Render.Outline(Widgets.SpriteBatch, body, Widgets.Hot(body) ? Widgets.Yellow * 0.6f : Color.White * 0.2f);
|
||||||
|
|
||||||
|
Player player = Main.LocalPlayer;
|
||||||
|
int y = body.Y, shown = 0;
|
||||||
|
foreach ((StatLine line, string label, int icon) in Lines)
|
||||||
|
{
|
||||||
|
if ((Settings.StatLines & line) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Row(new Rectangle(body.X, y, body.Width, RowHeight), label, Value(line, player), Settings.StatIcons ? icon : null);
|
||||||
|
y += RowHeight;
|
||||||
|
shown++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Height = Math.Max(1, shown) * RowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string Value(StatLine line, Player player) => line switch
|
||||||
|
{
|
||||||
|
StatLine.Dps => $"{Stats.Dps:0}",
|
||||||
|
StatLine.PeakDps => $"{Stats.PeakDps:0}",
|
||||||
|
StatLine.Damage => $"{Stats.Damage:N0}",
|
||||||
|
StatLine.BestHit => $"{Stats.BestHit:N0}",
|
||||||
|
StatLine.Kills => $"{Stats.Kills:N0}",
|
||||||
|
StatLine.Time => SessionTime(),
|
||||||
|
StatLine.Deaths => $"{Stats.Deaths}",
|
||||||
|
StatLine.Minions => $"{player.slotsMinions:0.#} / {player.maxMinions}",
|
||||||
|
StatLine.Sentries => $"{Sentries(player)} / {player.maxTurrets}",
|
||||||
|
StatLine.Defense => $"{(int)player.statDefense}",
|
||||||
|
StatLine.DamageReduction => $"{player.endurance:0%}",
|
||||||
|
StatLine.Crit => player.HeldItem.damage > 0 ? $"{player.GetWeaponCrit(player.HeldItem)}%" : "-",
|
||||||
|
StatLine.LifeRegen => $"{player.lifeRegen / 2f:0.#}/s",
|
||||||
|
StatLine.Speed => $"{player.velocity.Length() * MilesPerHour:0} mph",
|
||||||
|
StatLine.Luck => $"{player.luck:0.00}",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
|
||||||
|
static string SessionTime()
|
||||||
|
{
|
||||||
|
TimeSpan time = TimeSpan.FromSeconds(Stats.Ticks / 60);
|
||||||
|
return $"{(int)time.TotalHours}:{time.Minutes:00}:{time.Seconds:00}";
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sentries(Player player)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < Main.maxProjectiles; i++)
|
||||||
|
if (Main.projectile[i] is { active: true, sentry: true } projectile && projectile.owner == player.whoAmI)
|
||||||
|
count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Row(Rectangle row, string label, string value, int? icon)
|
||||||
|
{
|
||||||
|
int textX = row.X;
|
||||||
|
if (icon is { } type)
|
||||||
|
{
|
||||||
|
Render.Item(Widgets.SpriteBatch, type, new Rectangle(row.X, row.Y + (row.Height - IconSize) / 2, IconSize, IconSize));
|
||||||
|
textX += IconSize + 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, label, new Rectangle(textX, row.Y, row.Right - textX, row.Height), Widgets.Dim, TextScale);
|
||||||
|
Render.TextRight(Widgets.SpriteBatch, value, row, Color.White, TextScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,9 +12,18 @@ public abstract class Window(string id, string title, Vector2 position, int widt
|
|||||||
|
|
||||||
public int Width { get; } = width;
|
public int Width { get; } = width;
|
||||||
|
|
||||||
public int Height { get; } = height;
|
public int Height { get; protected set; } = height;
|
||||||
|
|
||||||
public bool Open { get; set; }
|
public virtual bool Open { get; set; }
|
||||||
|
|
||||||
|
// Draws only the body: no panel, title or close button.
|
||||||
|
public virtual bool Frameless => false;
|
||||||
|
|
||||||
|
// Closes on a click outside it or when the menu closes.
|
||||||
|
public virtual bool Popup => false;
|
||||||
|
|
||||||
|
// A window that is not interactive lets the mouse through to the game.
|
||||||
|
public virtual bool Interactive => true;
|
||||||
|
|
||||||
public Rectangle Bounds => new((int)Pos.X, (int)Pos.Y, Width, Height);
|
public Rectangle Bounds => new((int)Pos.X, (int)Pos.Y, Width, Height);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using Terraria;
|
using Terraria;
|
||||||
@@ -17,11 +18,13 @@ public static class WindowManager
|
|||||||
public static readonly ItemEditor Editor = new();
|
public static readonly ItemEditor Editor = new();
|
||||||
public static readonly ItemPicker SellList = new("sellList", "Auto Sell List", new Vector2(680, 130), Settings.SellList);
|
public static readonly ItemPicker SellList = new("sellList", "Auto Sell List", new Vector2(680, 130), Settings.SellList);
|
||||||
public static readonly ItemPicker ChestList = new("chestList", "Chest Finder Items", new Vector2(700, 140), Settings.ChestList);
|
public static readonly ItemPicker ChestList = new("chestList", "Chest Finder Items", new Vector2(700, 140), Settings.ChestList);
|
||||||
|
public static readonly StatsWindow Stats = new();
|
||||||
|
public static readonly StatsConfig StatsSetup = new();
|
||||||
|
|
||||||
const int TitleHeight = 40;
|
const int TitleHeight = 40;
|
||||||
const int MaxInputLength = 40;
|
const int MaxInputLength = 40;
|
||||||
|
|
||||||
static readonly List<Window> Stack = [Items, Buffs, Npcs, Editor, SellList, ChestList, Menu];
|
static readonly List<Window> Stack = [Items, Buffs, Npcs, Editor, SellList, ChestList, Stats, Menu, StatsSetup];
|
||||||
static Window? _dragging;
|
static Window? _dragging;
|
||||||
static Vector2 _dragOffset;
|
static Vector2 _dragOffset;
|
||||||
|
|
||||||
@@ -29,11 +32,11 @@ public static class WindowManager
|
|||||||
|
|
||||||
public static bool MouseOver { get; private set; }
|
public static bool MouseOver { get; private set; }
|
||||||
|
|
||||||
public static bool AnyOpen => Stack.Exists(w => w.Open);
|
public static bool AnyOpen => Stack.Exists(w => w.Open && w.Interactive);
|
||||||
|
|
||||||
public static IEnumerable<Window> All => Stack;
|
public static IEnumerable<Window> All => Stack;
|
||||||
|
|
||||||
public static bool Contains(Point point) => Stack.Exists(w => w.Open && w.Bounds.Contains(point));
|
public static bool Contains(Point point) => Stack.Exists(w => w.Open && w.Interactive && w.Bounds.Contains(point));
|
||||||
|
|
||||||
public static void Show(Window window)
|
public static void Show(Window window)
|
||||||
{
|
{
|
||||||
@@ -55,6 +58,10 @@ public static class WindowManager
|
|||||||
window.Open = false;
|
window.Open = false;
|
||||||
Widgets.Sound(11);
|
Widgets.Sound(11);
|
||||||
Unfocus();
|
Unfocus();
|
||||||
|
if (window == Menu)
|
||||||
|
foreach (Window popup in Stack)
|
||||||
|
if (popup.Popup)
|
||||||
|
popup.Open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Toggle(Window window)
|
public static void Toggle(Window window)
|
||||||
@@ -122,8 +129,10 @@ public static class WindowManager
|
|||||||
foreach (Window window in Stack)
|
foreach (Window window in Stack)
|
||||||
window.Pos = ClampPosition(window, window.Pos);
|
window.Pos = ClampPosition(window, window.Pos);
|
||||||
|
|
||||||
Window? top = Stack.FindLast(w => w.Open && w.Bounds.Contains(Widgets.Mouse));
|
Window? top = Stack.FindLast(w => w.Open && w.Interactive && w.Bounds.Contains(Widgets.Mouse));
|
||||||
MouseOver = Widgets.InputOk && top is not null;
|
MouseOver = Widgets.InputOk && top is not null;
|
||||||
|
// Popups the click landed outside of; a button that toggles one this frame still works.
|
||||||
|
Window[] dismissed = Widgets.Click ? [.. Stack.Where(w => w.Open && w.Popup && w != top)] : [];
|
||||||
if (Widgets.Click)
|
if (Widgets.Click)
|
||||||
if (top is null)
|
if (top is null)
|
||||||
Unfocus();
|
Unfocus();
|
||||||
@@ -148,6 +157,8 @@ public static class WindowManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widgets.Blocked = false;
|
Widgets.Blocked = false;
|
||||||
|
foreach (Window popup in dismissed)
|
||||||
|
popup.Open = false;
|
||||||
|
|
||||||
if (Widgets.Tooltip is not null && MouseOver)
|
if (Widgets.Tooltip is not null && MouseOver)
|
||||||
Main.instance.MouseText(Widgets.Tooltip, 0, 0, -1, -1, -1, -1, 0);
|
Main.instance.MouseText(Widgets.Tooltip, 0, 0, -1, -1, -1, -1, 0);
|
||||||
@@ -156,6 +167,21 @@ public static class WindowManager
|
|||||||
static void DrawFrame(Window window)
|
static void DrawFrame(Window window)
|
||||||
{
|
{
|
||||||
Rectangle bounds = window.Bounds;
|
Rectangle bounds = window.Bounds;
|
||||||
|
if (window.Frameless)
|
||||||
|
{
|
||||||
|
bool hot = Widgets.Hot(bounds);
|
||||||
|
if (hot)
|
||||||
|
Widgets.Tooltip = "Drag to move";
|
||||||
|
if (hot && Widgets.Click)
|
||||||
|
{
|
||||||
|
_dragging = window;
|
||||||
|
_dragOffset = window.Pos - new Vector2(Widgets.Mouse.X, Widgets.Mouse.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.DrawBody(bounds);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Render.Fill(Widgets.SpriteBatch, new Rectangle(bounds.X + 4, bounds.Y + 4, bounds.Width - 8, bounds.Height - 8), new Color(16, 20, 40) * 0.92f);
|
Render.Fill(Widgets.SpriteBatch, new Rectangle(bounds.X + 4, bounds.Y + 4, bounds.Width - 8, bounds.Height - 8), new Color(16, 20, 40) * 0.92f);
|
||||||
Widgets.Panel(bounds, Widgets.WindowBg);
|
Widgets.Panel(bounds, Widgets.WindowBg);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user