feat: Add item editor with prefix picker
This commit is contained in:
1 parent
73998f1aaa
commit
8d6296676a
7 files changed
+282
-3
No files matched your search
@@ -29,6 +29,7 @@ public static class Hotkeys
|
|||||||
new(InterfaceGroup, "ItemBrowser", "Item Browser", () => WindowManager.Toggle(WindowManager.Items)),
|
new(InterfaceGroup, "ItemBrowser", "Item Browser", () => WindowManager.Toggle(WindowManager.Items)),
|
||||||
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)),
|
||||||
|
|
||||||
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),
|
||||||
@@ -62,6 +63,8 @@ public static class Hotkeys
|
|||||||
Toggle("Items", "ItemVacuum", "Item Vacuum", () => Settings.ItemVacuum, v => Settings.ItemVacuum = v),
|
Toggle("Items", "ItemVacuum", "Item Vacuum", () => Settings.ItemVacuum, v => Settings.ItemVacuum = v),
|
||||||
Toggle("Items", "InstantBreak", "Instant Break", () => Settings.InstantBreak, v => Settings.InstantBreak = v),
|
Toggle("Items", "InstantBreak", "Instant Break", () => Settings.InstantBreak, v => Settings.InstantBreak = v),
|
||||||
Toggle("Items", "InfItems", "Infinite Items", () => Settings.InfItems, v => Settings.InfItems = v),
|
Toggle("Items", "InfItems", "Infinite Items", () => Settings.InfItems, v => Settings.InfItems = v),
|
||||||
|
new("Items", "MaxStack", "Max Stack", ItemFeatures.MaxStack),
|
||||||
|
new("Items", "Duplicate", "Duplicate Item", ItemFeatures.Duplicate),
|
||||||
|
|
||||||
Toggle("Visuals", "Esp", "ESP", () => Settings.Esp, v => Settings.Esp = v),
|
Toggle("Visuals", "Esp", "ESP", () => Settings.Esp, v => Settings.Esp = v),
|
||||||
Toggle("Visuals", "Spelunker", "Spelunker Vision", () => Settings.Spelunker, v => Settings.Spelunker = v),
|
Toggle("Visuals", "Spelunker", "Spelunker Vision", () => Settings.Spelunker, v => Settings.Spelunker = v),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Terraria;
|
using Terraria;
|
||||||
|
using TerraSharp.UI;
|
||||||
|
|
||||||
namespace TerraSharp.Features;
|
namespace TerraSharp.Features;
|
||||||
|
|
||||||
@@ -24,4 +25,38 @@ public static class ItemFeatures
|
|||||||
drop.velocity = Vector2.Zero;
|
drop.velocity = Vector2.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int InventorySlots = 50;
|
||||||
|
|
||||||
|
// The cursor item, or the held item when the cursor is empty.
|
||||||
|
public static Item EditTarget => Main.mouseItem.type != 0 ? Main.mouseItem : Main.LocalPlayer.inventory[Main.LocalPlayer.selectedItem];
|
||||||
|
|
||||||
|
public static void MaxStack()
|
||||||
|
{
|
||||||
|
Item item = EditTarget;
|
||||||
|
if (item.type == 0 || item.stack >= item.maxStack)
|
||||||
|
return;
|
||||||
|
|
||||||
|
item.stack = item.maxStack;
|
||||||
|
Widgets.Sound(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Duplicate()
|
||||||
|
{
|
||||||
|
Item item = EditTarget;
|
||||||
|
Item[] inventory = Main.LocalPlayer.inventory;
|
||||||
|
if (item.type == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < InventorySlots; i++)
|
||||||
|
{
|
||||||
|
if (inventory[i].type != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
inventory[i] = item.Clone();
|
||||||
|
inventory[i].favorited = false;
|
||||||
|
Widgets.Sound(7);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Terraria;
|
||||||
|
using Terraria.ID;
|
||||||
|
#if TMODLOADER
|
||||||
|
using Terraria.ModLoader;
|
||||||
|
#endif
|
||||||
|
using TerraSharp.Features;
|
||||||
|
|
||||||
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
|
// Edits the cursor item, or the held item when the cursor is empty. Only type, stack and prefix survive a save.
|
||||||
|
public sealed class ItemEditor() : Window("itemEditor", "Item Editor", new Vector2(700, 100), 480, 600)
|
||||||
|
{
|
||||||
|
const int RowHeight = 30;
|
||||||
|
|
||||||
|
readonly Scroll _scroll = new();
|
||||||
|
readonly Item _reference = new();
|
||||||
|
(int Type, int Prefix) _referenceKey = (-1, -1);
|
||||||
|
int _x, _y, _width;
|
||||||
|
bool _firstGroup;
|
||||||
|
|
||||||
|
public override void OnOpen() => Main.playerInventory = true;
|
||||||
|
|
||||||
|
public override void DrawBody(Rectangle body)
|
||||||
|
{
|
||||||
|
if (Main.gameMenu)
|
||||||
|
{
|
||||||
|
Render.TextCentered(Widgets.SpriteBatch, "Enter a world to use this.", body, Widgets.Dim, 0.9f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shows and edits base stats instead of the Damage Multiplier, Rapid Attack and Instant Break overrides.
|
||||||
|
HeldItemOverrides.Restore();
|
||||||
|
Item item = ItemFeatures.EditTarget;
|
||||||
|
DrawHeader(body, item);
|
||||||
|
|
||||||
|
int top = body.Y + 60;
|
||||||
|
var inner = new Rectangle(body.X, top, body.Width, body.Bottom - top);
|
||||||
|
Widgets.Panel(inner, Widgets.InnerBg);
|
||||||
|
if (item.type == 0)
|
||||||
|
{
|
||||||
|
Render.TextCentered(Widgets.SpriteBatch, "Hold an item or pick one up with the cursor.", inner, Widgets.Dim, 0.8f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var area = new Rectangle(inner.X + 12, inner.Y + 8, inner.Width - 38, inner.Height - 16);
|
||||||
|
_y = _scroll.Begin(area);
|
||||||
|
int start = _y;
|
||||||
|
(_x, _width, _firstGroup) = (area.X, area.Width, true);
|
||||||
|
Reference(item);
|
||||||
|
DrawActions(item);
|
||||||
|
DrawPrefixes(item);
|
||||||
|
DrawStats(item);
|
||||||
|
_scroll.End(area, _y - start + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawHeader(Rectangle body, Item item)
|
||||||
|
{
|
||||||
|
var slot = new Rectangle(body.X, body.Y, 52, 52);
|
||||||
|
Widgets.Slot(slot, false);
|
||||||
|
Render.Item(Widgets.SpriteBatch, item.type, new Rectangle(slot.X + 8, slot.Y + 8, slot.Width - 16, slot.Height - 16));
|
||||||
|
if (item.stack > 1)
|
||||||
|
Render.Text(Widgets.SpriteBatch, item.stack.ToString(), new Vector2(slot.X + 8, slot.Bottom - 20), Color.White, 0.7f);
|
||||||
|
|
||||||
|
string source = Main.mouseItem.type != 0 ? "Cursor item" : "Held item";
|
||||||
|
Render.Text(Widgets.SpriteBatch, item.type == 0 ? "No item" : item.AffixName(), new Vector2(slot.Right + 12, body.Y + 4), item.type == 0 ? Widgets.Dim : Color.White, 0.95f);
|
||||||
|
Render.Text(Widgets.SpriteBatch, item.type == 0 ? source : $"{source} #{item.type}", new Vector2(slot.Right + 12, body.Y + 30), Widgets.Dim, 0.72f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defaults for the current type and prefix; right-click on a stat resets to these.
|
||||||
|
void Reference(Item item)
|
||||||
|
{
|
||||||
|
if (_referenceKey == (item.type, item.prefix))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_reference.SetDefaults(item.type);
|
||||||
|
if (item.prefix > 0)
|
||||||
|
_reference.Prefix(item.prefix);
|
||||||
|
_referenceKey = (item.type, item.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Group(string title)
|
||||||
|
{
|
||||||
|
if (!_firstGroup)
|
||||||
|
_y += 8;
|
||||||
|
_firstGroup = false;
|
||||||
|
Widgets.Text(title, new Vector2(_x, _y + 2), Widgets.Header, 0.88f);
|
||||||
|
float width = Render.Measure(title, 0.88f).X;
|
||||||
|
Widgets.Separator(_x + (int)width + 12, _y + 12, _width - (int)width - 12);
|
||||||
|
_y += 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawActions(Item item)
|
||||||
|
{
|
||||||
|
Group("Actions");
|
||||||
|
string[] labels = ["Max Stack", "Duplicate", "Reset Stats"];
|
||||||
|
int width = (_width - 12) / labels.Length;
|
||||||
|
for (int i = 0; i < labels.Length; i++)
|
||||||
|
{
|
||||||
|
if (!Widgets.Button(new Rectangle(_x + i * (width + 6), _y + 2, width, 30), labels[i], false, 0.8f))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0: ItemFeatures.MaxStack(); break;
|
||||||
|
case 1: ItemFeatures.Duplicate(); break;
|
||||||
|
case 2: ResetStats(item); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_y += 38;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetStats(Item item)
|
||||||
|
{
|
||||||
|
int stack = item.stack, prefix = item.prefix;
|
||||||
|
bool favorited = item.favorited;
|
||||||
|
item.SetDefaults(item.type);
|
||||||
|
if (prefix > 0)
|
||||||
|
item.Prefix(prefix);
|
||||||
|
(item.stack, item.favorited) = (stack, favorited);
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<int> Prefixes(Item item)
|
||||||
|
{
|
||||||
|
#if TMODLOADER
|
||||||
|
return Enumerable.Range(1, PrefixLoader.PrefixCount - 1).Where(item.CanApplyPrefix);
|
||||||
|
#else
|
||||||
|
return item.GetRollablePrefixes() ?? [];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static string PrefixName(int prefix)
|
||||||
|
{
|
||||||
|
#if TMODLOADER
|
||||||
|
if (prefix >= PrefixID.Count)
|
||||||
|
return PrefixLoader.GetPrefix(prefix)?.DisplayName.Value ?? $"Prefix {prefix}";
|
||||||
|
#endif
|
||||||
|
return Lang.prefix[prefix].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawPrefixes(Item item)
|
||||||
|
{
|
||||||
|
int[] prefixes = [.. Prefixes(item).OrderBy(PrefixName, StringComparer.OrdinalIgnoreCase)];
|
||||||
|
if (prefixes.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Group("Prefix");
|
||||||
|
const int Columns = 3, Gap = 6;
|
||||||
|
int width = (_width - Gap * (Columns - 1)) / Columns;
|
||||||
|
int[] options = [0, .. prefixes];
|
||||||
|
for (int i = 0; i < options.Length; i++)
|
||||||
|
{
|
||||||
|
var bounds = new Rectangle(_x + i % Columns * (width + Gap), _y + i / Columns * 34, width, 30);
|
||||||
|
if (Widgets.Button(bounds, options[i] == 0 ? "None" : PrefixName(options[i]), item.prefix == options[i], 0.76f) && item.prefix != options[i])
|
||||||
|
ApplyPrefix(item, options[i]);
|
||||||
|
}
|
||||||
|
_y += (options.Length + Columns - 1) / Columns * 34 + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ApplyPrefix(Item item, int prefix)
|
||||||
|
{
|
||||||
|
int stack = item.stack;
|
||||||
|
bool favorited = item.favorited;
|
||||||
|
item.SetDefaults(item.type);
|
||||||
|
item.Prefix(prefix);
|
||||||
|
(item.stack, item.favorited) = (stack, favorited);
|
||||||
|
Widgets.Sound(2, -1, -1, 37);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawStats(Item item)
|
||||||
|
{
|
||||||
|
Group("Stats");
|
||||||
|
bool weapon = _reference.damage > 0;
|
||||||
|
if (weapon)
|
||||||
|
{
|
||||||
|
item.damage = IntRow("Damage", item.damage, 0, Math.Max(200, _reference.damage * 10), _reference.damage);
|
||||||
|
item.crit = IntRow("Critical chance", item.crit, 0, 100, _reference.crit, "{0}%");
|
||||||
|
item.knockBack = FloatRow("Knockback", item.knockBack, 0f, 20f, _reference.knockBack, "{0:0.0}");
|
||||||
|
item.scale = FloatRow("Size", item.scale, 0.25f, 5f, _reference.scale, "{0:0.00}x");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_reference.useStyle != 0)
|
||||||
|
{
|
||||||
|
item.useTime = IntRow("Use time", item.useTime, 1, Math.Max(60, _reference.useTime), _reference.useTime, "{0} ticks");
|
||||||
|
item.useAnimation = IntRow("Use animation", item.useAnimation, 1, Math.Max(60, _reference.useAnimation), _reference.useAnimation, "{0} ticks");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_reference.shoot > 0)
|
||||||
|
item.shootSpeed = FloatRow("Shot speed", item.shootSpeed, 0f, 40f, _reference.shootSpeed, "{0:0.0}");
|
||||||
|
if (_reference.defense > 0 || _reference.accessory || _reference.headSlot >= 0 || _reference.bodySlot >= 0 || _reference.legSlot >= 0)
|
||||||
|
item.defense = IntRow("Defense", item.defense, 0, Math.Max(100, _reference.defense * 10), _reference.defense);
|
||||||
|
if (item.maxStack > 1)
|
||||||
|
item.stack = IntRow("Stack", item.stack, 1, item.maxStack, item.maxStack);
|
||||||
|
if (_reference.useStyle != 0)
|
||||||
|
item.autoReuse = BoolRow("Auto reuse", item.autoReuse);
|
||||||
|
}
|
||||||
|
|
||||||
|
float FloatRow(string label, float value, float minimum, float maximum, float fallback, string format)
|
||||||
|
{
|
||||||
|
var row = new Rectangle(_x, _y, _width, RowHeight - 2);
|
||||||
|
string id = "itemEditor|" + label;
|
||||||
|
bool hot = Widgets.Hot(row) || Widgets.ActiveSlider == id;
|
||||||
|
if (hot)
|
||||||
|
Widgets.Tooltip = "Drag to change. Right-click to reset.";
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, $"{label}: {string.Format(format, value)}", row, hot ? Widgets.Yellow : Widgets.Dim, 0.85f);
|
||||||
|
float result = Widgets.ValueBar(id, row, MathHelper.Clamp(value, minimum, maximum), minimum, maximum, fallback);
|
||||||
|
_y += RowHeight;
|
||||||
|
return Widgets.ActiveSlider == id || result != MathHelper.Clamp(value, minimum, maximum) ? result : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int IntRow(string label, int value, int minimum, int maximum, int fallback, string format = "{0}") => (int)Math.Round(FloatRow(label, value, minimum, maximum, fallback, format.Replace("{0}", "{0:0}")));
|
||||||
|
|
||||||
|
bool BoolRow(string label, bool value)
|
||||||
|
{
|
||||||
|
value = Widgets.ToggleRow(new Rectangle(_x, _y, _width, 28), label, value);
|
||||||
|
_y += RowHeight;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,8 +140,11 @@ public sealed partial class MainWindow
|
|||||||
Toggle("Sonar", ref Settings.Sonar);
|
Toggle("Sonar", ref Settings.Sonar);
|
||||||
|
|
||||||
Group("Actions");
|
Group("Actions");
|
||||||
if (Buttons(WindowManager.Items.Open ? "Close Item Browser" : "Item Browser") == 0)
|
switch (Buttons(WindowManager.Items.Open ? "Close Item Browser" : "Item Browser", WindowManager.Editor.Open ? "Close Item Editor" : "Item Editor"))
|
||||||
WindowManager.Toggle(WindowManager.Items);
|
{
|
||||||
|
case 0: WindowManager.Toggle(WindowManager.Items); break;
|
||||||
|
case 1: WindowManager.Toggle(WindowManager.Editor); break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VisualsPage()
|
void VisualsPage()
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public sealed partial class MainWindow
|
|||||||
["Infinite Items"] = "Keeps inventory stacks from shrinking.",
|
["Infinite Items"] = "Keeps inventory stacks from shrinking.",
|
||||||
["Item Vacuum"] = "Pulls all dropped items to you.",
|
["Item Vacuum"] = "Pulls all dropped items to you.",
|
||||||
["Bank Buttons"] = "Piggy Bank, Safe, Defender's Forge and Void Vault buttons left of the inventory.",
|
["Bank Buttons"] = "Piggy Bank, Safe, Defender's Forge and Void Vault buttons left of the inventory.",
|
||||||
|
["Item Editor"] = "Change the prefix, stats and stack of the held or cursor item.",
|
||||||
["Auto Fishing"] = "Reels in on a bite while you hold a fishing pole.",
|
["Auto Fishing"] = "Reels in on a bite while you hold a fishing pole.",
|
||||||
["Recast"] = "Casts again where you last cast by hand.",
|
["Recast"] = "Casts again where you last cast by hand.",
|
||||||
["Catch enemies"] = "Reels in enemy catches too.",
|
["Catch enemies"] = "Reels in enemy catches too.",
|
||||||
|
|||||||
@@ -227,6 +227,20 @@ public static class Widgets
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool ToggleRow(Rectangle row, string label, bool value)
|
||||||
|
{
|
||||||
|
bool hot = Hot(row);
|
||||||
|
if (hot && Click)
|
||||||
|
{
|
||||||
|
value = !value;
|
||||||
|
Sound(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.TextMid(SpriteBatch, label, row, hot ? Yellow : value ? On : Color.White, 0.85f);
|
||||||
|
ToggleIcon(new Rectangle(row.Right - 28, row.Y + 2, 24, 24), value, hot);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public static void ToggleIcon(Rectangle bounds, bool on, bool hot)
|
public static void ToggleIcon(Rectangle bounds, bool on, bool hot)
|
||||||
{
|
{
|
||||||
Render.TextRight(SpriteBatch, on ? "On" : "Off", new Rectangle(bounds.X - 44, bounds.Y, 38, bounds.Height), on ? On : Dim * 0.75f, 0.78f);
|
Render.TextRight(SpriteBatch, on ? "On" : "Off", new Rectangle(bounds.X - 44, bounds.Y, 38, bounds.Height), on ? On : Dim * 0.75f, 0.78f);
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ public static class WindowManager
|
|||||||
public static readonly ItemBrowser Items = new();
|
public static readonly ItemBrowser Items = new();
|
||||||
public static readonly BuffBrowser Buffs = new();
|
public static readonly BuffBrowser Buffs = new();
|
||||||
public static readonly NpcBrowser Npcs = new();
|
public static readonly NpcBrowser Npcs = new();
|
||||||
|
public static readonly ItemEditor Editor = 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, Menu];
|
static readonly List<Window> Stack = [Items, Buffs, Npcs, Editor, Menu];
|
||||||
static Window? _dragging;
|
static Window? _dragging;
|
||||||
static Vector2 _dragOffset;
|
static Vector2 _dragOffset;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user