Formats UO Content (#201)
This commit is contained in:
parent
86b7b3aed1
commit
ad3775c4d7
3249 changed files with 431083 additions and 440981 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,250 +1,265 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Server.Network;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Gumps
|
||||
{
|
||||
public class AddGump : Gump
|
||||
{
|
||||
private static readonly Type typeofItem = typeof(Item);
|
||||
private static readonly Type typeofMobile = typeof(Mobile);
|
||||
private readonly int m_Page;
|
||||
private readonly Type[] m_SearchResults;
|
||||
private readonly string m_SearchString;
|
||||
|
||||
public AddGump(Mobile from, string searchString, int page, Type[] searchResults, bool explicitSearch) : base(50, 50)
|
||||
{
|
||||
m_SearchString = searchString;
|
||||
m_SearchResults = searchResults;
|
||||
m_Page = page;
|
||||
|
||||
from.CloseGump<AddGump>();
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(0, 0, 420, 280, 5054);
|
||||
|
||||
AddImageTiled(10, 10, 400, 20, 2624);
|
||||
AddAlphaRegion(10, 10, 400, 20);
|
||||
AddImageTiled(41, 11, 184, 18, 0xBBC);
|
||||
AddImageTiled(42, 12, 182, 16, 2624);
|
||||
AddAlphaRegion(42, 12, 182, 16);
|
||||
|
||||
AddButton(10, 9, 4011, 4013, 1);
|
||||
AddTextEntry(44, 10, 180, 20, 0x480, 0, searchString);
|
||||
|
||||
AddHtmlLocalized(230, 10, 100, 20, 3010005, 0x7FFF);
|
||||
|
||||
AddImageTiled(10, 40, 400, 200, 2624);
|
||||
AddAlphaRegion(10, 40, 400, 200);
|
||||
|
||||
if (searchResults.Length > 0)
|
||||
for (int i = page * 10; i < (page + 1) * 10 && i < searchResults.Length; ++i)
|
||||
{
|
||||
int index = i % 10;
|
||||
|
||||
AddLabel(44, 39 + index * 20, 0x480, searchResults[i].Name);
|
||||
AddButton(10, 39 + index * 20, 4023, 4025, 4 + i);
|
||||
}
|
||||
else
|
||||
AddLabel(15, 44, 0x480, explicitSearch ? "Nothing matched your search terms." : "No results to display.");
|
||||
|
||||
AddImageTiled(10, 250, 400, 20, 2624);
|
||||
AddAlphaRegion(10, 250, 400, 20);
|
||||
|
||||
if (m_Page > 0)
|
||||
AddButton(10, 249, 4014, 4016, 2);
|
||||
else
|
||||
AddImage(10, 249, 4014);
|
||||
|
||||
AddHtmlLocalized(44, 250, 170, 20, 1061028, m_Page > 0 ? 0x7FFF : 0x5EF7); // Previous page
|
||||
|
||||
if ((m_Page + 1) * 10 < searchResults.Length)
|
||||
AddButton(210, 249, 4005, 4007, 3);
|
||||
else
|
||||
AddImage(210, 249, 4005);
|
||||
|
||||
AddHtmlLocalized(244, 250, 170, 20, 1061027, (m_Page + 1) * 10 < searchResults.Length ? 0x7FFF : 0x5EF7); // Next page
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("AddMenu", AccessLevel.GameMaster, AddMenu_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("AddMenu [searchString]")]
|
||||
[Description(
|
||||
"Opens an add menu, with an optional initial search string. This menu allows you to search for Items or Mobiles and add them interactively.")]
|
||||
private static void AddMenu_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
string val = e.ArgString.Trim();
|
||||
Type[] types;
|
||||
bool explicitSearch = false;
|
||||
|
||||
if (val.Length == 0)
|
||||
{
|
||||
types = Type.EmptyTypes;
|
||||
}
|
||||
else if (val.Length < 3)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid search string.");
|
||||
types = Type.EmptyTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
types = Match(val).ToArray();
|
||||
explicitSearch = true;
|
||||
}
|
||||
|
||||
e.Mobile.SendGump(new AddGump(e.Mobile, val, 0, types, explicitSearch));
|
||||
}
|
||||
|
||||
private static void Match(string match, Type[] types, List<Type> results)
|
||||
{
|
||||
if (match.Length == 0)
|
||||
return;
|
||||
|
||||
match = match.ToLower();
|
||||
|
||||
for (int i = 0; i < types.Length; ++i)
|
||||
{
|
||||
Type t = types[i];
|
||||
|
||||
if ((typeofMobile.IsAssignableFrom(t) || typeofItem.IsAssignableFrom(t)) &&
|
||||
t.Name.ToLower().IndexOf(match) >= 0 && !results.Contains(t))
|
||||
{
|
||||
ConstructorInfo[] ctors = t.GetConstructors();
|
||||
|
||||
for (int j = 0; j < ctors.Length; ++j)
|
||||
if (ctors[j].GetParameters().Length == 0 &&
|
||||
ctors[j].IsDefined(typeof(ConstructibleAttribute), false))
|
||||
{
|
||||
results.Add(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Type> Match(string match)
|
||||
{
|
||||
List<Type> results = new List<Type>();
|
||||
Type[] types;
|
||||
|
||||
Assembly[] asms = AssemblyHandler.Assemblies;
|
||||
|
||||
for (int i = 0; i < asms.Length; ++i)
|
||||
{
|
||||
types = AssemblyHandler.GetTypeCache(asms[i]).Types.ToArray();
|
||||
Match(match, types, results);
|
||||
}
|
||||
|
||||
types = AssemblyHandler.GetTypeCache(Core.Assembly).Types.ToArray();
|
||||
Match(match, types, results);
|
||||
|
||||
results.Sort(new TypeNameComparer());
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
Mobile from = sender.Mobile;
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 1: // Search
|
||||
{
|
||||
TextRelay te = info.GetTextEntry(0);
|
||||
string match = te?.Text.Trim() ?? "";
|
||||
|
||||
if (match.Length < 3)
|
||||
{
|
||||
from.SendMessage("Invalid search string.");
|
||||
from.SendGump(new AddGump(from, match, m_Page, m_SearchResults, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendGump(new AddGump(from, match, 0, Match(match).ToArray(), true));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Previous page
|
||||
{
|
||||
if (m_Page > 0)
|
||||
from.SendGump(new AddGump(from, m_SearchString, m_Page - 1, m_SearchResults, true));
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Next page
|
||||
{
|
||||
if ((m_Page + 1) * 10 < m_SearchResults.Length)
|
||||
from.SendGump(new AddGump(from, m_SearchString, m_Page + 1, m_SearchResults, true));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
int index = info.ButtonID - 4;
|
||||
|
||||
if (index >= 0 && index < m_SearchResults.Length)
|
||||
{
|
||||
from.SendMessage("Where do you wish to place this object? <ESC> to cancel.");
|
||||
from.Target = new InternalTarget(m_SearchResults[index], m_SearchResults, m_SearchString, m_Page);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeNameComparer : IComparer<Type>
|
||||
{
|
||||
public int Compare(Type x, Type y) => x?.Name.CompareTo(y?.Name) ?? 1;
|
||||
}
|
||||
|
||||
public class InternalTarget : Target
|
||||
{
|
||||
private readonly int m_Page;
|
||||
private readonly Type[] m_SearchResults;
|
||||
private readonly string m_SearchString;
|
||||
private readonly Type m_Type;
|
||||
|
||||
public InternalTarget(Type type, Type[] searchResults, string searchString, int page) : base(-1, true,
|
||||
TargetFlags.None)
|
||||
{
|
||||
m_Type = type;
|
||||
m_SearchResults = searchResults;
|
||||
m_SearchString = searchString;
|
||||
m_Page = page;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object o)
|
||||
{
|
||||
if (o is IPoint3D p)
|
||||
{
|
||||
p = p switch
|
||||
{
|
||||
Item item => item.GetWorldTop(),
|
||||
Mobile m => m.Location,
|
||||
_ => p
|
||||
};
|
||||
|
||||
Commands.Add.Invoke(from, new Point3D(p), new Point3D(p), new[] { m_Type.Name });
|
||||
|
||||
from.Target = new InternalTarget(m_Type, m_SearchResults, m_SearchString, m_Page);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
|
||||
{
|
||||
if (cancelType == TargetCancelType.Canceled)
|
||||
from.SendGump(new AddGump(from, m_SearchString, m_Page, m_SearchResults, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Server.Network;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Gumps
|
||||
{
|
||||
public class AddGump : Gump
|
||||
{
|
||||
private static readonly Type typeofItem = typeof(Item);
|
||||
private static readonly Type typeofMobile = typeof(Mobile);
|
||||
private readonly int m_Page;
|
||||
private readonly Type[] m_SearchResults;
|
||||
private readonly string m_SearchString;
|
||||
|
||||
public AddGump(Mobile from, string searchString, int page, Type[] searchResults, bool explicitSearch) : base(50, 50)
|
||||
{
|
||||
m_SearchString = searchString;
|
||||
m_SearchResults = searchResults;
|
||||
m_Page = page;
|
||||
|
||||
from.CloseGump<AddGump>();
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(0, 0, 420, 280, 5054);
|
||||
|
||||
AddImageTiled(10, 10, 400, 20, 2624);
|
||||
AddAlphaRegion(10, 10, 400, 20);
|
||||
AddImageTiled(41, 11, 184, 18, 0xBBC);
|
||||
AddImageTiled(42, 12, 182, 16, 2624);
|
||||
AddAlphaRegion(42, 12, 182, 16);
|
||||
|
||||
AddButton(10, 9, 4011, 4013, 1);
|
||||
AddTextEntry(44, 10, 180, 20, 0x480, 0, searchString);
|
||||
|
||||
AddHtmlLocalized(230, 10, 100, 20, 3010005, 0x7FFF);
|
||||
|
||||
AddImageTiled(10, 40, 400, 200, 2624);
|
||||
AddAlphaRegion(10, 40, 400, 200);
|
||||
|
||||
if (searchResults.Length > 0)
|
||||
for (var i = page * 10; i < (page + 1) * 10 && i < searchResults.Length; ++i)
|
||||
{
|
||||
var index = i % 10;
|
||||
|
||||
AddLabel(44, 39 + index * 20, 0x480, searchResults[i].Name);
|
||||
AddButton(10, 39 + index * 20, 4023, 4025, 4 + i);
|
||||
}
|
||||
else
|
||||
AddLabel(15, 44, 0x480, explicitSearch ? "Nothing matched your search terms." : "No results to display.");
|
||||
|
||||
AddImageTiled(10, 250, 400, 20, 2624);
|
||||
AddAlphaRegion(10, 250, 400, 20);
|
||||
|
||||
if (m_Page > 0)
|
||||
AddButton(10, 249, 4014, 4016, 2);
|
||||
else
|
||||
AddImage(10, 249, 4014);
|
||||
|
||||
AddHtmlLocalized(44, 250, 170, 20, 1061028, m_Page > 0 ? 0x7FFF : 0x5EF7); // Previous page
|
||||
|
||||
if ((m_Page + 1) * 10 < searchResults.Length)
|
||||
AddButton(210, 249, 4005, 4007, 3);
|
||||
else
|
||||
AddImage(210, 249, 4005);
|
||||
|
||||
AddHtmlLocalized(
|
||||
244,
|
||||
250,
|
||||
170,
|
||||
20,
|
||||
1061027,
|
||||
(m_Page + 1) * 10 < searchResults.Length ? 0x7FFF : 0x5EF7
|
||||
); // Next page
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("AddMenu", AccessLevel.GameMaster, AddMenu_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("AddMenu [searchString]")]
|
||||
[Description(
|
||||
"Opens an add menu, with an optional initial search string. This menu allows you to search for Items or Mobiles and add them interactively."
|
||||
)]
|
||||
private static void AddMenu_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
var val = e.ArgString.Trim();
|
||||
Type[] types;
|
||||
var explicitSearch = false;
|
||||
|
||||
if (val.Length == 0)
|
||||
{
|
||||
types = Type.EmptyTypes;
|
||||
}
|
||||
else if (val.Length < 3)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid search string.");
|
||||
types = Type.EmptyTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
types = Match(val).ToArray();
|
||||
explicitSearch = true;
|
||||
}
|
||||
|
||||
e.Mobile.SendGump(new AddGump(e.Mobile, val, 0, types, explicitSearch));
|
||||
}
|
||||
|
||||
private static void Match(string match, Type[] types, List<Type> results)
|
||||
{
|
||||
if (match.Length == 0)
|
||||
return;
|
||||
|
||||
match = match.ToLower();
|
||||
|
||||
for (var i = 0; i < types.Length; ++i)
|
||||
{
|
||||
var t = types[i];
|
||||
|
||||
if ((typeofMobile.IsAssignableFrom(t) || typeofItem.IsAssignableFrom(t)) &&
|
||||
t.Name.ToLower().IndexOf(match) >= 0 && !results.Contains(t))
|
||||
{
|
||||
var ctors = t.GetConstructors();
|
||||
|
||||
for (var j = 0; j < ctors.Length; ++j)
|
||||
if (ctors[j].GetParameters().Length == 0 &&
|
||||
ctors[j].IsDefined(typeof(ConstructibleAttribute), false))
|
||||
{
|
||||
results.Add(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Type> Match(string match)
|
||||
{
|
||||
var results = new List<Type>();
|
||||
Type[] types;
|
||||
|
||||
var asms = AssemblyHandler.Assemblies;
|
||||
|
||||
for (var i = 0; i < asms.Length; ++i)
|
||||
{
|
||||
types = AssemblyHandler.GetTypeCache(asms[i]).Types.ToArray();
|
||||
Match(match, types, results);
|
||||
}
|
||||
|
||||
types = AssemblyHandler.GetTypeCache(Core.Assembly).Types.ToArray();
|
||||
Match(match, types, results);
|
||||
|
||||
results.Sort(new TypeNameComparer());
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
var from = sender.Mobile;
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 1: // Search
|
||||
{
|
||||
var te = info.GetTextEntry(0);
|
||||
var match = te?.Text.Trim() ?? "";
|
||||
|
||||
if (match.Length < 3)
|
||||
{
|
||||
from.SendMessage("Invalid search string.");
|
||||
from.SendGump(new AddGump(from, match, m_Page, m_SearchResults, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendGump(new AddGump(from, match, 0, Match(match).ToArray(), true));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Previous page
|
||||
{
|
||||
if (m_Page > 0)
|
||||
from.SendGump(new AddGump(from, m_SearchString, m_Page - 1, m_SearchResults, true));
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Next page
|
||||
{
|
||||
if ((m_Page + 1) * 10 < m_SearchResults.Length)
|
||||
from.SendGump(new AddGump(from, m_SearchString, m_Page + 1, m_SearchResults, true));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var index = info.ButtonID - 4;
|
||||
|
||||
if (index >= 0 && index < m_SearchResults.Length)
|
||||
{
|
||||
from.SendMessage("Where do you wish to place this object? <ESC> to cancel.");
|
||||
from.Target = new InternalTarget(
|
||||
m_SearchResults[index],
|
||||
m_SearchResults,
|
||||
m_SearchString,
|
||||
m_Page
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeNameComparer : IComparer<Type>
|
||||
{
|
||||
public int Compare(Type x, Type y) => x?.Name.CompareTo(y?.Name) ?? 1;
|
||||
}
|
||||
|
||||
public class InternalTarget : Target
|
||||
{
|
||||
private readonly int m_Page;
|
||||
private readonly Type[] m_SearchResults;
|
||||
private readonly string m_SearchString;
|
||||
private readonly Type m_Type;
|
||||
|
||||
public InternalTarget(Type type, Type[] searchResults, string searchString, int page) : base(
|
||||
-1,
|
||||
true,
|
||||
TargetFlags.None
|
||||
)
|
||||
{
|
||||
m_Type = type;
|
||||
m_SearchResults = searchResults;
|
||||
m_SearchString = searchString;
|
||||
m_Page = page;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object o)
|
||||
{
|
||||
if (o is IPoint3D p)
|
||||
{
|
||||
p = p switch
|
||||
{
|
||||
Item item => item.GetWorldTop(),
|
||||
Mobile m => m.Location,
|
||||
_ => p
|
||||
};
|
||||
|
||||
Commands.Add.Invoke(from, new Point3D(p), new Point3D(p), new[] { m_Type.Name });
|
||||
|
||||
from.Target = new InternalTarget(m_Type, m_SearchResults, m_SearchString, m_Page);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
|
||||
{
|
||||
if (cancelType == TargetCancelType.Canceled)
|
||||
from.SendGump(new AddGump(from, m_SearchString, m_Page, m_SearchResults, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
using Server.Gumps;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class CAGCategory : CAGNode
|
||||
{
|
||||
private static CAGCategory m_Root;
|
||||
|
||||
public CAGCategory(string title, CAGCategory parent = null)
|
||||
{
|
||||
Title = title;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public override string Title { get; }
|
||||
|
||||
public CAGNode[] Nodes { get; set; }
|
||||
|
||||
public CAGCategory Parent { get; }
|
||||
|
||||
public static CAGCategory Root => m_Root ??= CAGLoader.Load();
|
||||
|
||||
public override void OnClick(Mobile from, int page)
|
||||
{
|
||||
from.SendGump(new CategorizedAddGump(from, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
using Server.Gumps;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class CAGCategory : CAGNode
|
||||
{
|
||||
private static CAGCategory m_Root;
|
||||
|
||||
public CAGCategory(string title, CAGCategory parent = null)
|
||||
{
|
||||
Title = title;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public override string Title { get; }
|
||||
|
||||
public CAGNode[] Nodes { get; set; }
|
||||
|
||||
public CAGCategory Parent { get; }
|
||||
|
||||
public static CAGCategory Root => m_Root ??= CAGLoader.Load();
|
||||
|
||||
public override void OnClick(Mobile from, int page)
|
||||
{
|
||||
from.SendGump(new CategorizedAddGump(from, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,68 +1,62 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Json;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public static class CAGLoader
|
||||
{
|
||||
public static CAGCategory Load()
|
||||
{
|
||||
var root = new CAGCategory("Add Menu");
|
||||
var path = Path.Combine(Core.BaseDirectory, "Data/objects.json");
|
||||
|
||||
List<CAGJson> list = JsonConfig.Deserialize<List<CAGJson>>(path);
|
||||
|
||||
// Not an optimized solution
|
||||
foreach (var cag in list)
|
||||
{
|
||||
var parent = root;
|
||||
// Navigate through the dot notation categories until we find the last one
|
||||
var categories = cag.Category.Split(".");
|
||||
for (int i = 0; i < categories.Length; i++)
|
||||
{
|
||||
var category = categories[i];
|
||||
var oldParent = parent;
|
||||
|
||||
for (int j = 0; j < parent.Nodes.Length; j++)
|
||||
{
|
||||
var node = parent.Nodes[i];
|
||||
if (category == node.Title && node is CAGCategory cat)
|
||||
{
|
||||
parent = cat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent == oldParent)
|
||||
parent = new CAGCategory(category, parent);
|
||||
}
|
||||
|
||||
// Set the objects associated with the child most node
|
||||
parent.Nodes = new CAGNode[cag.Objects.Length];
|
||||
for (int i = 0; i < cag.Objects.Length; i++)
|
||||
{
|
||||
var obj = cag.Objects[i];
|
||||
obj.Parent = parent;
|
||||
parent.Nodes[i] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public class CAGJson
|
||||
{
|
||||
public CAGJson()
|
||||
{
|
||||
}
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public string Category { get; set; }
|
||||
|
||||
[JsonPropertyName("objects")]
|
||||
public CAGObject[] Objects { get; set; }
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Json;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public static class CAGLoader
|
||||
{
|
||||
public static CAGCategory Load()
|
||||
{
|
||||
var root = new CAGCategory("Add Menu");
|
||||
var path = Path.Combine(Core.BaseDirectory, "Data/objects.json");
|
||||
|
||||
var list = JsonConfig.Deserialize<List<CAGJson>>(path);
|
||||
|
||||
// Not an optimized solution
|
||||
foreach (var cag in list)
|
||||
{
|
||||
var parent = root;
|
||||
// Navigate through the dot notation categories until we find the last one
|
||||
var categories = cag.Category.Split(".");
|
||||
for (var i = 0; i < categories.Length; i++)
|
||||
{
|
||||
var category = categories[i];
|
||||
var oldParent = parent;
|
||||
|
||||
for (var j = 0; j < parent.Nodes.Length; j++)
|
||||
{
|
||||
var node = parent.Nodes[i];
|
||||
if (category == node.Title && node is CAGCategory cat)
|
||||
{
|
||||
parent = cat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent == oldParent)
|
||||
parent = new CAGCategory(category, parent);
|
||||
}
|
||||
|
||||
// Set the objects associated with the child most node
|
||||
parent.Nodes = new CAGNode[cag.Objects.Length];
|
||||
for (var i = 0; i < cag.Objects.Length; i++)
|
||||
{
|
||||
var obj = cag.Objects[i];
|
||||
obj.Parent = parent;
|
||||
parent.Nodes[i] = obj;
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public class CAGJson
|
||||
{
|
||||
[JsonPropertyName("category")] public string Category { get; set; }
|
||||
|
||||
[JsonPropertyName("objects")] public CAGObject[] Objects { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
namespace Server.Commands
|
||||
{
|
||||
public abstract class CAGNode
|
||||
{
|
||||
public abstract string Title { get; }
|
||||
public abstract void OnClick(Mobile from, int page);
|
||||
}
|
||||
}
|
||||
namespace Server.Commands
|
||||
{
|
||||
public abstract class CAGNode
|
||||
{
|
||||
public abstract string Title { get; }
|
||||
public abstract void OnClick(Mobile from, int page);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,33 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Gumps;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class CAGObject : CAGNode
|
||||
{
|
||||
public CAGObject()
|
||||
{
|
||||
}
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public Type Type { get; set; }
|
||||
|
||||
[JsonPropertyName("gfx")]
|
||||
public int ItemID { get; set; }
|
||||
|
||||
[JsonPropertyName("hue")]
|
||||
public int? Hue { get; set; }
|
||||
|
||||
public CAGCategory Parent { get; set; }
|
||||
|
||||
public override string Title => Type == null ? "bad type" : Type.Name;
|
||||
|
||||
public override void OnClick(Mobile from, int page)
|
||||
{
|
||||
if (Type == null)
|
||||
{
|
||||
from.SendMessage("That is an invalid type name.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandSystem.Handle(from, $"{CommandSystem.Prefix}Add {Type.Name}");
|
||||
|
||||
from.SendGump(new CategorizedAddGump(from, Parent, page));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Gumps;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class CAGObject : CAGNode
|
||||
{
|
||||
[JsonPropertyName("type")] public Type Type { get; set; }
|
||||
|
||||
[JsonPropertyName("gfx")] public int ItemID { get; set; }
|
||||
|
||||
[JsonPropertyName("hue")] public int? Hue { get; set; }
|
||||
|
||||
public CAGCategory Parent { get; set; }
|
||||
|
||||
public override string Title => Type == null ? "bad type" : Type.Name;
|
||||
|
||||
public override void OnClick(Mobile from, int page)
|
||||
{
|
||||
if (Type == null)
|
||||
{
|
||||
from.SendMessage("That is an invalid type name.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandSystem.Handle(from, $"{CommandSystem.Prefix}Add {Type.Name}");
|
||||
|
||||
from.SendGump(new CategorizedAddGump(from, Parent, page));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,391 +1,398 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Server.Items;
|
||||
using Server.Json;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public static class Categorization
|
||||
{
|
||||
private static CategoryEntry m_RootItems, m_RootMobiles;
|
||||
|
||||
private static readonly Type typeofItem = typeof(Item);
|
||||
private static readonly Type typeofMobile = typeof(Mobile);
|
||||
private static readonly Type typeofConstructible = typeof(ConstructibleAttribute);
|
||||
|
||||
public static CategoryEntry Items
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_RootItems == null)
|
||||
Load();
|
||||
|
||||
return m_RootItems;
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryEntry Mobiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_RootMobiles == null)
|
||||
Load();
|
||||
|
||||
return m_RootMobiles;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("RebuildCategorization", AccessLevel.Administrator, RebuildCategorization_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("RebuildCategorization")]
|
||||
[Description("Rebuilds the categorization data file used by the Add command.")]
|
||||
public static void RebuildCategorization_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
CategoryEntry root = new CategoryEntry(null, "Add Menu", new[] { Items, Mobiles });
|
||||
|
||||
List<CategoryEntry> ceList = new List<CategoryEntry>();
|
||||
ceList.AddRange(root.SubCategories);
|
||||
|
||||
Export(ceList, "Data/objects.json");
|
||||
|
||||
e.Mobile.SendMessage("Categorization menu rebuilt.");
|
||||
}
|
||||
|
||||
public static void Export(List<CategoryEntry> ceList, string fileName)
|
||||
{
|
||||
List<CAGJson> list = new List<CAGJson>();
|
||||
foreach (var ce in ceList)
|
||||
RecurseExport(list, ce, null);
|
||||
|
||||
JsonConfig.Serialize(fileName, list);
|
||||
}
|
||||
|
||||
public static void RecurseExport(List<CAGJson> list, CategoryEntry ce, string category)
|
||||
{
|
||||
category = string.IsNullOrWhiteSpace(category) ? ce.Title : $"{category}{ce.Title}";
|
||||
|
||||
if (ce.Matched.Count > 0)
|
||||
list.Add(new CAGJson
|
||||
{
|
||||
Category = category,
|
||||
Objects = ce.Matched.Select(cte =>
|
||||
{
|
||||
if (cte.Object is Item item)
|
||||
{
|
||||
int itemID = item.ItemID;
|
||||
|
||||
if (item is BaseAddon addon && addon.Components.Count == 1)
|
||||
itemID = addon.Components[0].ItemID;
|
||||
|
||||
if (itemID > TileData.MaxItemValue)
|
||||
itemID = 1;
|
||||
|
||||
int? hue = item.Hue & 0x7FFF;
|
||||
|
||||
if ((hue & 0x4000) != 0)
|
||||
hue = 0;
|
||||
|
||||
return new CAGObject
|
||||
{
|
||||
Type = cte.Type,
|
||||
ItemID = itemID,
|
||||
Hue = hue == 0 ? null : hue
|
||||
};
|
||||
}
|
||||
|
||||
if (cte.Object is Mobile m)
|
||||
{
|
||||
int itemID = ShrinkTable.Lookup(m, 1);
|
||||
|
||||
int? hue = m.Hue & 0x7FFF;
|
||||
|
||||
if ((hue & 0x4000) != 0)
|
||||
hue = 0;
|
||||
|
||||
return new CAGObject
|
||||
{
|
||||
Type = cte.Type,
|
||||
ItemID = itemID,
|
||||
Hue = hue == 0 ? null : hue
|
||||
};
|
||||
}
|
||||
|
||||
throw new InvalidCastException($"Categorization Type Entry: {cte.Type.Name} is not a valid type.");
|
||||
}).ToArray()
|
||||
});
|
||||
|
||||
List<CategoryEntry> subCats = new List<CategoryEntry>(ce.SubCategories);
|
||||
|
||||
subCats.Sort(new CategorySorter());
|
||||
|
||||
for (int i = 0; i < subCats.Count; i++)
|
||||
{
|
||||
var subCat = subCats[i];
|
||||
RecurseExport(list, subCat, category);
|
||||
}
|
||||
}
|
||||
public static void Load()
|
||||
{
|
||||
List<Type> types = new List<Type>();
|
||||
|
||||
AddTypes(Core.Assembly, types);
|
||||
|
||||
for (int i = 0; i < AssemblyHandler.Assemblies.Length; ++i)
|
||||
AddTypes(AssemblyHandler.Assemblies[i], types);
|
||||
|
||||
m_RootItems = Load(types, "Data/items.cfg");
|
||||
m_RootMobiles = Load(types, "Data/mobiles.cfg");
|
||||
}
|
||||
|
||||
private static CategoryEntry Load(List<Type> types, string config)
|
||||
{
|
||||
CategoryLine[] lines = CategoryLine.Load(config);
|
||||
|
||||
if (lines.Length <= 0) return new CategoryEntry();
|
||||
|
||||
int index = 0;
|
||||
CategoryEntry root = new CategoryEntry(null, lines, ref index);
|
||||
|
||||
Fill(root, types);
|
||||
|
||||
return root;
|
||||
|
||||
}
|
||||
|
||||
private static bool IsConstructible(Type type)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeofItem) && !type.IsSubclassOf(typeofMobile))
|
||||
return false;
|
||||
|
||||
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
|
||||
|
||||
return ctor?.IsDefined(typeofConstructible, false) == true;
|
||||
}
|
||||
|
||||
private static void AddTypes(Assembly asm, List<Type> types)
|
||||
{
|
||||
Type[] allTypes = asm.GetTypes();
|
||||
|
||||
for (int i = 0; i < allTypes.Length; ++i)
|
||||
{
|
||||
Type type = allTypes[i];
|
||||
|
||||
if (type.IsAbstract)
|
||||
continue;
|
||||
|
||||
if (IsConstructible(type))
|
||||
types.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Fill(CategoryEntry root, List<Type> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
Type type = list[i];
|
||||
CategoryEntry match = GetDeepestMatch(root, type);
|
||||
|
||||
if (match == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
match.Matched.Add(new CategoryTypeEntry(type));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CategoryEntry GetDeepestMatch(CategoryEntry root, Type type)
|
||||
{
|
||||
if (!root.IsMatch(type))
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < root.SubCategories.Length; ++i)
|
||||
{
|
||||
CategoryEntry check = GetDeepestMatch(root.SubCategories[i], type);
|
||||
|
||||
if (check != null)
|
||||
return check;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategorySorter : IComparer<CategoryEntry>
|
||||
{
|
||||
public int Compare(CategoryEntry x, CategoryEntry y)
|
||||
{
|
||||
string a = x?.Title;
|
||||
string b = y?.Title;
|
||||
|
||||
return a switch
|
||||
{
|
||||
null when b == null => 0,
|
||||
null => 1,
|
||||
_ => a.CompareTo(b)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryTypeSorter : IComparer<CategoryTypeEntry>
|
||||
{
|
||||
public int Compare(CategoryTypeEntry x, CategoryTypeEntry y)
|
||||
{
|
||||
string a = x?.Type.Name;
|
||||
string b = y?.Type.Name;
|
||||
|
||||
return a switch
|
||||
{
|
||||
null when b == null => 0,
|
||||
null => 1,
|
||||
_ => a.CompareTo(b)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryTypeEntry
|
||||
{
|
||||
public CategoryTypeEntry(Type type)
|
||||
{
|
||||
Type = type;
|
||||
Object = ActivatorUtil.CreateInstance(type);
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public object Object { get; }
|
||||
}
|
||||
|
||||
public class CategoryEntry
|
||||
{
|
||||
public CategoryEntry(CategoryEntry parent = null, string title = "(empty)", CategoryEntry[] subCats = null)
|
||||
{
|
||||
Parent = parent;
|
||||
Title = title;
|
||||
SubCategories = subCats ?? Array.Empty<CategoryEntry>();
|
||||
Matches = Array.Empty<Type>();
|
||||
Matched = new List<CategoryTypeEntry>();
|
||||
}
|
||||
|
||||
public CategoryEntry(CategoryEntry parent, CategoryLine[] lines, ref int index)
|
||||
{
|
||||
Parent = parent;
|
||||
|
||||
string text = lines[index].Text;
|
||||
|
||||
int start = text.IndexOf('(');
|
||||
|
||||
if (start < 0)
|
||||
throw new FormatException($"Input string not correctly formatted ('{text}')");
|
||||
|
||||
Title = text.Substring(0, start).Trim();
|
||||
|
||||
int end = text.IndexOf(')', ++start);
|
||||
|
||||
if (end < start)
|
||||
throw new FormatException($"Input string not correctly formatted ('{text}')");
|
||||
|
||||
text = text.Substring(start, end - start);
|
||||
string[] split = text.Split(';');
|
||||
|
||||
List<Type> list = new List<Type>();
|
||||
|
||||
for (int i = 0; i < split.Length; ++i)
|
||||
{
|
||||
Type type = AssemblyHandler.FindFirstTypeForName(split[i].Trim());
|
||||
|
||||
if (type == null)
|
||||
Console.WriteLine("Match type not found ('{0}')", split[i].Trim());
|
||||
else
|
||||
list.Add(type);
|
||||
}
|
||||
|
||||
Matches = list.ToArray();
|
||||
list.Clear();
|
||||
|
||||
int ourIndentation = lines[index].Indentation;
|
||||
|
||||
++index;
|
||||
|
||||
List<CategoryEntry> entryList = new List<CategoryEntry>();
|
||||
|
||||
while (index < lines.Length && lines[index].Indentation > ourIndentation)
|
||||
entryList.Add(new CategoryEntry(this, lines, ref index));
|
||||
|
||||
SubCategories = entryList.ToArray();
|
||||
entryList.Clear();
|
||||
|
||||
Matched = new List<CategoryTypeEntry>();
|
||||
}
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
public Type[] Matches { get; }
|
||||
|
||||
public CategoryEntry Parent { get; }
|
||||
|
||||
public CategoryEntry[] SubCategories { get; }
|
||||
|
||||
public List<CategoryTypeEntry> Matched { get; }
|
||||
|
||||
public bool IsMatch(Type type)
|
||||
{
|
||||
bool isMatch = false;
|
||||
|
||||
for (int i = 0; !isMatch && i < Matches.Length; ++i)
|
||||
isMatch = type == Matches[i] || type.IsSubclassOf(Matches[i]);
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryLine
|
||||
{
|
||||
public CategoryLine(string input)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < input.Length; ++index)
|
||||
if (char.IsLetter(input, index))
|
||||
break;
|
||||
|
||||
if (index >= input.Length)
|
||||
throw new FormatException($"Input string not correctly formatted ('{input}')");
|
||||
|
||||
Indentation = index;
|
||||
Text = input.Substring(index);
|
||||
}
|
||||
|
||||
public int Indentation { get; }
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
public static CategoryLine[] Load(string path)
|
||||
{
|
||||
List<CategoryLine> list = new List<CategoryLine>();
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using StreamReader ip = new StreamReader(path);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
list.Add(new CategoryLine(line));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Server.Items;
|
||||
using Server.Json;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public static class Categorization
|
||||
{
|
||||
private static CategoryEntry m_RootItems, m_RootMobiles;
|
||||
|
||||
private static readonly Type typeofItem = typeof(Item);
|
||||
private static readonly Type typeofMobile = typeof(Mobile);
|
||||
private static readonly Type typeofConstructible = typeof(ConstructibleAttribute);
|
||||
|
||||
public static CategoryEntry Items
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_RootItems == null)
|
||||
Load();
|
||||
|
||||
return m_RootItems;
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryEntry Mobiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_RootMobiles == null)
|
||||
Load();
|
||||
|
||||
return m_RootMobiles;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("RebuildCategorization", AccessLevel.Administrator, RebuildCategorization_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("RebuildCategorization")]
|
||||
[Description("Rebuilds the categorization data file used by the Add command.")]
|
||||
public static void RebuildCategorization_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
var root = new CategoryEntry(null, "Add Menu", new[] { Items, Mobiles });
|
||||
|
||||
var ceList = new List<CategoryEntry>();
|
||||
ceList.AddRange(root.SubCategories);
|
||||
|
||||
Export(ceList, "Data/objects.json");
|
||||
|
||||
e.Mobile.SendMessage("Categorization menu rebuilt.");
|
||||
}
|
||||
|
||||
public static void Export(List<CategoryEntry> ceList, string fileName)
|
||||
{
|
||||
var list = new List<CAGJson>();
|
||||
foreach (var ce in ceList)
|
||||
RecurseExport(list, ce, null);
|
||||
|
||||
JsonConfig.Serialize(fileName, list);
|
||||
}
|
||||
|
||||
public static void RecurseExport(List<CAGJson> list, CategoryEntry ce, string category)
|
||||
{
|
||||
category = string.IsNullOrWhiteSpace(category) ? ce.Title : $"{category}{ce.Title}";
|
||||
|
||||
if (ce.Matched.Count > 0)
|
||||
list.Add(
|
||||
new CAGJson
|
||||
{
|
||||
Category = category,
|
||||
Objects = ce.Matched.Select(
|
||||
cte =>
|
||||
{
|
||||
if (cte.Object is Item item)
|
||||
{
|
||||
var itemID = item.ItemID;
|
||||
|
||||
if (item is BaseAddon addon && addon.Components.Count == 1)
|
||||
itemID = addon.Components[0].ItemID;
|
||||
|
||||
if (itemID > TileData.MaxItemValue)
|
||||
itemID = 1;
|
||||
|
||||
int? hue = item.Hue & 0x7FFF;
|
||||
|
||||
if ((hue & 0x4000) != 0)
|
||||
hue = 0;
|
||||
|
||||
return new CAGObject
|
||||
{
|
||||
Type = cte.Type,
|
||||
ItemID = itemID,
|
||||
Hue = hue == 0 ? null : hue
|
||||
};
|
||||
}
|
||||
|
||||
if (cte.Object is Mobile m)
|
||||
{
|
||||
var itemID = ShrinkTable.Lookup(m, 1);
|
||||
|
||||
int? hue = m.Hue & 0x7FFF;
|
||||
|
||||
if ((hue & 0x4000) != 0)
|
||||
hue = 0;
|
||||
|
||||
return new CAGObject
|
||||
{
|
||||
Type = cte.Type,
|
||||
ItemID = itemID,
|
||||
Hue = hue == 0 ? null : hue
|
||||
};
|
||||
}
|
||||
|
||||
throw new InvalidCastException(
|
||||
$"Categorization Type Entry: {cte.Type.Name} is not a valid type."
|
||||
);
|
||||
}
|
||||
)
|
||||
.ToArray()
|
||||
}
|
||||
);
|
||||
|
||||
var subCats = new List<CategoryEntry>(ce.SubCategories);
|
||||
|
||||
subCats.Sort(new CategorySorter());
|
||||
|
||||
for (var i = 0; i < subCats.Count; i++)
|
||||
{
|
||||
var subCat = subCats[i];
|
||||
RecurseExport(list, subCat, category);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
var types = new List<Type>();
|
||||
|
||||
AddTypes(Core.Assembly, types);
|
||||
|
||||
for (var i = 0; i < AssemblyHandler.Assemblies.Length; ++i)
|
||||
AddTypes(AssemblyHandler.Assemblies[i], types);
|
||||
|
||||
m_RootItems = Load(types, "Data/items.cfg");
|
||||
m_RootMobiles = Load(types, "Data/mobiles.cfg");
|
||||
}
|
||||
|
||||
private static CategoryEntry Load(List<Type> types, string config)
|
||||
{
|
||||
var lines = CategoryLine.Load(config);
|
||||
|
||||
if (lines.Length <= 0) return new CategoryEntry();
|
||||
|
||||
var index = 0;
|
||||
var root = new CategoryEntry(null, lines, ref index);
|
||||
|
||||
Fill(root, types);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static bool IsConstructible(Type type)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeofItem) && !type.IsSubclassOf(typeofMobile))
|
||||
return false;
|
||||
|
||||
var ctor = type.GetConstructor(Type.EmptyTypes);
|
||||
|
||||
return ctor?.IsDefined(typeofConstructible, false) == true;
|
||||
}
|
||||
|
||||
private static void AddTypes(Assembly asm, List<Type> types)
|
||||
{
|
||||
var allTypes = asm.GetTypes();
|
||||
|
||||
for (var i = 0; i < allTypes.Length; ++i)
|
||||
{
|
||||
var type = allTypes[i];
|
||||
|
||||
if (type.IsAbstract)
|
||||
continue;
|
||||
|
||||
if (IsConstructible(type))
|
||||
types.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Fill(CategoryEntry root, List<Type> list)
|
||||
{
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var type = list[i];
|
||||
var match = GetDeepestMatch(root, type);
|
||||
|
||||
if (match == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
match.Matched.Add(new CategoryTypeEntry(type));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CategoryEntry GetDeepestMatch(CategoryEntry root, Type type)
|
||||
{
|
||||
if (!root.IsMatch(type))
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < root.SubCategories.Length; ++i)
|
||||
{
|
||||
var check = GetDeepestMatch(root.SubCategories[i], type);
|
||||
|
||||
if (check != null)
|
||||
return check;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategorySorter : IComparer<CategoryEntry>
|
||||
{
|
||||
public int Compare(CategoryEntry x, CategoryEntry y)
|
||||
{
|
||||
var a = x?.Title;
|
||||
var b = y?.Title;
|
||||
|
||||
return a switch
|
||||
{
|
||||
null when b == null => 0,
|
||||
null => 1,
|
||||
_ => a.CompareTo(b)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryTypeSorter : IComparer<CategoryTypeEntry>
|
||||
{
|
||||
public int Compare(CategoryTypeEntry x, CategoryTypeEntry y)
|
||||
{
|
||||
var a = x?.Type.Name;
|
||||
var b = y?.Type.Name;
|
||||
|
||||
return a switch
|
||||
{
|
||||
null when b == null => 0,
|
||||
null => 1,
|
||||
_ => a.CompareTo(b)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryTypeEntry
|
||||
{
|
||||
public CategoryTypeEntry(Type type)
|
||||
{
|
||||
Type = type;
|
||||
Object = ActivatorUtil.CreateInstance(type);
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public object Object { get; }
|
||||
}
|
||||
|
||||
public class CategoryEntry
|
||||
{
|
||||
public CategoryEntry(CategoryEntry parent = null, string title = "(empty)", CategoryEntry[] subCats = null)
|
||||
{
|
||||
Parent = parent;
|
||||
Title = title;
|
||||
SubCategories = subCats ?? Array.Empty<CategoryEntry>();
|
||||
Matches = Array.Empty<Type>();
|
||||
Matched = new List<CategoryTypeEntry>();
|
||||
}
|
||||
|
||||
public CategoryEntry(CategoryEntry parent, CategoryLine[] lines, ref int index)
|
||||
{
|
||||
Parent = parent;
|
||||
|
||||
var text = lines[index].Text;
|
||||
|
||||
var start = text.IndexOf('(');
|
||||
|
||||
if (start < 0)
|
||||
throw new FormatException($"Input string not correctly formatted ('{text}')");
|
||||
|
||||
Title = text.Substring(0, start).Trim();
|
||||
|
||||
var end = text.IndexOf(')', ++start);
|
||||
|
||||
if (end < start)
|
||||
throw new FormatException($"Input string not correctly formatted ('{text}')");
|
||||
|
||||
text = text.Substring(start, end - start);
|
||||
var split = text.Split(';');
|
||||
|
||||
var list = new List<Type>();
|
||||
|
||||
for (var i = 0; i < split.Length; ++i)
|
||||
{
|
||||
var type = AssemblyHandler.FindFirstTypeForName(split[i].Trim());
|
||||
|
||||
if (type == null)
|
||||
Console.WriteLine("Match type not found ('{0}')", split[i].Trim());
|
||||
else
|
||||
list.Add(type);
|
||||
}
|
||||
|
||||
Matches = list.ToArray();
|
||||
list.Clear();
|
||||
|
||||
var ourIndentation = lines[index].Indentation;
|
||||
|
||||
++index;
|
||||
|
||||
var entryList = new List<CategoryEntry>();
|
||||
|
||||
while (index < lines.Length && lines[index].Indentation > ourIndentation)
|
||||
entryList.Add(new CategoryEntry(this, lines, ref index));
|
||||
|
||||
SubCategories = entryList.ToArray();
|
||||
entryList.Clear();
|
||||
|
||||
Matched = new List<CategoryTypeEntry>();
|
||||
}
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
public Type[] Matches { get; }
|
||||
|
||||
public CategoryEntry Parent { get; }
|
||||
|
||||
public CategoryEntry[] SubCategories { get; }
|
||||
|
||||
public List<CategoryTypeEntry> Matched { get; }
|
||||
|
||||
public bool IsMatch(Type type)
|
||||
{
|
||||
var isMatch = false;
|
||||
|
||||
for (var i = 0; !isMatch && i < Matches.Length; ++i)
|
||||
isMatch = type == Matches[i] || type.IsSubclassOf(Matches[i]);
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryLine
|
||||
{
|
||||
public CategoryLine(string input)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < input.Length; ++index)
|
||||
if (char.IsLetter(input, index))
|
||||
break;
|
||||
|
||||
if (index >= input.Length)
|
||||
throw new FormatException($"Input string not correctly formatted ('{input}')");
|
||||
|
||||
Indentation = index;
|
||||
Text = input.Substring(index);
|
||||
}
|
||||
|
||||
public int Indentation { get; }
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
public static CategoryLine[] Load(string path)
|
||||
{
|
||||
var list = new List<CategoryLine>();
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using var ip = new StreamReader(path);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
list.Add(new CategoryLine(line));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,247 +1,274 @@
|
|||
using System;
|
||||
using Server.Commands;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Gumps
|
||||
{
|
||||
public class CategorizedAddGump : Gump
|
||||
{
|
||||
public static bool OldStyle = PropsConfig.OldStyle;
|
||||
|
||||
public static readonly int EntryHeight = 24; // PropsConfig.EntryHeight;
|
||||
|
||||
public static readonly int OffsetSize = PropsConfig.OffsetSize;
|
||||
public static readonly int BorderSize = PropsConfig.BorderSize;
|
||||
|
||||
public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX;
|
||||
public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY;
|
||||
|
||||
public static readonly int TextHue = PropsConfig.TextHue;
|
||||
public static readonly int TextOffsetX = PropsConfig.TextOffsetX;
|
||||
|
||||
public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID;
|
||||
public static readonly int HeaderGumpID = PropsConfig.HeaderGumpID;
|
||||
public static readonly int EntryGumpID = PropsConfig.EntryGumpID;
|
||||
public static readonly int BackGumpID = PropsConfig.BackGumpID;
|
||||
public static readonly int SetGumpID = PropsConfig.SetGumpID;
|
||||
|
||||
public static readonly int SetWidth = PropsConfig.SetWidth;
|
||||
|
||||
public static readonly int SetOffsetX = PropsConfig.SetOffsetX,
|
||||
SetOffsetY = PropsConfig.SetOffsetY + (EntryHeight - 20) / 2 / 2;
|
||||
|
||||
public static readonly int SetButtonID1 = PropsConfig.SetButtonID1;
|
||||
public static readonly int SetButtonID2 = PropsConfig.SetButtonID2;
|
||||
|
||||
public static readonly int PrevWidth = PropsConfig.PrevWidth;
|
||||
|
||||
public static readonly int PrevOffsetX = PropsConfig.PrevOffsetX,
|
||||
PrevOffsetY = PropsConfig.PrevOffsetY + (EntryHeight - 20) / 2 / 2;
|
||||
|
||||
public static readonly int PrevButtonID1 = PropsConfig.PrevButtonID1;
|
||||
public static readonly int PrevButtonID2 = PropsConfig.PrevButtonID2;
|
||||
|
||||
public static readonly int NextWidth = PropsConfig.NextWidth;
|
||||
|
||||
public static readonly int NextOffsetX = PropsConfig.NextOffsetX,
|
||||
NextOffsetY = PropsConfig.NextOffsetY + (EntryHeight - 20) / 2 / 2;
|
||||
|
||||
public static readonly int NextButtonID1 = PropsConfig.NextButtonID1;
|
||||
public static readonly int NextButtonID2 = PropsConfig.NextButtonID2;
|
||||
|
||||
private static readonly bool PrevLabel = false;
|
||||
private static readonly bool NextLabel = false;
|
||||
|
||||
private static readonly int PrevLabelOffsetX = PrevWidth + 1;
|
||||
private static readonly int PrevLabelOffsetY = 0;
|
||||
|
||||
private static readonly int NextLabelOffsetX = -29;
|
||||
private static readonly int NextLabelOffsetY = 0;
|
||||
|
||||
private static readonly int EntryWidth = 180;
|
||||
private static readonly int EntryCount = 15;
|
||||
|
||||
private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize;
|
||||
private static readonly int TotalHeight = OffsetSize + (EntryHeight + OffsetSize) * (EntryCount + 1);
|
||||
|
||||
private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize;
|
||||
private static readonly int BackHeight = BorderSize + TotalHeight + BorderSize;
|
||||
private readonly CAGCategory m_Category;
|
||||
|
||||
private readonly Mobile m_Owner;
|
||||
private int m_Page;
|
||||
|
||||
public CategorizedAddGump(Mobile owner) : this(owner, CAGCategory.Root)
|
||||
{
|
||||
}
|
||||
|
||||
public CategorizedAddGump(Mobile owner, CAGCategory category, int page = 0) : base(GumpOffsetX, GumpOffsetY)
|
||||
{
|
||||
owner.CloseGump<WhoGump>();
|
||||
|
||||
m_Owner = owner;
|
||||
m_Category = category;
|
||||
|
||||
Initialize(page);
|
||||
}
|
||||
|
||||
public void Initialize(int page)
|
||||
{
|
||||
m_Page = page;
|
||||
|
||||
CAGNode[] nodes = m_Category.Nodes;
|
||||
|
||||
int count = Math.Clamp(nodes.Length - page * EntryCount, 0, EntryCount);
|
||||
|
||||
int totalHeight = OffsetSize + (EntryHeight + OffsetSize) * (count + 1);
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(0, 0, BackWidth, BorderSize + totalHeight + BorderSize, BackGumpID);
|
||||
AddImageTiled(BorderSize, BorderSize, TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0), totalHeight,
|
||||
OffsetGumpID);
|
||||
|
||||
int x = BorderSize + OffsetSize;
|
||||
int y = BorderSize + OffsetSize;
|
||||
|
||||
if (OldStyle)
|
||||
AddImageTiled(x, y, TotalWidth - OffsetSize * 3 - SetWidth, EntryHeight, HeaderGumpID);
|
||||
else
|
||||
AddImageTiled(x, y, PrevWidth, EntryHeight, HeaderGumpID);
|
||||
|
||||
if (m_Category.Parent != null)
|
||||
{
|
||||
AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, 1);
|
||||
|
||||
if (PrevLabel)
|
||||
AddLabel(x + PrevLabelOffsetX, y + PrevLabelOffsetY, TextHue, "Previous");
|
||||
}
|
||||
|
||||
x += PrevWidth + OffsetSize;
|
||||
|
||||
int emptyWidth = TotalWidth - PrevWidth * 2 - NextWidth - OffsetSize * 5 -
|
||||
(OldStyle ? SetWidth + OffsetSize : 0);
|
||||
|
||||
if (!OldStyle)
|
||||
AddImageTiled(x - (OldStyle ? OffsetSize : 0), y, emptyWidth + (OldStyle ? OffsetSize * 2 : 0), EntryHeight,
|
||||
EntryGumpID);
|
||||
|
||||
AddHtml(x + TextOffsetX, y + (EntryHeight - 20) / 2, emptyWidth - TextOffsetX, EntryHeight,
|
||||
$"<center>{m_Category.Title}</center>");
|
||||
|
||||
x += emptyWidth + OffsetSize;
|
||||
|
||||
if (OldStyle)
|
||||
AddImageTiled(x, y, TotalWidth - OffsetSize * 3 - SetWidth, EntryHeight, HeaderGumpID);
|
||||
else
|
||||
AddImageTiled(x, y, PrevWidth, EntryHeight, HeaderGumpID);
|
||||
|
||||
if (page > 0)
|
||||
{
|
||||
AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, 2);
|
||||
|
||||
if (PrevLabel)
|
||||
AddLabel(x + PrevLabelOffsetX, y + PrevLabelOffsetY, TextHue, "Previous");
|
||||
}
|
||||
|
||||
x += PrevWidth + OffsetSize;
|
||||
|
||||
if (!OldStyle)
|
||||
AddImageTiled(x, y, NextWidth, EntryHeight, HeaderGumpID);
|
||||
|
||||
if ((page + 1) * EntryCount < nodes.Length)
|
||||
{
|
||||
AddButton(x + NextOffsetX, y + NextOffsetY, NextButtonID1, NextButtonID2, 3, GumpButtonType.Reply, 1);
|
||||
|
||||
if (NextLabel)
|
||||
AddLabel(x + NextLabelOffsetX, y + NextLabelOffsetY, TextHue, "Next");
|
||||
}
|
||||
|
||||
for (int i = 0, index = page * EntryCount; i < EntryCount && index < nodes.Length; ++i, ++index)
|
||||
{
|
||||
x = BorderSize + OffsetSize;
|
||||
y += EntryHeight + OffsetSize;
|
||||
|
||||
CAGNode node = nodes[index];
|
||||
|
||||
AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID);
|
||||
AddLabelCropped(x + TextOffsetX, y + (EntryHeight - 20) / 2, EntryWidth - TextOffsetX, EntryHeight, TextHue,
|
||||
node.Title);
|
||||
|
||||
x += EntryWidth + OffsetSize;
|
||||
|
||||
if (SetGumpID != 0)
|
||||
AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID);
|
||||
|
||||
AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, i + 4);
|
||||
|
||||
if (node is CAGObject obj)
|
||||
{
|
||||
int itemID = obj.ItemID;
|
||||
|
||||
Rectangle2D bounds = ItemBounds.Table[itemID];
|
||||
|
||||
if (itemID != 1 && bounds.Height < EntryHeight * 2)
|
||||
{
|
||||
if (bounds.Height < EntryHeight)
|
||||
AddItem(x - OffsetSize - 22 - i % 2 * 44 - bounds.Width / 2 - bounds.X,
|
||||
y + EntryHeight / 2 - bounds.Height / 2 - bounds.Y, itemID);
|
||||
else
|
||||
AddItem(x - OffsetSize - 22 - i % 2 * 44 - bounds.Width / 2 - bounds.X,
|
||||
y + EntryHeight - 1 - bounds.Height - bounds.Y, itemID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState state, RelayInfo info)
|
||||
{
|
||||
Mobile from = m_Owner;
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 0: // Closed
|
||||
{
|
||||
return;
|
||||
}
|
||||
case 1: // Up
|
||||
{
|
||||
if (m_Category.Parent != null)
|
||||
{
|
||||
int index = Array.IndexOf(m_Category.Parent.Nodes, m_Category) / EntryCount;
|
||||
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
|
||||
from.SendGump(new CategorizedAddGump(from, m_Category.Parent, index));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Previous
|
||||
{
|
||||
if (m_Page > 0)
|
||||
from.SendGump(new CategorizedAddGump(from, m_Category, m_Page - 1));
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Next
|
||||
{
|
||||
if ((m_Page + 1) * EntryCount < m_Category.Nodes.Length)
|
||||
from.SendGump(new CategorizedAddGump(from, m_Category, m_Page + 1));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
int index = m_Page * EntryCount + (info.ButtonID - 4);
|
||||
|
||||
if (index >= 0 && index < m_Category.Nodes.Length)
|
||||
m_Category.Nodes[index].OnClick(from, m_Page);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using Server.Commands;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Gumps
|
||||
{
|
||||
public class CategorizedAddGump : Gump
|
||||
{
|
||||
public static bool OldStyle = PropsConfig.OldStyle;
|
||||
|
||||
public static readonly int EntryHeight = 24; // PropsConfig.EntryHeight;
|
||||
|
||||
public static readonly int OffsetSize = PropsConfig.OffsetSize;
|
||||
public static readonly int BorderSize = PropsConfig.BorderSize;
|
||||
|
||||
public static readonly int GumpOffsetX = PropsConfig.GumpOffsetX;
|
||||
public static readonly int GumpOffsetY = PropsConfig.GumpOffsetY;
|
||||
|
||||
public static readonly int TextHue = PropsConfig.TextHue;
|
||||
public static readonly int TextOffsetX = PropsConfig.TextOffsetX;
|
||||
|
||||
public static readonly int OffsetGumpID = PropsConfig.OffsetGumpID;
|
||||
public static readonly int HeaderGumpID = PropsConfig.HeaderGumpID;
|
||||
public static readonly int EntryGumpID = PropsConfig.EntryGumpID;
|
||||
public static readonly int BackGumpID = PropsConfig.BackGumpID;
|
||||
public static readonly int SetGumpID = PropsConfig.SetGumpID;
|
||||
|
||||
public static readonly int SetWidth = PropsConfig.SetWidth;
|
||||
|
||||
public static readonly int SetOffsetX = PropsConfig.SetOffsetX,
|
||||
SetOffsetY = PropsConfig.SetOffsetY + (EntryHeight - 20) / 2 / 2;
|
||||
|
||||
public static readonly int SetButtonID1 = PropsConfig.SetButtonID1;
|
||||
public static readonly int SetButtonID2 = PropsConfig.SetButtonID2;
|
||||
|
||||
public static readonly int PrevWidth = PropsConfig.PrevWidth;
|
||||
|
||||
public static readonly int PrevOffsetX = PropsConfig.PrevOffsetX,
|
||||
PrevOffsetY = PropsConfig.PrevOffsetY + (EntryHeight - 20) / 2 / 2;
|
||||
|
||||
public static readonly int PrevButtonID1 = PropsConfig.PrevButtonID1;
|
||||
public static readonly int PrevButtonID2 = PropsConfig.PrevButtonID2;
|
||||
|
||||
public static readonly int NextWidth = PropsConfig.NextWidth;
|
||||
|
||||
public static readonly int NextOffsetX = PropsConfig.NextOffsetX,
|
||||
NextOffsetY = PropsConfig.NextOffsetY + (EntryHeight - 20) / 2 / 2;
|
||||
|
||||
public static readonly int NextButtonID1 = PropsConfig.NextButtonID1;
|
||||
public static readonly int NextButtonID2 = PropsConfig.NextButtonID2;
|
||||
|
||||
private static readonly bool PrevLabel = false;
|
||||
private static readonly bool NextLabel = false;
|
||||
|
||||
private static readonly int PrevLabelOffsetX = PrevWidth + 1;
|
||||
private static readonly int PrevLabelOffsetY = 0;
|
||||
|
||||
private static readonly int NextLabelOffsetX = -29;
|
||||
private static readonly int NextLabelOffsetY = 0;
|
||||
|
||||
private static readonly int EntryWidth = 180;
|
||||
private static readonly int EntryCount = 15;
|
||||
|
||||
private static readonly int TotalWidth = OffsetSize + EntryWidth + OffsetSize + SetWidth + OffsetSize;
|
||||
private static readonly int TotalHeight = OffsetSize + (EntryHeight + OffsetSize) * (EntryCount + 1);
|
||||
|
||||
private static readonly int BackWidth = BorderSize + TotalWidth + BorderSize;
|
||||
private static readonly int BackHeight = BorderSize + TotalHeight + BorderSize;
|
||||
private readonly CAGCategory m_Category;
|
||||
|
||||
private readonly Mobile m_Owner;
|
||||
private int m_Page;
|
||||
|
||||
public CategorizedAddGump(Mobile owner) : this(owner, CAGCategory.Root)
|
||||
{
|
||||
}
|
||||
|
||||
public CategorizedAddGump(Mobile owner, CAGCategory category, int page = 0) : base(GumpOffsetX, GumpOffsetY)
|
||||
{
|
||||
owner.CloseGump<WhoGump>();
|
||||
|
||||
m_Owner = owner;
|
||||
m_Category = category;
|
||||
|
||||
Initialize(page);
|
||||
}
|
||||
|
||||
public void Initialize(int page)
|
||||
{
|
||||
m_Page = page;
|
||||
|
||||
var nodes = m_Category.Nodes;
|
||||
|
||||
var count = Math.Clamp(nodes.Length - page * EntryCount, 0, EntryCount);
|
||||
|
||||
var totalHeight = OffsetSize + (EntryHeight + OffsetSize) * (count + 1);
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(0, 0, BackWidth, BorderSize + totalHeight + BorderSize, BackGumpID);
|
||||
AddImageTiled(
|
||||
BorderSize,
|
||||
BorderSize,
|
||||
TotalWidth - (OldStyle ? SetWidth + OffsetSize : 0),
|
||||
totalHeight,
|
||||
OffsetGumpID
|
||||
);
|
||||
|
||||
var x = BorderSize + OffsetSize;
|
||||
var y = BorderSize + OffsetSize;
|
||||
|
||||
if (OldStyle)
|
||||
AddImageTiled(x, y, TotalWidth - OffsetSize * 3 - SetWidth, EntryHeight, HeaderGumpID);
|
||||
else
|
||||
AddImageTiled(x, y, PrevWidth, EntryHeight, HeaderGumpID);
|
||||
|
||||
if (m_Category.Parent != null)
|
||||
{
|
||||
AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, 1);
|
||||
|
||||
if (PrevLabel)
|
||||
AddLabel(x + PrevLabelOffsetX, y + PrevLabelOffsetY, TextHue, "Previous");
|
||||
}
|
||||
|
||||
x += PrevWidth + OffsetSize;
|
||||
|
||||
var emptyWidth = TotalWidth - PrevWidth * 2 - NextWidth - OffsetSize * 5 -
|
||||
(OldStyle ? SetWidth + OffsetSize : 0);
|
||||
|
||||
if (!OldStyle)
|
||||
AddImageTiled(
|
||||
x - (OldStyle ? OffsetSize : 0),
|
||||
y,
|
||||
emptyWidth + (OldStyle ? OffsetSize * 2 : 0),
|
||||
EntryHeight,
|
||||
EntryGumpID
|
||||
);
|
||||
|
||||
AddHtml(
|
||||
x + TextOffsetX,
|
||||
y + (EntryHeight - 20) / 2,
|
||||
emptyWidth - TextOffsetX,
|
||||
EntryHeight,
|
||||
$"<center>{m_Category.Title}</center>"
|
||||
);
|
||||
|
||||
x += emptyWidth + OffsetSize;
|
||||
|
||||
if (OldStyle)
|
||||
AddImageTiled(x, y, TotalWidth - OffsetSize * 3 - SetWidth, EntryHeight, HeaderGumpID);
|
||||
else
|
||||
AddImageTiled(x, y, PrevWidth, EntryHeight, HeaderGumpID);
|
||||
|
||||
if (page > 0)
|
||||
{
|
||||
AddButton(x + PrevOffsetX, y + PrevOffsetY, PrevButtonID1, PrevButtonID2, 2);
|
||||
|
||||
if (PrevLabel)
|
||||
AddLabel(x + PrevLabelOffsetX, y + PrevLabelOffsetY, TextHue, "Previous");
|
||||
}
|
||||
|
||||
x += PrevWidth + OffsetSize;
|
||||
|
||||
if (!OldStyle)
|
||||
AddImageTiled(x, y, NextWidth, EntryHeight, HeaderGumpID);
|
||||
|
||||
if ((page + 1) * EntryCount < nodes.Length)
|
||||
{
|
||||
AddButton(x + NextOffsetX, y + NextOffsetY, NextButtonID1, NextButtonID2, 3, GumpButtonType.Reply, 1);
|
||||
|
||||
if (NextLabel)
|
||||
AddLabel(x + NextLabelOffsetX, y + NextLabelOffsetY, TextHue, "Next");
|
||||
}
|
||||
|
||||
for (int i = 0, index = page * EntryCount; i < EntryCount && index < nodes.Length; ++i, ++index)
|
||||
{
|
||||
x = BorderSize + OffsetSize;
|
||||
y += EntryHeight + OffsetSize;
|
||||
|
||||
var node = nodes[index];
|
||||
|
||||
AddImageTiled(x, y, EntryWidth, EntryHeight, EntryGumpID);
|
||||
AddLabelCropped(
|
||||
x + TextOffsetX,
|
||||
y + (EntryHeight - 20) / 2,
|
||||
EntryWidth - TextOffsetX,
|
||||
EntryHeight,
|
||||
TextHue,
|
||||
node.Title
|
||||
);
|
||||
|
||||
x += EntryWidth + OffsetSize;
|
||||
|
||||
if (SetGumpID != 0)
|
||||
AddImageTiled(x, y, SetWidth, EntryHeight, SetGumpID);
|
||||
|
||||
AddButton(x + SetOffsetX, y + SetOffsetY, SetButtonID1, SetButtonID2, i + 4);
|
||||
|
||||
if (node is CAGObject obj)
|
||||
{
|
||||
var itemID = obj.ItemID;
|
||||
|
||||
var bounds = ItemBounds.Table[itemID];
|
||||
|
||||
if (itemID != 1 && bounds.Height < EntryHeight * 2)
|
||||
{
|
||||
if (bounds.Height < EntryHeight)
|
||||
AddItem(
|
||||
x - OffsetSize - 22 - i % 2 * 44 - bounds.Width / 2 - bounds.X,
|
||||
y + EntryHeight / 2 - bounds.Height / 2 - bounds.Y,
|
||||
itemID
|
||||
);
|
||||
else
|
||||
AddItem(
|
||||
x - OffsetSize - 22 - i % 2 * 44 - bounds.Width / 2 - bounds.X,
|
||||
y + EntryHeight - 1 - bounds.Height - bounds.Y,
|
||||
itemID
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState state, RelayInfo info)
|
||||
{
|
||||
var from = m_Owner;
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 0: // Closed
|
||||
{
|
||||
return;
|
||||
}
|
||||
case 1: // Up
|
||||
{
|
||||
if (m_Category.Parent != null)
|
||||
{
|
||||
var index = Array.IndexOf(m_Category.Parent.Nodes, m_Category) / EntryCount;
|
||||
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
|
||||
from.SendGump(new CategorizedAddGump(from, m_Category.Parent, index));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Previous
|
||||
{
|
||||
if (m_Page > 0)
|
||||
from.SendGump(new CategorizedAddGump(from, m_Category, m_Page - 1));
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Next
|
||||
{
|
||||
if ((m_Page + 1) * EntryCount < m_Category.Nodes.Length)
|
||||
from.SendGump(new CategorizedAddGump(from, m_Category, m_Page + 1));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var index = m_Page * EntryCount + (info.ButtonID - 4);
|
||||
|
||||
if (index >= 0 && index < m_Category.Nodes.Length)
|
||||
m_Category.Nodes[index].OnClick(from, m_Page);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,152 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public struct TeleporterDefinition
|
||||
{
|
||||
[JsonPropertyName("src")]
|
||||
public WorldLocation Source { get; set; }
|
||||
|
||||
[JsonPropertyName("dst")]
|
||||
public WorldLocation Destination { get; set; }
|
||||
|
||||
[JsonPropertyName("back")]
|
||||
public bool Back { get; set; }
|
||||
|
||||
public override string ToString() => $"{{{Source},{Destination},{Back}}}";
|
||||
|
||||
public bool Equals(TeleporterDefinition other) =>
|
||||
Source.Equals(other.Source) && Destination.Equals(other.Destination) && Back == other.Back;
|
||||
|
||||
public override bool Equals(object obj) => obj is TeleporterDefinition other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Source, Destination, Back);
|
||||
}
|
||||
|
||||
public static class GenTeleporter
|
||||
{
|
||||
private const int SuccessHue = 72, WarningHue = 53, ErrorHue = 33;
|
||||
private static readonly string TeleporterJsonDataPath = Path.Combine("Data", "teleporters.json");
|
||||
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("TelGen", AccessLevel.Administrator, GenTeleporter_OnCommand);
|
||||
CommandSystem.Register("TelGenDelete", AccessLevel.Administrator, TelGenDelete_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("TelGenDelete")]
|
||||
[Description("Destroys world/dungeon teleporters for all facets.")]
|
||||
public static void TelGenDelete_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
from.SendMessage("Removing teleporters, please wait.");
|
||||
int count = 0;
|
||||
|
||||
void ProcessDeletion(TeleporterDefinition x)
|
||||
{
|
||||
count += TeleportersCreator.DeleteTeleporters(x.Source);
|
||||
if (x.Back) count += TeleportersCreator.DeleteTeleporters(x.Destination);
|
||||
}
|
||||
|
||||
if (!ProcessTeleporterData(from, ProcessDeletion))
|
||||
{
|
||||
if (count > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion, {count} Teleporters Removed.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage(WarningHue, $"{count} Teleporters Removed.");
|
||||
}
|
||||
|
||||
[Usage("TelGen")]
|
||||
[Description("Generates world/dungeon teleporters for all facets.")]
|
||||
public static void GenTeleporter_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
from.SendMessage("Generating teleporters, please wait.");
|
||||
TeleportersCreator c = new TeleportersCreator();
|
||||
|
||||
if (!ProcessTeleporterData(from, c.CreateTeleporter))
|
||||
{
|
||||
if (c.DelCount > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion: {c.DelCount} Teleporters Removed.");
|
||||
if (c.Count > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion: {c.Count} Teleporters Added.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage(SuccessHue, "Teleporter generating complete.");
|
||||
from.SendMessage(WarningHue, $"{c.DelCount} Teleporters Removed.");
|
||||
from.SendMessage(SuccessHue, $"{c.Count} Teleporters Added.");
|
||||
}
|
||||
|
||||
private static bool ProcessTeleporterData(Mobile m, Action<TeleporterDefinition> processor)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json;
|
||||
using (StreamReader reader = new StreamReader(TeleporterJsonDataPath))
|
||||
json = reader.ReadToEnd();
|
||||
var teleporters = JsonSerializer.Deserialize<List<TeleporterDefinition>>(json, JsonOptions);
|
||||
for (int i = 0; i < teleporters.Count; i++)
|
||||
processor(teleporters[i]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
m.SendMessage(ErrorHue, $"Failed to load/process data file '{TeleporterJsonDataPath}'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class TeleportersCreator
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
public int DelCount { get; private set; }
|
||||
|
||||
private static bool IsWithinZ(int delta) => delta >= -12 && delta <= 12;
|
||||
|
||||
public static int DeleteTeleporters(WorldLocation worldLocation)
|
||||
{
|
||||
IPooledEnumerable<Teleporter> eable = worldLocation.Map.GetItemsInRange<Teleporter>(worldLocation, 0);
|
||||
var items = eable
|
||||
.Where(x => !(x is KeywordTeleporter || x is SkillTeleporter) && IsWithinZ(x.Z - worldLocation.Z));
|
||||
int count = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
count++;
|
||||
item.Delete();
|
||||
}
|
||||
eable.Free();
|
||||
return count;
|
||||
}
|
||||
|
||||
public void CreateTeleporter(TeleporterDefinition telDef)
|
||||
{
|
||||
DelCount += DeleteTeleporters(telDef.Source);
|
||||
Count++;
|
||||
new Teleporter(telDef.Destination, telDef.Destination.Map).MoveToWorld(telDef.Source, telDef.Source.Map);
|
||||
if (!telDef.Back) return;
|
||||
DelCount += DeleteTeleporters(telDef.Destination);
|
||||
Count++;
|
||||
new Teleporter(telDef.Source, telDef.Source.Map).MoveToWorld(telDef.Destination, telDef.Destination.Map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public struct TeleporterDefinition
|
||||
{
|
||||
[JsonPropertyName("src")] public WorldLocation Source { get; set; }
|
||||
|
||||
[JsonPropertyName("dst")] public WorldLocation Destination { get; set; }
|
||||
|
||||
[JsonPropertyName("back")] public bool Back { get; set; }
|
||||
|
||||
public override string ToString() => $"{{{Source},{Destination},{Back}}}";
|
||||
|
||||
public bool Equals(TeleporterDefinition other) =>
|
||||
Source.Equals(other.Source) && Destination.Equals(other.Destination) && Back == other.Back;
|
||||
|
||||
public override bool Equals(object obj) => obj is TeleporterDefinition other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Source, Destination, Back);
|
||||
}
|
||||
|
||||
public static class GenTeleporter
|
||||
{
|
||||
private const int SuccessHue = 72, WarningHue = 53, ErrorHue = 33;
|
||||
private static readonly string TeleporterJsonDataPath = Path.Combine("Data", "teleporters.json");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("TelGen", AccessLevel.Administrator, GenTeleporter_OnCommand);
|
||||
CommandSystem.Register("TelGenDelete", AccessLevel.Administrator, TelGenDelete_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("TelGenDelete")]
|
||||
[Description("Destroys world/dungeon teleporters for all facets.")]
|
||||
public static void TelGenDelete_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
var from = e.Mobile;
|
||||
|
||||
from.SendMessage("Removing teleporters, please wait.");
|
||||
var count = 0;
|
||||
|
||||
void ProcessDeletion(TeleporterDefinition x)
|
||||
{
|
||||
count += TeleportersCreator.DeleteTeleporters(x.Source);
|
||||
if (x.Back) count += TeleportersCreator.DeleteTeleporters(x.Destination);
|
||||
}
|
||||
|
||||
if (!ProcessTeleporterData(from, ProcessDeletion))
|
||||
{
|
||||
if (count > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion, {count} Teleporters Removed.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage(WarningHue, $"{count} Teleporters Removed.");
|
||||
}
|
||||
|
||||
[Usage("TelGen")]
|
||||
[Description("Generates world/dungeon teleporters for all facets.")]
|
||||
public static void GenTeleporter_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
var from = e.Mobile;
|
||||
|
||||
from.SendMessage("Generating teleporters, please wait.");
|
||||
var c = new TeleportersCreator();
|
||||
|
||||
if (!ProcessTeleporterData(from, c.CreateTeleporter))
|
||||
{
|
||||
if (c.DelCount > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion: {c.DelCount} Teleporters Removed.");
|
||||
if (c.Count > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion: {c.Count} Teleporters Added.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage(SuccessHue, "Teleporter generating complete.");
|
||||
from.SendMessage(WarningHue, $"{c.DelCount} Teleporters Removed.");
|
||||
from.SendMessage(SuccessHue, $"{c.Count} Teleporters Added.");
|
||||
}
|
||||
|
||||
private static bool ProcessTeleporterData(Mobile m, Action<TeleporterDefinition> processor)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json;
|
||||
using (var reader = new StreamReader(TeleporterJsonDataPath))
|
||||
{
|
||||
json = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
var teleporters = JsonSerializer.Deserialize<List<TeleporterDefinition>>(json, JsonOptions);
|
||||
for (var i = 0; i < teleporters.Count; i++)
|
||||
processor(teleporters[i]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
m.SendMessage(ErrorHue, $"Failed to load/process data file '{TeleporterJsonDataPath}'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class TeleportersCreator
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
public int DelCount { get; private set; }
|
||||
|
||||
private static bool IsWithinZ(int delta) => delta >= -12 && delta <= 12;
|
||||
|
||||
public static int DeleteTeleporters(WorldLocation worldLocation)
|
||||
{
|
||||
var eable = worldLocation.Map.GetItemsInRange<Teleporter>(worldLocation, 0);
|
||||
var items = eable
|
||||
.Where(x => !(x is KeywordTeleporter || x is SkillTeleporter) && IsWithinZ(x.Z - worldLocation.Z));
|
||||
var count = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
count++;
|
||||
item.Delete();
|
||||
}
|
||||
|
||||
eable.Free();
|
||||
return count;
|
||||
}
|
||||
|
||||
public void CreateTeleporter(TeleporterDefinition telDef)
|
||||
{
|
||||
DelCount += DeleteTeleporters(telDef.Source);
|
||||
Count++;
|
||||
new Teleporter(telDef.Destination, telDef.Destination.Map).MoveToWorld(telDef.Source, telDef.Source.Map);
|
||||
if (!telDef.Back) return;
|
||||
DelCount += DeleteTeleporters(telDef.Destination);
|
||||
Count++;
|
||||
new Teleporter(telDef.Source, telDef.Source.Map).MoveToWorld(telDef.Destination, telDef.Destination.Map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue