feat: Add mod filter, sort menu and category counts to browsers
This commit is contained in:
1 parent
9de8112ce9
commit
aae755913d
7 files changed
+248
-26
No files matched your search
@@ -11,7 +11,7 @@ using TerraSharp.Features;
|
|||||||
|
|
||||||
namespace TerraSharp.UI;
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
public sealed class BuffBrowser() : GridBrowser("buffs", "Buff Browser", new Vector2(660, 160), 480, 540, "Search buffs by name or ID", "buffs", new IdIndex(() => BuffCount, Lang.GetBuffName), Categories)
|
public sealed class BuffBrowser() : GridBrowser("buffs", "Buff Browser", new Vector2(660, 160), 480, 574, "Search buffs by name or ID", "buffs", new IdIndex(() => BuffCount, Lang.GetBuffName), Categories, [])
|
||||||
{
|
{
|
||||||
const string DurationSlider = "buffDuration";
|
const string DurationSlider = "buffDuration";
|
||||||
|
|
||||||
@@ -45,6 +45,10 @@ 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
|
||||||
|
|
||||||
|
#if TMODLOADER
|
||||||
|
protected override string? ModOf(int type) => BuffLoader.GetBuff(type)?.Mod.DisplayNameClean;
|
||||||
|
#endif
|
||||||
|
|
||||||
public static string FormatTime(int ticks)
|
public static string FormatTime(int ticks)
|
||||||
{
|
{
|
||||||
int seconds = ticks / 60;
|
int seconds = ticks / 60;
|
||||||
|
|||||||
@@ -10,19 +10,50 @@ namespace TerraSharp.UI;
|
|||||||
// Icon is a frame of Images/UI/Creative/Infinite_Icons; -1 draws the name instead.
|
// Icon is a frame of Images/UI/Creative/Infinite_Icons; -1 draws the name instead.
|
||||||
public sealed record BrowserCategory(string Name, Func<int, bool> Fits, int Icon = -1, BrowserCategory[]? Subcategories = null);
|
public sealed record BrowserCategory(string Name, Func<int, bool> Fits, int Icon = -1, BrowserCategory[]? Subcategories = null);
|
||||||
|
|
||||||
public abstract class GridBrowser(string id, string title, Vector2 position, int width, int height, string placeholder, string noun, IdIndex index, BrowserCategory[] categories)
|
// A null order keeps ID order, with name-prefix matches first while searching.
|
||||||
|
public sealed record BrowserSort(string Name, Comparison<int>? Order);
|
||||||
|
|
||||||
|
public abstract class GridBrowser(string id, string title, Vector2 position, int width, int height, string placeholder, string noun, IdIndex index, BrowserCategory[] categories, BrowserSort[] sorts)
|
||||||
: Window(id, title, position, width, height)
|
: Window(id, title, position, width, height)
|
||||||
{
|
{
|
||||||
const int SlotSize = 44, Gap = 4, Step = SlotSize + Gap, TabHeight = 32, SubTabHeight = 24, IconTabWidth = 38;
|
enum Menu { None, Mod, Sort }
|
||||||
|
|
||||||
|
const int SlotSize = 44, Gap = 4, Step = SlotSize + Gap, TabHeight = 32, SubTabHeight = 24, IconTabWidth = 38, ToolbarHeight = 28, MenuRowHeight = 26, MenuRows = 10;
|
||||||
|
const float MenuScale = 0.78f;
|
||||||
|
|
||||||
readonly TextField _search = new(placeholder);
|
readonly TextField _search = new(placeholder);
|
||||||
readonly Scroll _scroll = new();
|
readonly Scroll _scroll = new();
|
||||||
|
readonly Scroll _menuScroll = new();
|
||||||
string _lastQuery = "";
|
string _lastQuery = "";
|
||||||
int _category, _subcategory;
|
int _category, _subcategory, _mod, _sort;
|
||||||
readonly BrowserCategory[]?[] _subTabs = categories.Select(category => category.Subcategories?.Prepend(new BrowserCategory("All", category.Fits)).ToArray()).ToArray();
|
readonly BrowserCategory[]?[] _subTabs = categories.Select(category => category.Subcategories?.Prepend(new BrowserCategory("All", category.Fits)).ToArray()).ToArray();
|
||||||
|
readonly BrowserSort[] _sorts = [new("ID", null), new("Name", (a, b) => string.Compare(index.Name(a), index.Name(b), StringComparison.OrdinalIgnoreCase)), .. sorts];
|
||||||
|
|
||||||
|
// Mod display name per id, null for vanilla.
|
||||||
|
string?[]? _sources;
|
||||||
|
(string? Source, string Name, int Count)[] _mods = [];
|
||||||
|
|
||||||
|
Func<int, bool>? _fits;
|
||||||
|
(int Category, int Subcategory, int Mod) _fitsKey;
|
||||||
|
readonly Dictionary<BrowserCategory, int> _counts = [];
|
||||||
|
(int Version, int Mod) _countsKey = (-1, -1);
|
||||||
|
|
||||||
|
Menu _menu;
|
||||||
|
Rectangle _menuAnchor;
|
||||||
|
bool _menuOpened;
|
||||||
|
|
||||||
protected IdIndex Index { get; } = index;
|
protected IdIndex Index { get; } = index;
|
||||||
|
|
||||||
|
public override bool Open
|
||||||
|
{
|
||||||
|
get => base.Open;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Open = value;
|
||||||
|
_menu = Menu.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public sealed override void DrawBody(Rectangle body)
|
public sealed override void DrawBody(Rectangle body)
|
||||||
{
|
{
|
||||||
if (Main.gameMenu)
|
if (Main.gameMenu)
|
||||||
@@ -30,31 +61,170 @@ public abstract class GridBrowser(string id, string title, Vector2 position, int
|
|||||||
Render.TextCentered(Widgets.SpriteBatch, "Enter a world to use this.", body, Widgets.Dim, 0.9f);
|
Render.TextCentered(Widgets.SpriteBatch, "Enter a world to use this.", body, Widgets.Dim, 0.9f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An open menu takes all input until it closes.
|
||||||
|
bool blocked = Widgets.Blocked;
|
||||||
|
if (_menu != Menu.None)
|
||||||
|
Widgets.Blocked = true;
|
||||||
DrawContent(body);
|
DrawContent(body);
|
||||||
|
Widgets.Blocked = blocked;
|
||||||
|
if (_menu != Menu.None)
|
||||||
|
DrawMenu(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void DrawContent(Rectangle body);
|
protected abstract void DrawContent(Rectangle body);
|
||||||
|
|
||||||
protected abstract void DrawSlot(int id, Rectangle bounds, bool hot);
|
protected abstract void DrawSlot(int id, Rectangle bounds, bool hot);
|
||||||
|
|
||||||
|
protected virtual string? ModOf(int id) => null;
|
||||||
|
|
||||||
protected void DrawSearchGrid(Rectangle body, int top, int bottom)
|
protected void DrawSearchGrid(Rectangle body, int top, int bottom)
|
||||||
{
|
{
|
||||||
var field = new Rectangle(body.X, top, body.Width - 110, 36);
|
LoadSources();
|
||||||
Widgets.Field(_search, field);
|
Widgets.Field(_search, new Rectangle(body.X, top, body.Width, 36));
|
||||||
int gridTop = DrawCategories(body, top + 42);
|
int toolbarY = top + 42;
|
||||||
Index.Filter(_search.Text, _subTabs[_category] is { } subTabs ? subTabs[_subcategory].Fits : categories[_category].Fits);
|
int gridTop = DrawCategories(body, DrawToolbar(body, toolbarY));
|
||||||
|
Index.Filter(_search.Text, CurrentFits(), _sorts[_sort].Order);
|
||||||
if (_search.Text != _lastQuery)
|
if (_search.Text != _lastQuery)
|
||||||
{
|
{
|
||||||
_lastQuery = _search.Text;
|
_lastQuery = _search.Text;
|
||||||
_scroll.Reset();
|
_scroll.Reset();
|
||||||
}
|
}
|
||||||
Render.TextRight(Widgets.SpriteBatch, $"{Index.Ids.Count} {noun}", new Rectangle(field.Right, top, 106, 36), Widgets.Dim, 0.8f);
|
Render.TextRight(Widgets.SpriteBatch, $"{Index.Ids.Count} {noun}", new Rectangle(body.X, toolbarY, body.Width - 2, ToolbarHeight), Widgets.Dim, 0.8f);
|
||||||
|
|
||||||
var inner = new Rectangle(body.X, gridTop, body.Width, bottom - gridTop);
|
var inner = new Rectangle(body.X, gridTop, body.Width, bottom - gridTop);
|
||||||
Widgets.Panel(inner, Widgets.InnerBg);
|
Widgets.Panel(inner, Widgets.InnerBg);
|
||||||
DrawGrid(Index.Ids, new Rectangle(inner.X + 10, inner.Y + 10, inner.Width - 34, inner.Height - 20));
|
DrawGrid(Index.Ids, new Rectangle(inner.X + 10, inner.Y + 10, inner.Width - 34, inner.Height - 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoadSources()
|
||||||
|
{
|
||||||
|
if (_sources is not null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int[] ids = [.. Index.All()];
|
||||||
|
_sources = new string?[ids.Length == 0 ? 0 : ids[ids.Length - 1] + 1];
|
||||||
|
Dictionary<string, int> counts = [];
|
||||||
|
foreach (int id in ids)
|
||||||
|
if (ModOf(id) is { } mod)
|
||||||
|
{
|
||||||
|
_sources[id] = mod;
|
||||||
|
counts[mod] = counts.TryGetValue(mod, out int count) ? count + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counts.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_mods = [(null, "All mods", ids.Length), (null, "Terraria", ids.Length - counts.Values.Sum()), .. counts.OrderBy(pair => pair.Key, StringComparer.OrdinalIgnoreCase).Select(pair => ((string?)pair.Key, pair.Key, pair.Value))];
|
||||||
|
}
|
||||||
|
|
||||||
|
string? Source(int id) => id < _sources!.Length ? _sources[id] : null;
|
||||||
|
|
||||||
|
Func<int, bool> WithMod(Func<int, bool> fits)
|
||||||
|
{
|
||||||
|
if (_mod == 0)
|
||||||
|
return fits;
|
||||||
|
|
||||||
|
string? source = _mods[_mod].Source;
|
||||||
|
return id => Source(id) == source && fits(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdIndex.Filter compares delegates by reference, so reuse one per selection.
|
||||||
|
Func<int, bool> CurrentFits()
|
||||||
|
{
|
||||||
|
if (_fits is not null && _fitsKey == (_category, _subcategory, _mod))
|
||||||
|
return _fits;
|
||||||
|
|
||||||
|
_fitsKey = (_category, _subcategory, _mod);
|
||||||
|
BrowserCategory selected = _subTabs[_category] is { } subTabs ? subTabs[_subcategory] : categories[_category];
|
||||||
|
return _fits = WithMod(selected.Fits);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CountOf(BrowserCategory tab)
|
||||||
|
{
|
||||||
|
if (_countsKey != (Index.Version, _mod))
|
||||||
|
{
|
||||||
|
_counts.Clear();
|
||||||
|
_countsKey = (Index.Version, _mod);
|
||||||
|
}
|
||||||
|
if (!_counts.TryGetValue(tab, out int count))
|
||||||
|
_counts[tab] = count = Index.Count(WithMod(tab.Fits));
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DrawToolbar(Rectangle body, int y)
|
||||||
|
{
|
||||||
|
int x = body.X;
|
||||||
|
if (_mods.Length > 0)
|
||||||
|
x = MenuButton(new Rectangle(x, y, 190, ToolbarHeight), Menu.Mod, "Images/UI/Bestiary/Button_Filtering", _mods[_mod].Name) + 6;
|
||||||
|
MenuButton(new Rectangle(x, y, 150, ToolbarHeight), Menu.Sort, "Images/UI/Bestiary/Button_Sorting", $"Sort: {_sorts[_sort].Name}");
|
||||||
|
return y + ToolbarHeight + 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MenuButton(Rectangle bounds, Menu menu, string icon, string label)
|
||||||
|
{
|
||||||
|
bool open = _menu == menu;
|
||||||
|
if (Widgets.Button(bounds, "", open, MenuScale))
|
||||||
|
{
|
||||||
|
(_menu, _menuOpened, open) = (menu, true, true);
|
||||||
|
_menuScroll.Reset();
|
||||||
|
}
|
||||||
|
if (open)
|
||||||
|
_menuAnchor = bounds;
|
||||||
|
|
||||||
|
Widgets.Icon(GameTextures.Request(icon), new Rectangle(bounds.X + 5, bounds.Y + 4, bounds.Height - 8, bounds.Height - 8));
|
||||||
|
var text = new Rectangle(bounds.X + bounds.Height + 2, bounds.Y, bounds.Width - bounds.Height - 8, bounds.Height);
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, Render.Ellipsize(label, MenuScale, text.Width), text, open || Widgets.Hot(bounds) ? Widgets.Yellow : Color.White, MenuScale);
|
||||||
|
return bounds.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawMenu(Rectangle body)
|
||||||
|
{
|
||||||
|
(string Name, string Detail)[] entries = _menu == Menu.Mod ? [.. _mods.Select(mod => (mod.Name, mod.Count.ToString()))] : [.. _sorts.Select(sort => (sort.Name, ""))];
|
||||||
|
int selected = _menu == Menu.Mod ? _mod : _sort;
|
||||||
|
int widest = (int)entries.Max(entry => Render.Measure(entry.Name, MenuScale).X + Render.Measure(entry.Detail, MenuScale).X);
|
||||||
|
int contentHeight = entries.Length * MenuRowHeight;
|
||||||
|
var popup = new Rectangle(_menuAnchor.X, _menuAnchor.Bottom + 2, Math.Max(_menuAnchor.Width, widest + 56), Math.Min(Math.Min(entries.Length, MenuRows) * MenuRowHeight + 8, body.Bottom - _menuAnchor.Bottom - 2));
|
||||||
|
bool scrolls = contentHeight > popup.Height - 8;
|
||||||
|
Widgets.Panel(popup, Widgets.WindowBg);
|
||||||
|
|
||||||
|
var area = new Rectangle(popup.X + 4, popup.Y + 4, popup.Width - 8 - (scrolls ? 20 : 0), popup.Height - 8);
|
||||||
|
int y0 = _menuScroll.Begin(area);
|
||||||
|
int picked = -1;
|
||||||
|
for (int i = 0; i < entries.Length; i++)
|
||||||
|
{
|
||||||
|
var row = new Rectangle(area.X, y0 + i * MenuRowHeight, area.Width, MenuRowHeight);
|
||||||
|
if (row.Bottom < area.Y || row.Y > area.Bottom)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bool hot = Widgets.Hot(row);
|
||||||
|
if (hot)
|
||||||
|
Render.Fill(Widgets.SpriteBatch, row, Widgets.ButtonHot);
|
||||||
|
var text = new Rectangle(row.X + 6, row.Y, row.Width - 12, row.Height);
|
||||||
|
Render.TextMid(Widgets.SpriteBatch, entries[i].Name, text, hot || i == selected ? Widgets.Yellow : Color.White, MenuScale);
|
||||||
|
Render.TextRight(Widgets.SpriteBatch, entries[i].Detail, text, Widgets.Dim, MenuScale);
|
||||||
|
if (hot && Widgets.Click)
|
||||||
|
picked = i;
|
||||||
|
}
|
||||||
|
_menuScroll.End(area, contentHeight);
|
||||||
|
|
||||||
|
if (picked >= 0)
|
||||||
|
{
|
||||||
|
if (_menu == Menu.Mod)
|
||||||
|
_mod = picked;
|
||||||
|
else
|
||||||
|
_sort = picked;
|
||||||
|
_menu = Menu.None;
|
||||||
|
_scroll.Reset();
|
||||||
|
Widgets.Sound(12);
|
||||||
|
}
|
||||||
|
else if (Widgets.Click && !_menuOpened && !Widgets.Hot(popup))
|
||||||
|
{
|
||||||
|
_menu = Menu.None;
|
||||||
|
}
|
||||||
|
_menuOpened = false;
|
||||||
|
}
|
||||||
|
|
||||||
int DrawCategories(Rectangle body, int y)
|
int DrawCategories(Rectangle body, int y)
|
||||||
{
|
{
|
||||||
if (DrawTabs(body, categories, ref _category, y, TabHeight, 0.78f))
|
if (DrawTabs(body, categories, ref _category, y, TabHeight, 0.78f))
|
||||||
@@ -91,18 +261,16 @@ public abstract class GridBrowser(string id, string title, Vector2 position, int
|
|||||||
changed = true;
|
changed = true;
|
||||||
_scroll.Reset();
|
_scroll.Reset();
|
||||||
}
|
}
|
||||||
if (!iconTab)
|
if (Widgets.Hot(bounds))
|
||||||
|
Widgets.Tooltip = $"{tab.Name}: {CountOf(tab)} {noun}";
|
||||||
|
if (!iconTab || icons is null)
|
||||||
continue;
|
continue;
|
||||||
if (icons is not null)
|
|
||||||
{
|
|
||||||
// Journey icon frames include a 2px separator on the right.
|
// Journey icon frames include a 2px separator on the right.
|
||||||
Rectangle frame = GameTextures.Frame(icons, 11, 1, tab.Icon, 0);
|
Rectangle frame = GameTextures.Frame(icons, 11, 1, tab.Icon, 0);
|
||||||
frame.Width -= 2;
|
frame.Width -= 2;
|
||||||
Widgets.Icon(icons, new Rectangle(bounds.X + 4, bounds.Y + 4, bounds.Width - 8, bounds.Height - 8), frame, 1f, i == selected || Widgets.Hot(bounds) ? Color.White : Color.White * 0.7f);
|
Widgets.Icon(icons, new Rectangle(bounds.X + 4, bounds.Y + 4, bounds.Width - 8, bounds.Height - 8), frame, 1f, i == selected || Widgets.Hot(bounds) ? Color.White : Color.White * 0.7f);
|
||||||
}
|
}
|
||||||
if (Widgets.Hot(bounds))
|
|
||||||
Widgets.Tooltip = tab.Name;
|
|
||||||
}
|
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ public sealed class IdIndex(Func<int> count, Func<int, string?> name)
|
|||||||
string[]? _names;
|
string[]? _names;
|
||||||
string? _lastQuery;
|
string? _lastQuery;
|
||||||
Func<int, bool>? _lastFits;
|
Func<int, bool>? _lastFits;
|
||||||
|
Comparison<int>? _lastOrder;
|
||||||
|
|
||||||
public List<int> Ids { get; } = [];
|
public List<int> Ids { get; } = [];
|
||||||
|
|
||||||
|
public int Version { get; private set; }
|
||||||
|
|
||||||
string[] Names
|
string[] Names
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -27,23 +30,45 @@ public sealed class IdIndex(Func<int> count, Func<int, string?> name)
|
|||||||
|
|
||||||
public string Name(int id) => id > 0 && id < Names.Length ? Names[id] : "";
|
public string Name(int id) => id > 0 && id < Names.Length ? Names[id] : "";
|
||||||
|
|
||||||
public void Refresh() => _lastQuery = null;
|
public IEnumerable<int> All()
|
||||||
|
{
|
||||||
|
string[] names = Names;
|
||||||
|
for (int i = 1; i < names.Length; i++)
|
||||||
|
if (names[i].Length > 0)
|
||||||
|
yield return i;
|
||||||
|
}
|
||||||
|
|
||||||
public void Filter(string query, Func<int, bool> fits)
|
public int Count(Func<int, bool> fits)
|
||||||
|
{
|
||||||
|
int total = 0;
|
||||||
|
foreach (int id in All())
|
||||||
|
if (fits(id))
|
||||||
|
total++;
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
_lastQuery = null;
|
||||||
|
Version++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Filter(string query, Func<int, bool> fits, Comparison<int>? order)
|
||||||
{
|
{
|
||||||
query = query.Trim();
|
query = query.Trim();
|
||||||
if (query == _lastQuery && fits == _lastFits)
|
if (query == _lastQuery && fits == _lastFits && order == _lastOrder)
|
||||||
return;
|
return;
|
||||||
(_lastQuery, _lastFits) = (query, fits);
|
(_lastQuery, _lastFits, _lastOrder) = (query, fits, order);
|
||||||
string[] names = Names;
|
string[] names = Names;
|
||||||
Ids.Clear();
|
Ids.Clear();
|
||||||
if (query.Length == 0)
|
if (query.Length == 0)
|
||||||
{
|
{
|
||||||
for (int i = 1; i < names.Length; i++)
|
foreach (int i in All())
|
||||||
if (names[i].Length > 0 && fits(i))
|
if (fits(i))
|
||||||
Ids.Add(i);
|
Ids.Add(i);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (int.TryParse(query, out int id) && id > 0 && id < names.Length && names[id].Length > 0 && fits(id))
|
if (int.TryParse(query, out int id) && id > 0 && id < names.Length && names[id].Length > 0 && fits(id))
|
||||||
Ids.Add(id);
|
Ids.Add(id);
|
||||||
foreach (bool prefixPass in (bool[])[true, false])
|
foreach (bool prefixPass in (bool[])[true, false])
|
||||||
@@ -54,4 +79,8 @@ public sealed class IdIndex(Func<int> count, Func<int, string?> name)
|
|||||||
Ids.Add(i);
|
Ids.Add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (order is not null)
|
||||||
|
Ids.Sort((a, b) => order(a, b) is var result and not 0 ? result : a.CompareTo(b));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ using Terraria;
|
|||||||
|
|
||||||
namespace TerraSharp.UI;
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
public sealed class ItemBrowser() : ItemGrid("items", "Item Browser", new Vector2(660, 120), 534, 520, [])
|
public sealed class ItemBrowser() : ItemGrid("items", "Item Browser", new Vector2(660, 120), 534, 554, [])
|
||||||
{
|
{
|
||||||
const int InventorySlots = 50;
|
const int InventorySlots = 50;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using Terraria.ModLoader;
|
|||||||
|
|
||||||
namespace TerraSharp.UI;
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
public abstract class ItemGrid(string id, string title, Vector2 position, int width, int height, BrowserCategory[] extra) : GridBrowser(id, title, position, width, height, "Search items by name or ID", "items", new IdIndex(() => ItemCount, Lang.GetItemNameValue), [.. Categories(), .. extra])
|
public abstract class ItemGrid(string id, string title, Vector2 position, int width, int height, BrowserCategory[] extra) : GridBrowser(id, title, position, width, height, "Search items by name or ID", "items", new IdIndex(() => ItemCount, Lang.GetItemNameValue), [.. Categories(), .. extra], Sorts)
|
||||||
{
|
{
|
||||||
#if TMODLOADER
|
#if TMODLOADER
|
||||||
static int ItemCount => ItemLoader.ItemCount;
|
static int ItemCount => ItemLoader.ItemCount;
|
||||||
@@ -36,6 +36,21 @@ public abstract class ItemGrid(string id, string title, Vector2 position, int wi
|
|||||||
new(Language.GetTextValue("CreativePowers.TabMisc"), type => !Fits(Weapon, type) && !Filters.Any(entry => Fits(entry.Filter, type)), 5),
|
new(Language.GetTextValue("CreativePowers.TabMisc"), type => !Fits(Weapon, type) && !Filters.Any(entry => Fits(entry.Filter, type)), 5),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static readonly BrowserSort[] Sorts = [new("Rarity", (a, b) => RarityRank(b).CompareTo(RarityRank(a))), new("Value", (a, b) => (ContentSample(b)?.value ?? 0).CompareTo(ContentSample(a)?.value ?? 0))];
|
||||||
|
|
||||||
|
// Expert (-12) and Master (-13) rank above Purple (11); Quest and Gray stay at the bottom.
|
||||||
|
static int RarityRank(int type) => ContentSample(type)?.rare switch
|
||||||
|
{
|
||||||
|
-13 => 13,
|
||||||
|
-12 => 12,
|
||||||
|
{ } rare => rare,
|
||||||
|
null => int.MinValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
#if TMODLOADER
|
||||||
|
protected override string? ModOf(int type) => ItemLoader.GetItem(type)?.Mod.DisplayNameClean;
|
||||||
|
#endif
|
||||||
|
|
||||||
static Item? ContentSample(int type) => ContentSamples.ItemsByType.TryGetValue(type, out Item? item) ? item : null;
|
static Item? ContentSample(int type) => ContentSamples.ItemsByType.TryGetValue(type, out Item? item) ? item : null;
|
||||||
|
|
||||||
// Whips must be classified as summon weapons before checking melee.
|
// Whips must be classified as summon weapons before checking melee.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Microsoft.Xna.Framework;
|
|||||||
namespace TerraSharp.UI;
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
// Edits a persisted set of item types, such as the auto sell list.
|
// Edits a persisted set of item types, such as the auto sell list.
|
||||||
public sealed class ItemPicker(string id, string title, Vector2 position, HashSet<int> list) : ItemGrid(id, title, position, 534, 520, [new("Selected", list.Contains)])
|
public sealed class ItemPicker(string id, string title, Vector2 position, HashSet<int> list) : ItemGrid(id, title, position, 534, 554, [new("Selected", list.Contains)])
|
||||||
{
|
{
|
||||||
protected override void DrawContent(Rectangle body)
|
protected override void DrawContent(Rectangle body)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using TerraSharp.Features;
|
|||||||
|
|
||||||
namespace TerraSharp.UI;
|
namespace TerraSharp.UI;
|
||||||
|
|
||||||
public sealed class NpcBrowser() : GridBrowser("npcs", "NPC Spawner", new Vector2(660, 140), 532, 520, "Search NPCs by name or ID", "NPCs", new IdIndex(() => NpcCount, Lang.GetNPCNameValue), Categories)
|
public sealed class NpcBrowser() : GridBrowser("npcs", "NPC Spawner", new Vector2(660, 140), 532, 554, "Search NPCs by name or ID", "NPCs", new IdIndex(() => NpcCount, Lang.GetNPCNameValue), Categories, Sorts)
|
||||||
{
|
{
|
||||||
const int Batch = 10;
|
const int Batch = 10;
|
||||||
|
|
||||||
@@ -21,8 +21,14 @@ public sealed class NpcBrowser() : GridBrowser("npcs", "NPC Spawner", new Vector
|
|||||||
|
|
||||||
static readonly BrowserCategory[] Categories = [new("All", NpcSpawner.Available), new("Bosses", type => NpcSpawner.Available(type) && IsBoss(type)), new("Enemies", type => NpcSpawner.Available(type) && Sample(type) is { friendly: false, townNPC: false } && !IsBoss(type) && !IsCritter(type)), new("Town", type => NpcSpawner.Available(type) && Sample(type) is { townNPC: true }), new("Critters", type => NpcSpawner.Available(type) && IsCritter(type))];
|
static readonly BrowserCategory[] Categories = [new("All", NpcSpawner.Available), new("Bosses", type => NpcSpawner.Available(type) && IsBoss(type)), new("Enemies", type => NpcSpawner.Available(type) && Sample(type) is { friendly: false, townNPC: false } && !IsBoss(type) && !IsCritter(type)), new("Town", type => NpcSpawner.Available(type) && Sample(type) is { townNPC: true }), new("Critters", type => NpcSpawner.Available(type) && IsCritter(type))];
|
||||||
|
|
||||||
|
static readonly BrowserSort[] Sorts = [new("Life", (a, b) => (Sample(b)?.lifeMax ?? 0).CompareTo(Sample(a)?.lifeMax ?? 0))];
|
||||||
|
|
||||||
bool _multiplayer;
|
bool _multiplayer;
|
||||||
|
|
||||||
|
#if TMODLOADER
|
||||||
|
protected override string? ModOf(int type) => NPCLoader.GetNPC(type)?.Mod.DisplayNameClean;
|
||||||
|
#endif
|
||||||
|
|
||||||
static NPC? Sample(int type) => ContentSamples.NpcsByNetId.TryGetValue(type, out NPC? npc) ? npc : null;
|
static NPC? Sample(int type) => ContentSamples.NpcsByNetId.TryGetValue(type, out NPC? npc) ? npc : null;
|
||||||
|
|
||||||
static bool IsBoss(int type) => Sample(type) is { boss: true };
|
static bool IsBoss(int type) => Sample(type) is { boss: true };
|
||||||
|
|||||||
Reference in new issue
Block a user