Formats UO Content (#201)

This commit is contained in:
Kamron Batman 2020-08-27 18:30:38 -07:00 • committed by GitHub
parent 86b7b3aed1
commit ad3775c4d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3249 changed files with 431083 additions and 440981 deletions

File diff suppressed because it is too large Load diff

View file

@ -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));
}
}
}
}

View file

@ -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));
}
}
}

View file

@ -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; }
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}
}
}

View file

@ -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();
}
}
}

View file

@ -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

View file

@ -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);
}
}
}
}