feat: Add player list, player inspector and party frames
This commit is contained in:
1 parent
d68de62cf5
commit
9d748d679b
19 files changed
+630
-7
No files matched your search
@@ -31,6 +31,8 @@ public static class Hotkeys
|
|||||||
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)),
|
new(InterfaceGroup, "Stats", "Stats Overlay", () => WindowManager.Toggle(WindowManager.Stats)),
|
||||||
|
new(InterfaceGroup, "Players", "Player List", () => WindowManager.Toggle(WindowManager.Players)),
|
||||||
|
new(InterfaceGroup, "PartyFrames", "Party Frames", () => WindowManager.Toggle(WindowManager.Party)),
|
||||||
|
|
||||||
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),
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ public static partial class Settings
|
|||||||
[nameof(ShowStats)] = "Player.Stats.Enabled",
|
[nameof(ShowStats)] = "Player.Stats.Enabled",
|
||||||
[nameof(StatLines)] = "Player.Stats.Lines",
|
[nameof(StatLines)] = "Player.Stats.Lines",
|
||||||
[nameof(StatIcons)] = "Player.Stats.Icons",
|
[nameof(StatIcons)] = "Player.Stats.Icons",
|
||||||
|
[nameof(PartyFrames)] = "Player.PartyFrames.Enabled",
|
||||||
|
[nameof(PartySelf)] = "Player.PartyFrames.Self",
|
||||||
|
|
||||||
[nameof(Fly)] = "Movement.Fly.Enabled",
|
[nameof(Fly)] = "Movement.Fly.Enabled",
|
||||||
[nameof(FlySpeed)] = "Movement.Fly.Speed",
|
[nameof(FlySpeed)] = "Movement.Fly.Speed",
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public static partial class Settings
|
|||||||
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 bool ShowStats, StatIcons = true;
|
||||||
|
public static bool PartyFrames, PartySelf;
|
||||||
public static StatLine StatLines = StatLine.Dps | StatLine.Damage | StatLine.Kills | StatLine.Minions | StatLine.Sentries | StatLine.Defense;
|
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;
|
||||||
|
|||||||
@@ -284,6 +284,13 @@ public static class PlayerFeatures
|
|||||||
player.fallStart = (int)(player.position.Y / 16f);
|
player.fallStart = (int)(player.position.Y / 16f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same jump as the Wormhole Potion in Player.HandleSpectatingControls, without the potion.
|
||||||
|
public static void TeleportToPlayer(Player target)
|
||||||
|
{
|
||||||
|
Player player = Main.LocalPlayer;
|
||||||
|
player.UnityTeleport(player.position + target.Bottom - player.Bottom);
|
||||||
|
}
|
||||||
|
|
||||||
// Matches the Magic Mirror branch of Player.ItemCheck_Inner.
|
// Matches the Magic Mirror branch of Player.ItemCheck_Inner.
|
||||||
public static void TeleportSpawn()
|
public static void TeleportSpawn()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using Terraria;
|
||||||
|
|
||||||
|
namespace TerraSharp.Features;
|
||||||
|
|
||||||
|
public static class Spectate
|
||||||
|
{
|
||||||
|
public static bool Available => NpcSpawner.Multiplayer;
|
||||||
|
|
||||||
|
#if TMODLOADER
|
||||||
|
// tModLoader 1.4.4 has no spectating; SpectateCameraPatch follows this player instead.
|
||||||
|
public static int Target { get; private set; } = -1;
|
||||||
|
#else
|
||||||
|
// The client sets spectating to itself until the server confirms the target.
|
||||||
|
public static int Target => Main.LocalPlayer.spectating is var who && who != Main.myPlayer ? who : -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static void Toggle(int who)
|
||||||
|
{
|
||||||
|
if (Target == who)
|
||||||
|
Stop();
|
||||||
|
else
|
||||||
|
Start(who);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Start(int who)
|
||||||
|
{
|
||||||
|
if (!Available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if TMODLOADER
|
||||||
|
Target = who;
|
||||||
|
#else
|
||||||
|
Main.LocalPlayer.SetOrRequestSpectating(who);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Stop()
|
||||||
|
{
|
||||||
|
#if TMODLOADER
|
||||||
|
Target = -1;
|
||||||
|
#else
|
||||||
|
if (Main.LocalPlayer.spectating >= 0)
|
||||||
|
Main.LocalPlayer.SetOrRequestSpectating(-1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,9 @@ using MonoMod.Cil;
|
|||||||
using Terraria;
|
using Terraria;
|
||||||
using Terraria.Graphics.Light;
|
using Terraria.Graphics.Light;
|
||||||
using TerraSharp.Core;
|
using TerraSharp.Core;
|
||||||
|
#if TMODLOADER
|
||||||
|
using TerraSharp.Features;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace TerraSharp.Patches;
|
namespace TerraSharp.Patches;
|
||||||
|
|
||||||
@@ -14,6 +17,9 @@ internal static class VisionPatches
|
|||||||
{
|
{
|
||||||
FullBrightPatch.Install(hooks);
|
FullBrightPatch.Install(hooks);
|
||||||
ZoomPatch.Install(hooks);
|
ZoomPatch.Install(hooks);
|
||||||
|
#if TMODLOADER
|
||||||
|
SpectateCameraPatch.Install(hooks);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,3 +84,32 @@ internal static class ZoomPatch
|
|||||||
throw new MissingMethodException(context.Method.FullName, "Clamp");
|
throw new MissingMethodException(context.Method.FullName, "Clamp");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TMODLOADER
|
||||||
|
// Centers the camera the way Player.SpectatingCameraPosition does in 1.4.5.
|
||||||
|
internal static class SpectateCameraPatch
|
||||||
|
{
|
||||||
|
internal static void Install(HookRegistry hooks) => hooks.Add(typeof(Main), "DoDraw_UpdateCameraPosition", (Action<Action>)UpdateCamera);
|
||||||
|
|
||||||
|
static void UpdateCamera(Action original)
|
||||||
|
{
|
||||||
|
original();
|
||||||
|
Guard.Run("Spectate camera", Follow);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Follow()
|
||||||
|
{
|
||||||
|
if (Spectate.Target < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Plugin.Active || Main.gameMenu || !Spectate.Available || Main.player[Spectate.Target] is not { active: true } target)
|
||||||
|
{
|
||||||
|
Spectate.Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 center = target.Bottom + new Vector2(0f, target.gfxOffY - 21f);
|
||||||
|
Main.screenPosition = (center - new Vector2(Main.screenWidth, Main.screenHeight) / 2f).Floor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -45,7 +45,7 @@ public sealed class BuffBrowser() : GridBrowser("buffs", "Buff Browser", new Vec
|
|||||||
static bool IsMount(int type) => BuffID.Sets.MountType[type] >= 0;
|
static bool IsMount(int type) => BuffID.Sets.MountType[type] >= 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static string FormatTime(int ticks)
|
public static string FormatTime(int ticks)
|
||||||
{
|
{
|
||||||
int seconds = ticks / 60;
|
int seconds = ticks / 60;
|
||||||
return seconds >= 3600 ? $"{seconds / 3600}h" : seconds >= 60 ? $"{seconds / 60}m" : $"{seconds}s";
|
return seconds >= 3600 ? $"{seconds / 3600}h" : seconds >= 60 ? $"{seconds / 60}m" : $"{seconds}s";
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public static class InterfaceLayers
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
layers.RemoveAll(l => l.Name.StartsWith(Prefix, StringComparison.Ordinal));
|
layers.RemoveAll(l => l.Name.StartsWith(Prefix, StringComparison.Ordinal));
|
||||||
|
PlayerHeads.Install();
|
||||||
|
|
||||||
layers.Insert(0, new LegacyGameInterfaceLayer(Prefix + "Under UI", DrawUnderUi, InterfaceScaleType.None));
|
layers.Insert(0, new LegacyGameInterfaceLayer(Prefix + "Under UI", DrawUnderUi, InterfaceScaleType.None));
|
||||||
|
|
||||||
@@ -45,7 +46,11 @@ public static class InterfaceLayers
|
|||||||
layers.Insert(mouseTextIndex < 0 ? layers.Count : mouseTextIndex, new LegacyGameInterfaceLayer(Prefix + "Windows", DrawWindows, InterfaceScaleType.UI));
|
layers.Insert(mouseTextIndex < 0 ? layers.Count : mouseTextIndex, new LegacyGameInterfaceLayer(Prefix + "Windows", DrawWindows, InterfaceScaleType.UI));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Uninstall() => Layers?.RemoveAll(l => l.Name.StartsWith(Prefix, StringComparison.Ordinal));
|
public static void Uninstall()
|
||||||
|
{
|
||||||
|
Layers?.RemoveAll(l => l.Name.StartsWith(Prefix, StringComparison.Ordinal));
|
||||||
|
PlayerHeads.Uninstall();
|
||||||
|
}
|
||||||
|
|
||||||
static bool DrawUnderUi()
|
static bool DrawUnderUi()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ public sealed partial class MainWindow
|
|||||||
Group("Stats");
|
Group("Stats");
|
||||||
if (Toggle("Stats Overlay", ref Settings.ShowStats) && Buttons("Configure Overlay") == 0)
|
if (Toggle("Stats Overlay", ref Settings.ShowStats) && Buttons("Configure Overlay") == 0)
|
||||||
WindowManager.StatsSetup.ShowBeside(Bounds);
|
WindowManager.StatsSetup.ShowBeside(Bounds);
|
||||||
|
|
||||||
|
Group("Party");
|
||||||
|
if (Toggle("Party Frames", ref Settings.PartyFrames))
|
||||||
|
Sub(() => Toggle("Include yourself", ref Settings.PartySelf));
|
||||||
|
if (Buttons(WindowManager.Players.Open ? "Close Player List" : "Player List") == 0)
|
||||||
|
WindowManager.Toggle(WindowManager.Players);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MovementPage()
|
void MovementPage()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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.",
|
||||||
|
["Party Frames"] = "Shows your teammates' health and mana on screen. Open the menu to move it.",
|
||||||
["Stats Overlay"] = "Shows the selected stats on screen. Damage, kills and deaths count from when you enter the world.",
|
["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.",
|
["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.",
|
||||||
|
|||||||
@@ -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, Settings.ShowStats)),
|
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, Settings.PartyFrames)),
|
||||||
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,189 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using Terraria;
|
||||||
|
using Terraria.ID;
|
||||||
|
using TerraSharp.Core;
|
||||||
|
using TerraSharp.Features;
|
||||||
|
|
||||||
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
|
// Teammate health (similar to what we have in MMOs)
|
||||||
|
public sealed class PartyFrames() : Window("party", "", new Vector2(20, 440), FrameWidth, RowHeight)
|
||||||
|
{
|
||||||
|
const int FrameWidth = 240, RowHeight = 40, IconSize = 16;
|
||||||
|
|
||||||
|
static readonly Color Life = new(220, 50, 50), Mana = new(70, 110, 235);
|
||||||
|
|
||||||
|
public override bool Open { get => Settings.PartyFrames; set => Settings.PartyFrames = value; }
|
||||||
|
|
||||||
|
public override bool Frameless => true;
|
||||||
|
|
||||||
|
// Bounds from the last draw; input is resolved before the body draws.
|
||||||
|
readonly List<Rectangle> _frames = [], _eyes = [];
|
||||||
|
|
||||||
|
public override bool Interactive => !Main.gameMenu && (WindowManager.Menu.Open || _frames.Exists(frame => frame.Contains(Widgets.Mouse)));
|
||||||
|
|
||||||
|
public override bool Draggable => WindowManager.Menu.Open && !_eyes.Exists(eye => eye.Contains(Widgets.Mouse));
|
||||||
|
|
||||||
|
public override void DrawBody(Rectangle body)
|
||||||
|
{
|
||||||
|
if (Main.gameMenu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_frames.Clear();
|
||||||
|
_eyes.Clear();
|
||||||
|
Player local = Main.LocalPlayer;
|
||||||
|
int y = body.Y;
|
||||||
|
if (Settings.PartySelf)
|
||||||
|
y += Frame(local, body.X, y, body.Width);
|
||||||
|
|
||||||
|
for (int i = 0; i < Main.maxPlayers && local.team > 0; i++)
|
||||||
|
if (i != Main.myPlayer && Main.player[i] is { active: true } player && player.team == local.team)
|
||||||
|
y += Frame(player, body.X, y, body.Width);
|
||||||
|
|
||||||
|
Height = Math.Max(RowHeight, y - body.Y);
|
||||||
|
if (!WindowManager.Menu.Open)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (y == body.Y)
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, "Party frames: no teammates", body, Widgets.Dim, 0.75f);
|
||||||
|
Render.Outline(Widgets.SpriteBatch, body, Widgets.Hot(body) ? Widgets.Yellow * 0.6f : Color.White * 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the height used, including the gap below.
|
||||||
|
int Frame(Player player, int x, int y, int width)
|
||||||
|
{
|
||||||
|
int debuffs = 0;
|
||||||
|
foreach (int t in player.buffType)
|
||||||
|
if (ShownDebuff(t))
|
||||||
|
debuffs++;
|
||||||
|
|
||||||
|
var frame = new Rectangle(x, y, width, RowHeight - 4 + (debuffs > 0 ? IconSize + 3 : 0));
|
||||||
|
_frames.Add(frame);
|
||||||
|
float fade = player.dead ? 0.5f : 1f;
|
||||||
|
Render.FillSoft(Widgets.SpriteBatch, frame, Color.Black * 0.45f);
|
||||||
|
if (Spectate.Target == player.whoAmI)
|
||||||
|
Render.Outline(Widgets.SpriteBatch, frame, Widgets.Yellow * 0.8f);
|
||||||
|
var eye = new Rectangle(frame.Right - 21, frame.Y + 18, IconSize, IconSize);
|
||||||
|
Actions(player, frame, eye);
|
||||||
|
|
||||||
|
PlayerHeads.Draw(player, new Vector2(frame.X + 18, frame.Y + (RowHeight - 4) / 2f), 0.8f);
|
||||||
|
if (!player.dead && !player.HeldItem.IsAir)
|
||||||
|
Render.Item(Widgets.SpriteBatch, player.HeldItem.type, new Rectangle(eye.X, frame.Y + 2, IconSize, IconSize));
|
||||||
|
|
||||||
|
int barX = frame.X + 38, barWidth = frame.Right - 26 - barX;
|
||||||
|
Eye(player, eye);
|
||||||
|
|
||||||
|
// The name line fills from the right: life, defense, potion sickness, then the name in what is left.
|
||||||
|
int right = barX + barWidth;
|
||||||
|
string life = player.dead ? DeathText(player) : $"{player.statLife}/{player.statLifeMax2}";
|
||||||
|
right = Stat(life, right, frame.Y, player.dead ? null : GameTextures.Get("Heart"), null, (player.dead ? Widgets.Red : Color.White) * fade, null);
|
||||||
|
if (!player.dead && GameTextures.Get("Extra", 58) is { } shield)
|
||||||
|
{
|
||||||
|
int defense = (int)player.statDefense;
|
||||||
|
right = Stat($"{defense}", right - 4, frame.Y, shield, GameTextures.Frame(shield, 3, 2, 0, 0), Color.White * fade, $"{defense} {Lang.inter[10].Value}");
|
||||||
|
}
|
||||||
|
if (player.FindBuffIndex(BuffID.PotionSickness) is var sickness and >= 0)
|
||||||
|
{
|
||||||
|
right -= IconSize + 4;
|
||||||
|
PotionSickness(player, sickness, new Rectangle(right, frame.Y + 3, IconSize, IconSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.Text(Widgets.SpriteBatch, Render.Ellipsize(player.name, 0.7f, right - barX - 6), new Vector2(barX, frame.Y + 2), Render.TeamColor(player) * fade, 0.7f);
|
||||||
|
|
||||||
|
Bar(new Rectangle(barX, frame.Y + 21, barWidth, 8), player.dead ? 0f : player.statLife / (float)Math.Max(1, player.statLifeMax2), Life);
|
||||||
|
Bar(new Rectangle(barX, frame.Y + 31, barWidth, 3), player.dead ? 0f : player.statMana / (float)Math.Max(1, player.statManaMax2), Mana);
|
||||||
|
|
||||||
|
if (debuffs > 0)
|
||||||
|
Debuffs(player, new Rectangle(barX, frame.Y + RowHeight - 4, barWidth, IconSize));
|
||||||
|
return frame.Height + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clicks act only while the menu is closed; with it open, a click drags the frames.
|
||||||
|
static void Actions(Player player, Rectangle frame, Rectangle eye)
|
||||||
|
{
|
||||||
|
bool other = player.whoAmI != Main.myPlayer && Spectate.Available;
|
||||||
|
if (!other || !Widgets.Hot(frame) || eye.Contains(Widgets.Mouse))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Widgets.Tooltip = WindowManager.Menu.Open ? "Right-click to teleport" : "Click to spectate, right-click to teleport";
|
||||||
|
if (Widgets.Click && !WindowManager.Menu.Open)
|
||||||
|
Spectate.Toggle(player.whoAmI);
|
||||||
|
if (Widgets.RightClick && !player.dead)
|
||||||
|
PlayerFeatures.TeleportToPlayer(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Eye(Player player, Rectangle bounds)
|
||||||
|
{
|
||||||
|
_eyes.Add(bounds);
|
||||||
|
bool hot = Widgets.Hot(bounds);
|
||||||
|
bool inspecting = WindowManager.Inspector.Open && WindowManager.Inspector.Target == player.whoAmI;
|
||||||
|
Widgets.Icon(GameTextures.Get(inspecting ? "InventoryTickOff" : "InventoryTickOn"), bounds, null, 1f, hot ? Widgets.Yellow : Color.White * 0.7f);
|
||||||
|
if (!hot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Widgets.Tooltip = inspecting ? "Close inspect" : "Inspect";
|
||||||
|
if (!Widgets.Click)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (inspecting)
|
||||||
|
WindowManager.Hide(WindowManager.Inspector);
|
||||||
|
else
|
||||||
|
WindowManager.Inspector.Inspect(player.whoAmI);
|
||||||
|
}
|
||||||
|
|
||||||
|
// respawnTimer only counts down where the game updates it; otherwise just "Dead".
|
||||||
|
static string DeathText(Player player) => player.respawnTimer > 0 ? $"Dead {(player.respawnTimer + 59) / 60}s" : "Dead";
|
||||||
|
|
||||||
|
// Draws an icon and value ending at right; returns where it starts.
|
||||||
|
static int Stat(string text, int right, int y, Texture2D? icon, Rectangle? frame, Color color, string? tooltip)
|
||||||
|
{
|
||||||
|
const int statIcon = 12;
|
||||||
|
int textWidth = (int)Render.Measure(text, 0.65f).X;
|
||||||
|
Render.Text(Widgets.SpriteBatch, text, new Vector2(right - textWidth, y + 3), color, 0.65f);
|
||||||
|
if (icon is null)
|
||||||
|
return right - textWidth;
|
||||||
|
|
||||||
|
var bounds = new Rectangle(right - textWidth - statIcon - 2, y + 4, statIcon, statIcon);
|
||||||
|
Widgets.Icon(icon, bounds, frame, 1f, color);
|
||||||
|
if (tooltip is not null && Widgets.Hot(new Rectangle(bounds.X, y + 2, right - bounds.X, IconSize)))
|
||||||
|
Widgets.Tooltip = tooltip;
|
||||||
|
return bounds.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PotionSickness(Player player, int index, Rectangle bounds)
|
||||||
|
{
|
||||||
|
Render.Item(Widgets.SpriteBatch, ItemID.HealingPotion, bounds, Color.White * 0.55f);
|
||||||
|
Render.Line(Widgets.SpriteBatch, new Vector2(bounds.X, bounds.Bottom), new Vector2(bounds.Right, bounds.Y), Widgets.Red, 2f);
|
||||||
|
if (Widgets.Hot(bounds))
|
||||||
|
Widgets.Tooltip = player.buffTime[index] > 0 ? $"{Lang.GetBuffName(BuffID.PotionSickness)}: {BuffBrowser.FormatTime(player.buffTime[index])}" : Lang.GetBuffName(BuffID.PotionSickness);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Potion sickness has its own icon next to the name.
|
||||||
|
static bool ShownDebuff(int type) => type > 0 && type != BuffID.PotionSickness && Main.debuff[type];
|
||||||
|
|
||||||
|
static void Debuffs(Player player, Rectangle row)
|
||||||
|
{
|
||||||
|
int x = row.X;
|
||||||
|
for (int i = 0; i < player.buffType.Length && x + IconSize <= row.Right; i++)
|
||||||
|
{
|
||||||
|
int type = player.buffType[i];
|
||||||
|
if (!ShownDebuff(type))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var bounds = new Rectangle(x, row.Y, IconSize, IconSize);
|
||||||
|
Widgets.Icon(GameTextures.Get("Buff", type), bounds);
|
||||||
|
if (Widgets.Hot(bounds))
|
||||||
|
Widgets.Tooltip = player.buffTime[i] > 0 ? $"{Lang.GetBuffName(type)}: {BuffBrowser.FormatTime(player.buffTime[i])}" : Lang.GetBuffName(type);
|
||||||
|
x += IconSize + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Bar(Rectangle bounds, float fraction, Color color)
|
||||||
|
{
|
||||||
|
Render.Fill(Widgets.SpriteBatch, bounds, Color.Black * 0.6f);
|
||||||
|
Render.Fill(Widgets.SpriteBatch, new Rectangle(bounds.X, bounds.Y, (int)(bounds.Width * MathHelper.Clamp(fraction, 0f, 1f)), bounds.Height), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using Terraria;
|
||||||
|
using Terraria.DataStructures;
|
||||||
|
using Terraria.GameContent;
|
||||||
|
using TerraSharp.Core;
|
||||||
|
|
||||||
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
|
// MapHeadRenderer with its own targets, so heads are always facing right (lol)
|
||||||
|
public sealed class PlayerHeads : INeedRenderTargetContent
|
||||||
|
{
|
||||||
|
static readonly PlayerHeads Instance = new();
|
||||||
|
|
||||||
|
readonly PlayerHeadDrawRenderTargetContent[] _heads = new PlayerHeadDrawRenderTargetContent[Main.maxPlayers];
|
||||||
|
bool _anyRequested;
|
||||||
|
|
||||||
|
PlayerHeads()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _heads.Length; i++)
|
||||||
|
_heads[i] = new PlayerHeadDrawRenderTargetContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Install()
|
||||||
|
{
|
||||||
|
Main.ContentThatNeedsRenderTargets.Remove(Instance);
|
||||||
|
Main.ContentThatNeedsRenderTargets.Add(Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Uninstall()
|
||||||
|
{
|
||||||
|
Main.ContentThatNeedsRenderTargets.Remove(Instance);
|
||||||
|
Instance.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requested every frame, appearance changes show on the next frame.
|
||||||
|
public static void Draw(Player player, Vector2 center, float scale)
|
||||||
|
{
|
||||||
|
PlayerHeadDrawRenderTargetContent head = Instance._heads[player.whoAmI];
|
||||||
|
head.UsePlayer(player);
|
||||||
|
head.UseColor(Main.GetPlayerHeadBordersColor(player));
|
||||||
|
head.Request();
|
||||||
|
Instance._anyRequested = true;
|
||||||
|
if (!head.IsReady)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RenderTarget2D target = head.GetTarget();
|
||||||
|
// DrawData constructors overload on ReLogic's Asset<T>, so set the fields directly.
|
||||||
|
var data = default(DrawData);
|
||||||
|
data.texture = target;
|
||||||
|
data.position = center;
|
||||||
|
data.sourceRect = target.Bounds;
|
||||||
|
data.color = Color.White;
|
||||||
|
data.origin = new Vector2(target.Width, target.Height) / 2f;
|
||||||
|
data.scale = new Vector2(scale);
|
||||||
|
PlayerDrawHelper.SetShaderForData(player, player.cHead, ref data);
|
||||||
|
data.Draw(Widgets.SpriteBatch);
|
||||||
|
Main.pixelShader.CurrentTechnique.Passes[0].Apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReady => !_anyRequested;
|
||||||
|
|
||||||
|
// PlayerDrawHeadSet.BoringSetup reads the direction while the target renders.
|
||||||
|
public void PrepareRenderTarget(GraphicsDevice device, SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (!_anyRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_anyRequested = false;
|
||||||
|
for (int i = 0; i < _heads.Length; i++)
|
||||||
|
{
|
||||||
|
Player player = Main.player[i];
|
||||||
|
int direction = player.direction;
|
||||||
|
player.direction = 1;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_heads[i].PrepareRenderTarget(device, spriteBatch);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error("Player head target", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
player.direction = direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_anyRequested = false;
|
||||||
|
foreach (PlayerHeadDrawRenderTargetContent head in _heads)
|
||||||
|
head.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Terraria;
|
||||||
|
using Terraria.UI;
|
||||||
|
|
||||||
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
|
// Read-only view of another player.
|
||||||
|
public sealed class PlayerInspector() : Window("inspect", "Inspect Player", new Vector2(700, 170), 470, 300)
|
||||||
|
{
|
||||||
|
enum Tab { Equipment, Inventory, Buffs }
|
||||||
|
|
||||||
|
const int SlotSize = 36, SlotStep = 40, Columns = 10, ContentTop = 72, FrameHeight = 62;
|
||||||
|
|
||||||
|
// Empty-slot icons are frames of TextureAssets.Extra[54], as ItemSlot.Draw picks them per context.
|
||||||
|
const int AccessoryIcon = 11, VanityAccessoryIcon = 2, DyeIcon = 1;
|
||||||
|
static readonly int[] ArmorIcons = [0, 6, 12], VanityArmorIcons = [3, 9, 15], MiscIcons = [4, 13, 7, 10, 17];
|
||||||
|
|
||||||
|
Tab _tab;
|
||||||
|
|
||||||
|
public int Target { get; private set; } = -1;
|
||||||
|
|
||||||
|
public void Inspect(int who)
|
||||||
|
{
|
||||||
|
Target = who;
|
||||||
|
WindowManager.Show(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawBody(Rectangle body)
|
||||||
|
{
|
||||||
|
if (Target < 0 || Main.player[Target] is not { active: true } player)
|
||||||
|
{
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, "Player left the world.", new Rectangle(body.X + 2, body.Y, body.Width, 26), Widgets.Dim, 0.85f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, Render.Ellipsize(player.name, 0.9f, body.Width), new Rectangle(body.X + 2, body.Y, body.Width, 26), Render.TeamColor(player), 0.9f);
|
||||||
|
|
||||||
|
// The held item sits right of the tabs, on the hotbar's selected slot background.
|
||||||
|
int tabWidth = (body.Width - 12 - SlotStep) / 3;
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
if (Widgets.Button(new Rectangle(body.X + i * (tabWidth + 6), body.Y + 32, tabWidth, 30), ((Tab)i).ToString(), _tab == (Tab)i, 0.82f))
|
||||||
|
_tab = (Tab)i;
|
||||||
|
Slot(player.HeldItem, body.Right - SlotSize, body.Y + 29, selected: true);
|
||||||
|
|
||||||
|
int x = body.X + (body.Width - (Columns * SlotStep - (SlotStep - SlotSize))) / 2, y = body.Y + ContentTop;
|
||||||
|
int height = _tab switch
|
||||||
|
{
|
||||||
|
Tab.Equipment => DrawEquipment(player, x, y),
|
||||||
|
Tab.Inventory => DrawInventory(player, x, y),
|
||||||
|
_ => DrawBuffs(player, x, y, body),
|
||||||
|
};
|
||||||
|
Height = FrameHeight + ContentTop + height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rows follow the vanilla equipment page: armor and accessories, vanity, dyes, then misc equips and their dyes.
|
||||||
|
static int DrawEquipment(Player player, int x, int y)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Columns; i++)
|
||||||
|
{
|
||||||
|
Slot(player.armor[i], x + i * SlotStep, y, "InventoryBack3", i < 3 ? ArmorIcons[i] : AccessoryIcon);
|
||||||
|
Slot(player.armor[i + Columns], x + i * SlotStep, y + SlotStep, "InventoryBack8", i < 3 ? VanityArmorIcons[i] : VanityAccessoryIcon);
|
||||||
|
Slot(player.dye[i], x + i * SlotStep, y + 2 * SlotStep, "InventoryBack12", DyeIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
int miscY = y + 3 * SlotStep + 8;
|
||||||
|
for (int i = 0; i < player.miscEquips.Length; i++)
|
||||||
|
{
|
||||||
|
Slot(player.miscEquips[i], x + i * SlotStep, miscY, "InventoryBack3", MiscIcons[i]);
|
||||||
|
Slot(player.miscDyes[i], x + (i + player.miscEquips.Length) * SlotStep, miscY, "InventoryBack12", DyeIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 4 * SlotStep + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slots 0-49 are the main inventory, 50-53 coins and 54-57 ammo.
|
||||||
|
static int DrawInventory(Player player, int x, int y)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 50; i++)
|
||||||
|
Slot(player.inventory[i], x + i % Columns * SlotStep, y + i / Columns * SlotStep, selected: i == player.selectedItem);
|
||||||
|
|
||||||
|
int extraY = y + 5 * SlotStep + 8;
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
Slot(player.inventory[50 + i], x + (i < 4 ? i : i + 2) * SlotStep, extraY);
|
||||||
|
|
||||||
|
return 6 * SlotStep + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int DrawBuffs(Player player, int x, int y, Rectangle body)
|
||||||
|
{
|
||||||
|
int shown = 0;
|
||||||
|
for (int i = 0; i < player.buffType.Length; i++)
|
||||||
|
{
|
||||||
|
int type = player.buffType[i];
|
||||||
|
if (type <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var bounds = new Rectangle(x + shown % Columns * SlotStep, y + shown / Columns * SlotStep, SlotSize, SlotSize);
|
||||||
|
bool hot = Widgets.Hot(bounds);
|
||||||
|
Widgets.Slot(bounds, hot);
|
||||||
|
Widgets.Icon(GameTextures.Get("Buff", type), new Rectangle(bounds.X + 4, bounds.Y + 4, 28, 28));
|
||||||
|
// Remote buff times are only known when the game syncs them.
|
||||||
|
if (player.buffTime[i] > 0 && !Main.buffNoTimeDisplay[type])
|
||||||
|
Render.TextCentered(Widgets.SpriteBatch, BuffBrowser.FormatTime(player.buffTime[i]), new Rectangle(bounds.X, bounds.Bottom - 12, bounds.Width, 14), Color.White, 0.6f);
|
||||||
|
if (hot)
|
||||||
|
Widgets.Tooltip = $"{Lang.GetBuffName(type)}\n{Lang.GetBuffDescription(type)}";
|
||||||
|
shown++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shown == 0)
|
||||||
|
{
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, "No active buffs", new Rectangle(body.X + 2, y, body.Width, SlotSize), Widgets.Dim * 0.8f, 0.8f);
|
||||||
|
return SlotStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (shown + Columns - 1) / Columns * SlotStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Slot(Item item, int x, int y, string background = "InventoryBack", int emptyIcon = -1, bool selected = false)
|
||||||
|
{
|
||||||
|
var bounds = new Rectangle(x, y, SlotSize, SlotSize);
|
||||||
|
bool hot = Widgets.Hot(bounds);
|
||||||
|
Widgets.Slot(bounds, hot, selected, background);
|
||||||
|
if (item.IsAir)
|
||||||
|
{
|
||||||
|
EmptyIcon(bounds, emptyIcon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.Item(Widgets.SpriteBatch, item.type, new Rectangle(bounds.X + 5, bounds.Y + 5, bounds.Width - 10, bounds.Height - 10));
|
||||||
|
if (item.stack > 1)
|
||||||
|
Render.Text(Widgets.SpriteBatch, item.stack.ToString(), new Vector2(bounds.X + 4, bounds.Bottom - 16), Color.White, 0.6f);
|
||||||
|
if (!hot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if TMODLOADER
|
||||||
|
ItemSlot.MouseHover(ref item, ItemSlot.Context.InventoryItem);
|
||||||
|
#else
|
||||||
|
ItemSlot.MouseHover(item, ItemSlot.Context.InventoryItem);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frames are square, so the row count follows from the sheet size; it differs between 1.4.4 and 1.4.5.
|
||||||
|
static void EmptyIcon(Rectangle bounds, int icon)
|
||||||
|
{
|
||||||
|
if (icon < 0 || GameTextures.Get("Extra", 54) is not { } sheet)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int size = sheet.Width / 3;
|
||||||
|
var frame = new Rectangle(icon % 3 * size, icon / 3 * size, size - 2, size - 2);
|
||||||
|
if (frame.Bottom > sheet.Height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Widgets.Icon(sheet, new Rectangle(bounds.X + 4, bounds.Y + 4, bounds.Width - 8, bounds.Height - 8), frame, 1f, Color.White * 0.35f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Terraria;
|
||||||
|
using TerraSharp.Features;
|
||||||
|
|
||||||
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
|
public sealed class PlayerList() : Window("players", "Players", new Vector2(640, 150), 600, 440)
|
||||||
|
{
|
||||||
|
const int RowHeight = 48, ButtonWidth = 84, ButtonGap = 6;
|
||||||
|
|
||||||
|
readonly Scroll _scroll = new();
|
||||||
|
|
||||||
|
public override void DrawBody(Rectangle body)
|
||||||
|
{
|
||||||
|
int online = 0;
|
||||||
|
for (int i = 0; i < Main.maxPlayers; i++)
|
||||||
|
if (Main.player[i].active)
|
||||||
|
online++;
|
||||||
|
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, $"{online} online", new Rectangle(body.X + 2, body.Y, body.Width, 26), Widgets.Dim, 0.82f);
|
||||||
|
|
||||||
|
var area = new Rectangle(body.X, body.Y + 32, body.Width - 22, body.Height - 32);
|
||||||
|
int y = _scroll.Begin(area), top = y;
|
||||||
|
for (int i = 0; i < Main.maxPlayers; i++)
|
||||||
|
{
|
||||||
|
if (!Main.player[i].active)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Row(Main.player[i], new Rectangle(area.X, y, area.Width, RowHeight - 4));
|
||||||
|
y += RowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scroll.End(area, y - top);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Row(Player player, Rectangle row)
|
||||||
|
{
|
||||||
|
Widgets.Panel(row, Widgets.InnerBg);
|
||||||
|
PlayerHeads.Draw(player, new Vector2(row.X + 24, row.Y + row.Height / 2f), 1f);
|
||||||
|
|
||||||
|
int textX = row.X + 50, buttonsX = row.Right - 3 * (ButtonWidth + ButtonGap);
|
||||||
|
bool local = player.whoAmI == Main.myPlayer;
|
||||||
|
string name = Render.Ellipsize(local ? player.name + " (you)" : player.name, 0.85f, buttonsX - textX - 8);
|
||||||
|
Render.Text(Widgets.SpriteBatch, name, new Vector2(textX, row.Y + 4), Render.TeamColor(player), 0.85f);
|
||||||
|
Render.Text(Widgets.SpriteBatch, Details(player, local), new Vector2(textX, row.Y + 24), player.dead ? Widgets.Red : Widgets.Dim, 0.72f);
|
||||||
|
|
||||||
|
var button = new Rectangle(buttonsX, row.Y + (row.Height - 28) / 2, ButtonWidth, 28);
|
||||||
|
if (!local && !player.dead && Spectate.Available && Widgets.Button(button, "Teleport", false, 0.78f))
|
||||||
|
PlayerFeatures.TeleportToPlayer(player);
|
||||||
|
|
||||||
|
button.X += ButtonWidth + ButtonGap;
|
||||||
|
if (!local && Spectate.Available && Widgets.Button(button, Spectate.Target == player.whoAmI ? "Stop" : "Spectate", Spectate.Target == player.whoAmI, 0.78f))
|
||||||
|
Spectate.Toggle(player.whoAmI);
|
||||||
|
|
||||||
|
button.X += ButtonWidth + ButtonGap;
|
||||||
|
bool inspecting = WindowManager.Inspector.Open && WindowManager.Inspector.Target == player.whoAmI;
|
||||||
|
if (Widgets.Button(button, "Inspect", inspecting, 0.78f))
|
||||||
|
WindowManager.Inspector.Inspect(player.whoAmI);
|
||||||
|
}
|
||||||
|
|
||||||
|
static string Details(Player player, bool local)
|
||||||
|
{
|
||||||
|
if (player.dead)
|
||||||
|
return "Dead";
|
||||||
|
|
||||||
|
string stats = $"Life {player.statLife}/{player.statLifeMax2} Mana {player.statMana}/{player.statManaMax2}";
|
||||||
|
return local ? stats : $"{stats} {(int)(Vector2.Distance(player.Center, Main.LocalPlayer.Center) / 16f)} tiles";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -142,5 +142,7 @@ public static class Render
|
|||||||
spriteBatch.Draw(texture, new Vector2(bounds.X + (bounds.Width - size.X) / 2f, bounds.Y + (bounds.Height - size.Y) / 2f), frame, color ?? Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f);
|
spriteBatch.Draw(texture, new Vector2(bounds.X + (bounds.Width - size.X) / 2f, bounds.Y + (bounds.Height - size.Y) / 2f), frame, color ?? Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Color TeamColor(Player player) => player.team > 0 ? Main.teamColor[player.team] : Color.White;
|
||||||
|
|
||||||
public static Vector2 ToScreen(Vector2 world) => Vector2.Transform(world - Main.screenPosition, Main.GameViewMatrix.TransformationMatrix);
|
public static Vector2 ToScreen(Vector2 world) => Vector2.Transform(world - Main.screenPosition, Main.GameViewMatrix.TransformationMatrix);
|
||||||
}
|
}
|
||||||
@@ -261,9 +261,9 @@ public static class Widgets
|
|||||||
Render.Fill(SpriteBatch, new Rectangle(bounds.X + 1, bounds.Y + 1, bounds.Width - 2, bounds.Height - 2), color);
|
Render.Fill(SpriteBatch, new Rectangle(bounds.X + 1, bounds.Y + 1, bounds.Width - 2, bounds.Height - 2), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Slot(Rectangle bounds, bool hot, bool selected = false)
|
public static void Slot(Rectangle bounds, bool hot, bool selected = false, string background = "InventoryBack")
|
||||||
{
|
{
|
||||||
if (GameTextures.Get(selected ? "InventoryBack14" : hot ? "InventoryBack15" : "InventoryBack") is { } back)
|
if (GameTextures.Get(selected ? "InventoryBack14" : hot ? "InventoryBack15" : background) is { } back)
|
||||||
SpriteBatch.Draw(back, bounds, SlotTint);
|
SpriteBatch.Draw(back, bounds, SlotTint);
|
||||||
else
|
else
|
||||||
Panel(bounds, ButtonBg);
|
Panel(bounds, ButtonBg);
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public abstract class Window(string id, string title, Vector2 position, int widt
|
|||||||
// A window that is not interactive lets the mouse through to the game.
|
// A window that is not interactive lets the mouse through to the game.
|
||||||
public virtual bool Interactive => true;
|
public virtual bool Interactive => true;
|
||||||
|
|
||||||
|
// Whether a click on a frameless window starts dragging it.
|
||||||
|
public virtual bool Draggable => 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);
|
||||||
|
|
||||||
public abstract void DrawBody(Rectangle body);
|
public abstract void DrawBody(Rectangle body);
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ public static class WindowManager
|
|||||||
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 StatsWindow Stats = new();
|
||||||
public static readonly StatsConfig StatsSetup = new();
|
public static readonly StatsConfig StatsSetup = new();
|
||||||
|
public static readonly PlayerList Players = new();
|
||||||
|
public static readonly PlayerInspector Inspector = new();
|
||||||
|
public static readonly PartyFrames Party = 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, Stats, Menu, StatsSetup];
|
static readonly List<Window> Stack = [Party, Items, Buffs, Npcs, Editor, SellList, ChestList, Stats, Players, Inspector, Menu, StatsSetup];
|
||||||
static Window? _dragging;
|
static Window? _dragging;
|
||||||
static Vector2 _dragOffset;
|
static Vector2 _dragOffset;
|
||||||
|
|
||||||
@@ -169,7 +172,7 @@ public static class WindowManager
|
|||||||
Rectangle bounds = window.Bounds;
|
Rectangle bounds = window.Bounds;
|
||||||
if (window.Frameless)
|
if (window.Frameless)
|
||||||
{
|
{
|
||||||
bool hot = Widgets.Hot(bounds);
|
bool hot = Widgets.Hot(bounds) && window.Draggable;
|
||||||
if (hot)
|
if (hot)
|
||||||
Widgets.Tooltip = "Drag to move";
|
Widgets.Tooltip = "Drag to move";
|
||||||
if (hot && Widgets.Click)
|
if (hot && Widgets.Click)
|
||||||
|
|||||||
Reference in new issue
Block a user