#region Header // _,-'/-'/ // . __,-; ,'( '/ // \. `-.__`-._`:_,-._ _ , . `` // `:-._,------' ` _,`--` -: `_ , ` ,' : // `---..__,,--' (C) 2023 ` -'. -' // # Vita-Nex [http://core.vita-nex.com] # // {o)xxx|===============- # -===============|xxx(o} // # # #endregion #region References using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using Server; using Server.Commands; using Server.Gumps; using VitaNex.IO; using VitaNex.Network; using VitaNex.SuperGumps; using VitaNex.SuperGumps.UI; #endregion namespace VitaNex { public static class TaskPriority { public const int Highest = 0, High = 250000, Medium = 500000, Low = 750000, Lowest = 1000000; } /// /// Exposes an interface for managing VitaNexCore and its' sub-systems. /// public static partial class VitaNexCore { public const AccessLevel Access = AccessLevel.Administrator; public static VersionInfo Version { get; } = new VersionInfo(5, 3, 1, 0) { Name = "VitaNexCore", Description = "Represents the local version value of Vita-Nex: Core" }; private static readonly CallPriorityComparer _PriorityComparer = new CallPriorityComparer(); public static readonly object ConsoleLock = new object(); public static readonly object IOLock = new object(); private static readonly DateTime _Started = DateTime.UtcNow; public static long Ticks { get { if (Stopwatch.IsHighResolution && !Core.Unix) { return (long)(Stopwatch.GetTimestamp() * (1000.0 / Stopwatch.Frequency)); } return (long)(DateTime.UtcNow.Ticks * (1000.0 / TimeSpan.TicksPerSecond)); } } private static long _Tick = Ticks; public static long Tick => _Tick; private static readonly List _Plugins = new List(0x20); public static IEnumerable Plugins { get { var idx = _Plugins.Count; while (--idx >= 0) { if (_Plugins.InBounds(idx)) { yield return _Plugins[idx]; } } } } public static int PluginCount => _Plugins.Count; /// /// Gets the amount of time that has passed since VitaNexCore was first initialized. /// public static TimeSpan UpTime => DateTime.UtcNow - _Started; /// /// Gets the root directory for VitaNexCore. /// This is the directory where the core scripts reside. /// public static DirectoryInfo RootDirectory { get; private set; } /// /// Gets the working directory for VitaNexCore. /// public static DirectoryInfo BaseDirectory { get; private set; } /// /// Gets the build directory for VitaNexCore /// public static DirectoryInfo BuildDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Build/"); /// /// Gets the data directory for VitaNexCore. /// public static DirectoryInfo DataDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Data/"); /// /// Gets the cache directory for VitaNexCore /// public static DirectoryInfo CacheDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Cache/"); /// /// Gets the services directory for VitaNexCore /// public static DirectoryInfo ServicesDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Services/"); /// /// Gets the modules directory for VitaNexCore /// public static DirectoryInfo ModulesDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Modules/"); /// /// Gets the saves backup directory for VitaNexCore /// public static DirectoryInfo BackupDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Backups/"); /// /// Gets the saves directory for VitaNexCore /// public static DirectoryInfo SavesDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Saves/"); /// /// Gets the logs directory for VitaNexCore /// public static DirectoryInfo LogsDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Logs/"); /// /// Gets a file used for unhandled and generec exception logging. /// public static FileInfo LogFile => IOUtility.EnsureFile(LogsDirectory + "/Logs (" + DateTime.Now.ToSimpleString("D d M y") + ").log"); /// /// Gets a value representing whether VitaNexCore is busy performing a save or load action /// public static bool Busy { get; private set; } /// /// Gets a value representing the compile state of VitaNexCore /// public static bool Compiled { get; private set; } /// /// Gets a value representing the configure state of VitaNexCore /// public static bool Configured { get; private set; } /// /// Gets a value representing the initialize state of VitaNexCore /// public static bool Initialized { get; private set; } /// /// Gets a value representing whether this run is the first boot of VitaNexCore /// public static bool FirstBoot { get; private set; } public static bool Disposing { get; private set; } public static bool Disposed { get; private set; } public static bool Crashed { get; private set; } public static TimeSpan BackupExpireAge { get; set; } public static event Action OnCompiled; public static event Action OnConfigured; public static event Action OnInitialized; public static event Action OnBackup; public static event Action OnSaved; public static event Action OnLoaded; public static event Action OnDispose; public static event Action OnDisposed; public static event Action OnExceptionThrown; /// /// Configure method entry point, called by ScriptCompiler during compile of 'Scripts' directory. /// Performs a global invoke action, processing all Services and Modules that support CSConfig() and CMConfig() /// [CallPriority(Int32.MaxValue)] public static void Configure() { if (Configured) { return; } /// DisplayRetroBoot(); CommandUtility.Register("VNC", AccessLevel.Administrator, OnCoreCommand); OutgoingPacketOverrides.Init(); ExtendedOPL.Init(); var now = DateTime.UtcNow; ToConsole(String.Empty); ToConsole("Compile action started..."); TryCatch(CompileServices, ToConsole); TryCatch(CompileModules, ToConsole); Compiled = true; InvokeByPriority(OnCompiled); var time = (DateTime.UtcNow - now).TotalSeconds; ToConsole("Compile action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty); now = DateTime.UtcNow; ToConsole(String.Empty); ToConsole("Configure action started..."); TryCatch(ConfigureServices, ToConsole); TryCatch(ConfigureModules, ToConsole); Configured = true; InvokeByPriority(OnConfigured); time = (DateTime.UtcNow - now).TotalSeconds; ToConsole("Configure action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty); ProcessINIT(); EventSink.ServerStarted += () => { EventSink.WorldSave += e => { TryCatch(Backup, ToConsole); TryCatch(Save, ToConsole); }; EventSink.Shutdown += e => TryCatch(Dispose, ToConsole); EventSink.Crashed += e => TryCatch(Dispose, ToConsole); }; try { var crashed = typeof(EventSink).GetEventDelegates("Crashed"); foreach (var m in crashed.OfType()) { EventSink.Crashed -= m; } EventSink.Crashed += e => Crashed = true; foreach (var m in crashed.OfType()) { EventSink.Crashed += m; } } catch (Exception x) { ToConsole(x); EventSink.Crashed += e => Crashed = true; } } /// /// Initialize method entry point, called by ScriptCompiler after compile of 'Scripts' directory. /// Performs a global invoke action, processing all Services and Modules that support CSInvoke() and CMInvoke() /// [CallPriority(Int32.MaxValue)] public static void Initialize() { if (Initialized) { return; } TryCatch(Load, ToConsole); var now = DateTime.UtcNow; ToConsole(String.Empty); ToConsole("Invoke action started..."); TryCatch(InvokeServices, ToConsole); TryCatch(InvokeModules, ToConsole); Initialized = true; InvokeByPriority(OnInitialized); var time = (DateTime.UtcNow - now).TotalSeconds; ToConsole("Invoke action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty); } /// /// Performs a global save action, processing all Services and Modules that support CSSave() and CMSave() /// public static void Save() { if (Busy) { ToConsole("Could not perform save action, the service is busy."); return; } Busy = true; var now = DateTime.UtcNow; try { ToConsole(String.Empty); ToConsole("Save action started..."); TryCatch(SaveServices, ToConsole); TryCatch(SaveModules, ToConsole); InvokeByPriority(OnSaved); } finally { Busy = false; } var time = (DateTime.UtcNow - now).TotalSeconds; ToConsole("Save action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty); } /// /// Performs a global load action, processing all Services and Modules that support CSLoad() and CMLoad() /// public static void Load() { if (Busy) { ToConsole("Could not perform load action, the service is busy."); return; } Busy = true; var now = DateTime.UtcNow; try { ToConsole(String.Empty); ToConsole("Load action started..."); TryCatch(LoadServices, ToConsole); TryCatch(LoadModules, ToConsole); InvokeByPriority(OnLoaded); } finally { Busy = false; } var time = (DateTime.UtcNow - now).TotalSeconds; ToConsole("Load action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty); } /// /// Performs a global dispose action, processing all Services and Modules that support CSDispose() and CMDispose() /// public static void Dispose() { if (Busy || Disposing || Disposed) { ToConsole("Could not perform dispose action, the service is busy."); return; } Busy = Disposing = Disposed = true; var now = DateTime.UtcNow; try { ToConsole(String.Empty); ToConsole("Dispose action started..."); InvokeByPriority(OnDispose); TryCatch(DisposeServices, ToConsole); TryCatch(DisposeModules, ToConsole); InvokeByPriority(OnDisposed); } finally { Busy = Disposing = false; } var time = (DateTime.UtcNow - now).TotalSeconds; ToConsole("Dispose action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty); } /// /// Performs a global backup action, copying all files in the SavesDirectory to the BackupDirectory. /// public static void Backup() { if (Busy) { ToConsole("Could not perform backup action, the service is busy."); return; } Busy = true; var now = DateTime.UtcNow; try { ToConsole(String.Empty); ToConsole("Backup action started..."); if (BackupExpireAge > TimeSpan.Zero) { ToConsole("Backup Expire Age: {0}", BackupExpireAge); lock (IOLock) { BackupDirectory.EmptyDirectory(BackupExpireAge); } } lock (IOLock) { SavesDirectory.CopyDirectory( IOUtility.EnsureDirectory(BackupDirectory + "/" + DateTime.Now.ToSimpleString("D d M y"), true)); } InvokeByPriority(OnBackup); } finally { Busy = false; } var time = (DateTime.UtcNow - now).TotalSeconds; ToConsole("Backup action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty); } private static void OnCoreCommand(CommandEventArgs e) { if (e == null || e.Mobile == null || e.Mobile.Deleted) { return; } var cmd = e.GetString(0); var search = e.GetString(1); switch (cmd.ToLower()) { case "srv": { var cs = !String.IsNullOrWhiteSpace(search) ? Services.FirstOrDefault(o => Insensitive.Contains(o.Name, search)) : null; VitaNexCoreUI.DisplayTo(e.Mobile, cs); } break; case "mod": { var cm = !String.IsNullOrWhiteSpace(search) ? Modules.FirstOrDefault(o => Insensitive.Contains(o.Name, search)) : null; VitaNexCoreUI.DisplayTo(e.Mobile, cm); } break; case "plg": { var cp = !String.IsNullOrWhiteSpace(search) ? Plugins.FirstOrDefault(o => Insensitive.Contains(o.Name, search)) : null; VitaNexCoreUI.DisplayTo(e.Mobile, cp); } break; default: VitaNexCoreUI.DisplayTo(e.Mobile); break; } } private static void InvokeByPriority(Delegate action) { if (action == null) { return; } var list = action.GetInvocationList(); foreach (var d in list.OrderBy(d => d.Method, _PriorityComparer)) { TryCatch(d, ToConsole); } } public static T TryCatchGet(Func func) { return TryCatchGet(func, ToConsole); } public static T TryCatchGet(Func func, Action handler) { if (func == null) { return default(T); } try { return func(); } catch (Exception e) { if (handler == null) { ToConsole("{0} at {1}:", e.GetType(), Trace(func)); } else { handler(e); } } return default(T); } public static T TryCatchGet(Func func, TState state) { return TryCatchGet(func, state, ToConsole); } public static T TryCatchGet(Func func, TState state, Action handler) { if (func == null) { return default(T); } try { return func(state); } catch (Exception e) { if (handler == null) { ToConsole("{0} at {1}:", e.GetType(), Trace(func)); } else { handler(e); } } return default(T); } public static void TryCatch(Delegate action) { TryCatch(action, ToConsole); } public static void TryCatch(Delegate action, Action handler) { if (action == null) { return; } try { if (action.Method.IsStatic) { action.Method.Invoke(null, null); } else { action.Method.Invoke(action.Target, null); } } catch (Exception e) { if (handler == null) { ToConsole("{0} at {1}:", e.GetType(), Trace(action)); } else { handler(e); } } } public static void TryCatch(Action action) { TryCatch(action, ToConsole); } public static void TryCatch(Action action, Action handler) { if (action == null) { return; } try { action(); } catch (Exception e) { if (handler == null) { ToConsole("{0} at {1}:", e.GetType(), Trace(action)); } else { handler(e); } } } public static void TryCatch(Action action, T state) { TryCatch(action, state, ToConsole); } public static void TryCatch(Action action, T state, Action handler) { if (action == null) { return; } try { action(state); } catch (Exception e) { if (handler == null) { ToConsole("{0} at {1}:", e.GetType(), Trace(action)); } else { handler(e); } } } public static void WaitWhile(Func func) { WaitWhile(func, TimeSpan.MaxValue); } public static void WaitWhile(Func func, TimeSpan timeOut) { if (func == null) { return; } var expire = DateTime.UtcNow.Add(timeOut); var exit = false; while (!exit) { exit = !func(); if (DateTime.UtcNow >= expire) { break; } Thread.Sleep(1); } } public static void ToConsole(string format, params object[] args) { lock (ConsoleLock) { Console.Write('['); Utility.PushColor(ConsoleColor.Yellow); Console.Write("VitaNexCore"); Utility.PopColor(); Console.Write("]: "); Utility.PushColor(ConsoleColor.DarkYellow); if (args.Length > 0) { Console.WriteLine(format, args); } else { Console.WriteLine(format); } Utility.PopColor(); } } public static void ToConsole(Exception e) { lock (ConsoleLock) { Console.Write('['); Utility.PushColor(ConsoleColor.Yellow); Console.Write("VitaNexCore"); Utility.PopColor(); Console.Write("]: "); Utility.PushColor(ConsoleColor.Red); Console.WriteLine(e); Utility.PopColor(); } e.Log(LogFile); InvokeByPriority(OnExceptionThrown); } public static string Trace(this Delegate d) { return Trace(d, false); } public static string Trace(this Delegate d, bool output) { if (d == null) { return "NULL"; } var value = ""; var dt = d.Method.DeclaringType; while (dt != null) { value = dt.Name + '.' + value; dt = dt.DeclaringType; } value += d.Method.Name; if (output) { ToConsole("Trace: {0}", value); } return value; } private const ConsoleColor _BackgroundColor = ConsoleColor.Black; private const ConsoleColor _BorderColor = ConsoleColor.Green; private const ConsoleColor _TextColor = ConsoleColor.White; private static void DrawLine(string text = "", int align = 0) { text = text ?? String.Empty; align = Math.Max(0, Math.Min(2, align)); var defBG = Console.BackgroundColor; const int borderWidth = 2; const int indentWidth = 1; var maxWidth = Math.Max(80, Console.WindowWidth) - ((borderWidth + indentWidth) * 2); var lines = new List(); if (text.Length > maxWidth) { var words = text.Split(' '); if (words.Length == 0) { for (int i = 0, offset = 0, count; i < (text.Length / maxWidth); i++) { lines.Add(text.Substring(offset, (count = Math.Min(text.Length - offset, maxWidth)))); offset += count; } } else { var rebuild = String.Empty; for (var wi = 0; wi < words.Length; wi++) { if (rebuild.Length + (words[wi].Length + 1) <= maxWidth) { rebuild += words[wi] + ' '; } else { lines.Add(rebuild); rebuild = words[wi] + ' '; } if (wi + 1 >= words.Length) { lines.Add(rebuild); } } } } else { lines.Add(text); } lock (ConsoleLock) { Utility.PushColor(_TextColor); foreach (var line in lines) { Console.BackgroundColor = _BorderColor; Console.Write(new string(' ', borderWidth)); Console.BackgroundColor = _BackgroundColor; Console.Write(new string(' ', indentWidth)); var len = maxWidth - line.Length; var str = line; switch (align) { //Center case 1: str = new string(' ', len / 2) + str + new string(' ', len / 2); break; //Right case 2: str = new string(' ', len) + str; break; } if (str.Length < maxWidth) { str += new string(' ', maxWidth - str.Length); } Console.Write(str); Console.Write(new string(' ', indentWidth)); Console.BackgroundColor = _BorderColor; Console.Write(new string(' ', borderWidth)); } lines.Free(true); Console.BackgroundColor = defBG; Utility.PopColor(); } } private static void DisplayRetroBoot() { ConsoleColor defBG; lock (ConsoleLock) { defBG = Console.BackgroundColor; Console.WriteLine(); Console.BackgroundColor = _BorderColor; Console.CursorLeft = 0; Console.Write(new string(' ', Math.Max(80, Console.WindowWidth))); } DrawLine(); DrawLine("**** VITA-NEX: CORE " + Version + " ****", 1); DrawLine(); DrawLine("Root Directory: " + RootDirectory.FullName.Replace(Core.BaseDirectory, ".")); DrawLine("Working Directory: " + BaseDirectory.FullName.Replace(Core.BaseDirectory, ".")); DrawLine(); DrawLine("http://core.vita-nex.com", 1); DrawLine(); if (FirstBoot) { try { var readme = IOUtility.GetSafeFilePath(RootDirectory + "/LICENSE.md", true); var lines = File.ReadAllLines(readme); lines.ForEach(line => DrawLine(line)); DrawLine(); } catch { } } if (Core.Debug) { DrawLine("Server is running in DEBUG mode."); DrawLine(); } lock (ConsoleLock) { Console.BackgroundColor = _BorderColor; Console.Write(new string(' ', Console.WindowWidth)); Console.BackgroundColor = defBG; Utility.PopColor(); Console.WriteLine(); } } public static void RegisterPlugin(ICorePluginInfo cp) { if (cp == null) { return; } _Plugins.Update(cp); TryCatch(cp.OnRegistered, cp.ToConsole); } } public sealed class VitaNexCoreUI : TreeGump { public static void DisplayTo(Mobile user, CoreServiceInfo cs) { var node = "Plugins|Services"; if (cs != null) { node += "|" + cs.FullName; } DisplayTo(user, false, node); } public static void DisplayTo(Mobile user, CoreModuleInfo cm) { var node = "Plugins|Modules"; if (cm != null) { node += "|" + cm.FullName; } DisplayTo(user, false, node); } public static void DisplayTo(Mobile user, ICorePluginInfo cp) { if (cp is CoreServiceInfo) { DisplayTo(user, (CoreServiceInfo)cp); return; } if (cp is CoreModuleInfo) { DisplayTo(user, (CoreModuleInfo)cp); return; } var node = "Plugins"; if (cp != null) { node += "|" + cp.FullName; } DisplayTo(user, false, node); } public static void DisplayTo(Mobile user) { DisplayTo(user, String.Empty); } public static void DisplayTo(Mobile user, string node) { DisplayTo(user, false, node); } public static void DisplayTo(Mobile user, bool refreshOnly) { DisplayTo(user, refreshOnly, String.Empty); } public static void DisplayTo(Mobile user, bool refreshOnly, string node) { if (user == null) { return; } if (user.AccessLevel < VitaNexCore.Access) { user.SendMessage(0x22, "You do not have access to this feature."); return; } var info = EnumerateInstances(user).FirstOrDefault(g => g != null && !g.IsDisposed && g.IsOpen); if (info == null) { if (refreshOnly) { return; } info = new VitaNexCoreUI(user); } if (!String.IsNullOrWhiteSpace(node)) { info.SelectedNode = node; } info.Refresh(true); } private List _Buffer; private int[,] _Indicies; private VitaNexCoreUI(Mobile user) : base(user) { _Buffer = new List(); _Indicies = new int[3, 2]; Width = 800; Height = 600; Title = "Vita-Nex: Core Control Panel"; } protected override void OnDispose() { _Buffer.Free(true); _Buffer = null; _Indicies = null; base.OnDispose(); } protected override bool OnBeforeSend() { if (base.OnBeforeSend() && User.AccessLevel >= VitaNexCore.Access) { return true; } User.SendMessage(0x22, "You do not have access to this feature."); return false; } protected override void CompileNodes(Dictionary> list) { list.Clear(); list["Core"] = CompileOverview; list["Plugins"] = CompilePlugins; list["Plugins|Services"] = CompileServices; list["Plugins|Modules"] = CompileModules; foreach (var p in VitaNexCore.Plugins) { var name = p.Name; if (String.IsNullOrWhiteSpace(name)) { name = p.TypeOf.Name; } if (p is CoreServiceInfo) { list["Plugins|Services|" + name] = CompileService; } else if (p is CoreModuleInfo) { list["Plugins|Modules|" + name] = CompileModule; } else { list["Plugins|" + name] = CompilePlugin; } } base.CompileNodes(list); } protected override void CompileEmptyNodeLayout( SuperGumpLayout layout, int x, int y, int w, int h, int index, TreeGumpNode node) { base.CompileEmptyNodeLayout(layout, x, y, w, h, index, node); layout.Add("node/page/" + index, () => CompileOverview(x, y, w, h)); } private void CompileOverview(Rectangle b, int i, TreeGumpNode n) { CompileOverview(b.X, b.Y, b.Width, b.Height); } private void CompileOverview(int x, int y, int w, int h) { var html = new StringBuilder(); html.AppendLine(); html.AppendLine("Vita-Nex: Core " + "{0}".WrapUOHtmlColor(Color.LawnGreen, HtmlColor), VitaNexCore.Version); html.AppendLine("A dynamic extension library for RunUO"); html.AppendLine("http://core.vita-nex.com"); html.AppendLine(); html.AppendLine("Services: " + "{0:#,0}".WrapUOHtmlColor(Color.LawnGreen, HtmlColor), VitaNexCore.ServiceCount); html.AppendLine("Modules: " + "{0:#,0}".WrapUOHtmlColor(Color.LawnGreen, HtmlColor), VitaNexCore.ModuleCount); html.AppendLine(); AddHtml(x + 5, y, w - 10, h, html.ToString().WrapUOHtmlColor(HtmlColor, false), false, true); } private void CompileService(Rectangle b, int i, TreeGumpNode n) { var cs = VitaNexCore.Services.FirstOrDefault(o => Insensitive.Equals(n.Name, o.Name)); if (cs != null) { CompileService(b.X, b.Y, b.Width, b.Height, cs); } } private void CompileService(int x, int y, int w, int h, CoreServiceInfo cs) { CompileBufferEntry(x, y, w, h, 0, cs); y += 75; h -= 75; cs.CompileControlPanel(this, x, y, w, h); } private void CompileModule(Rectangle b, int i, TreeGumpNode n) { var cm = VitaNexCore.Modules.FirstOrDefault(o => Insensitive.Equals(n.Name, o.Name)); if (cm != null) { CompileModule(b.X, b.Y, b.Width, b.Height, cm); } } private void CompileModule(int x, int y, int w, int h, CoreModuleInfo cm) { CompileBufferEntry(x, y, w, h, 0, cm); y += 75; h -= 75; cm.CompileControlPanel(this, x, y, w, h); } private void CompilePlugin(Rectangle b, int i, TreeGumpNode n) { var cp = VitaNexCore.Plugins.FirstOrDefault(o => Insensitive.Equals(n.Name, o.Name)); if (cp != null) { CompilePlugin(b.X, b.Y, b.Width, b.Height, cp); } } private void CompilePlugin(int x, int y, int w, int h, ICorePluginInfo cp) { CompileBufferEntry(x, y, w, h, 0, cp); y += 75; h -= 75; cp.CompileControlPanel(this, x, y, w, h); } private void CompileServices(Rectangle b, int i, TreeGumpNode n) { CompileBuffer(0, b.X, b.Y, b.Width, b.Height, VitaNexCore.Services); } private void CompileModules(Rectangle b, int i, TreeGumpNode n) { CompileBuffer(1, b.X, b.Y, b.Width, b.Height, VitaNexCore.Modules); } private void CompilePlugins(Rectangle b, int i, TreeGumpNode n) { CompileBuffer(2, b.X, b.Y, b.Width, b.Height, VitaNexCore.Plugins); } private void CompileBuffer(int i, int x, int y, int w, int h, IEnumerable plugins) { _Buffer.Clear(); _Buffer.AddRange(plugins); _Buffer.Sort(); _Indicies[i, 1] = _Buffer.Count; _Indicies[i, 0] = Math.Max(0, Math.Min(_Indicies[i, 1] - 1, _Indicies[i, 0])); var count = (int)Math.Floor(h / 55.0); var idx = 0; foreach (var cp in _Buffer.Skip(_Indicies[i, 0]).Take(count)) { CompileBufferEntry(x, y, w - 20, h, idx++, cp); } _Buffer.Clear(); AddScrollbarVM(x + (w - 15), y, h, _Indicies[i, 1], _Indicies[i, 0], (b, n) => { _Indicies[i, 0] -= n; Refresh(true); }, (b, n) => { _Indicies[i, 0] += n; Refresh(true); }); } private void CompileBufferEntry(int x, int y, int w, int h, int i, ICorePluginInfo cp) { if (w * h <= 0 || cp == null) { return; } var xx = x; var yy = y + (i * 57); AddRectangle(xx, yy, w, 55, Color.Black, cp.Active ? Color.PaleGoldenrod : Color.Silver, 1); xx += 5; yy += 5; var label = cp.Name.WrapUOHtmlColor(HtmlColor, false); AddHtml(xx + 5, yy, w - 80, 40, label, false, false); xx = x + (w - 60); label = cp.Version.ToString().WrapUOHtmlColor(HtmlColor, false); AddHtml(xx, yy, 55, 40, label, false, false); xx = x + 5; yy += 25; Color color, border, fill; if (cp.Disposed) { label = "DISPOSED".WrapUOHtmlBold().WrapUOHtmlCenter(); color = border = Color.OrangeRed; fill = Color.Black; AddRectangle(xx, yy, w - 10, 20, fill, border, 1); AddHtml(xx, yy, w, 20, label.WrapUOHtmlColor(color, false), false, false); return; } var bw = (w - 10) / 4; label = "CONFIG".WrapUOHtmlBold().WrapUOHtmlCenter(); color = border = Color.PaleGoldenrod; fill = Color.Black; AddHtmlButton(xx, yy, bw, 20, o => HandleConfig(cp), label, color, fill, border, 1); xx += bw; label = "DEBUG".WrapUOHtmlBold().WrapUOHtmlCenter(); color = border = cp.Debug ? Color.LawnGreen : Color.OrangeRed; AddHtmlButton(xx, yy, bw, 20, o => HandleDebug(cp), label, color, fill, border, 1); xx += bw; label = "QUIET".WrapUOHtmlBold().WrapUOHtmlCenter(); color = border = cp.Quiet ? Color.LawnGreen : Color.OrangeRed; AddHtmlButton(xx, yy, bw, 20, o => HandleQuiet(cp), label, color, fill, border, 1); xx += bw; label = "ACTIVE".WrapUOHtmlBold().WrapUOHtmlCenter(); color = border = cp.Active ? Color.LawnGreen : Color.OrangeRed; AddHtmlButton(xx, yy, bw, 20, o => HandleActive(cp), label, color, fill, border, 1); } private void HandleConfig(ICorePluginInfo cp) { Refresh(true); if (cp != null && !cp.Disposed) { User.SendGump(new PropertiesGump(User, cp)); } } private void HandleDebug(ICorePluginInfo cp) { if (cp != null && !cp.Disposed) { var old = cp.Debug; cp.Debug = !cp.Debug; if (cp.Debug != old) { User.SendMessage(85, "[{0}]: Debugging {1}", cp.Name, cp.Debug ? "enabled" : "disabled"); } } Refresh(true); } private void HandleQuiet(ICorePluginInfo cp) { if (cp != null && !cp.Disposed) { var old = cp.Quiet; cp.Quiet = !cp.Quiet; if (cp.Quiet != old) { User.SendMessage(85, "[{0}]: Using {1} output", cp.Name, cp.Quiet ? "simple" : "extended"); } } Refresh(true); } private void HandleActive(ICorePluginInfo cp) { if (cp != null && !cp.Disposed) { var old = cp.Active; cp.Active = !cp.Active; if (cp.Active != old) { User.SendMessage(85, "[{0}]: Now {1}", cp.Name, cp.Active ? "active" : "inactive"); } } Refresh(true); } } public interface ICorePluginInfo : IEquatable, IComparable { bool Active { get; set; } bool Configured { get; } bool Invoked { get; } bool Disposed { get; } Assembly DynamicAssembly { get; } FileInfo DynamicAssemblyFile { get; } bool Dynamic { get; } int Priority { get; } Type TypeOf { get; } VersionInfo Version { get; } string Name { get; } string FullName { get; } bool Debug { get; set; } bool Quiet { get; set; } void OnRegistered(); void SaveState(); void LoadState(); void SaveOptions(); void LoadOptions(); void ToConsole(string[] lines); void ToConsole(string format, params object[] args); void ToConsole(Exception[] errors); void ToConsole(Exception e); void CompileControlPanel(SuperGump g, int x, int y, int w, int h); } }