diff --git a/PathfinderAPI/Event/Menu/DrawMainMenuButtonEvent.cs b/PathfinderAPI/Event/Menu/DrawMainMenuButtonEvent.cs new file mode 100644 index 00000000..345268a3 --- /dev/null +++ b/PathfinderAPI/Event/Menu/DrawMainMenuButtonEvent.cs @@ -0,0 +1,213 @@ +using Hacknet; +using Hacknet.Gui; +using HarmonyLib; +using Microsoft.Xna.Framework; +using Mono.Cecil.Cil; +using MonoMod.Cil; + +namespace Pathfinder.Event.Menu; + +public enum MainMenuButtonType : int +{ + Undefined = -1, + Unknown = 0, + NewSession = 1, + Continue = 1102, + Login = 11, + Settings = 3, + StartRelayServer = 4, + Extensions = 5, + NewLabyrinthSession = 7, + Exit = 15, +} + +[HarmonyPatch] +public class DrawMainMenuButtonEvent : MainMenuEvent +{ + delegate bool ButtonDrawDelegate(int id, int x, int y, int width, int height, string text, Color color, MainMenu self); + + public ButtonData Data { get; } + + public DrawMainMenuButtonEvent(MainMenu mainMenu, ButtonData data) : base(mainMenu) + { + Data = data; + } + + [HarmonyILManipulator] + [HarmonyPatch(typeof(MainMenu), nameof(MainMenu.drawMainMenuButtons))] + private static void AfterMainMenuDraw(ILContext il) + { + ILCursor c = new ILCursor(il); + + c.GotoNext(MoveType.After, + x => x.MatchLdcI4(450), + x => x.MatchLdcI4(50), + x => x.MatchLdstr("New Session"), + x => x.MatchCall(typeof(LocaleTerms), nameof(LocaleTerms.Loc)), + x => x.MatchLdsfld(typeof(MainMenu), nameof(MainMenu.buttonColor)) + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + + // Continue Session Button + c.GotoNext(MoveType.After, + x => x.MatchLdfld(typeof(MainMenu), "canLoad"), + x => x.MatchBrtrue(out var _), + x => x.MatchCall(typeof(Color), "get_Black"), + x => x.MatchBr(out var _), + x => x.MatchLdsfld(typeof(MainMenu), nameof(MainMenu.buttonColor)), + x => x.MatchNop() + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + + c.GotoNext(MoveType.After, + x => x.MatchLdstr("Login"), + x => x.MatchCall(typeof(LocaleTerms), nameof(LocaleTerms.Loc)), + x => x.MatchLdloc(1), + x => x.MatchBrtrue(out var _), + x => x.MatchCall(typeof(Color), "get_Black"), + x => x.MatchBr(out var _), + x => x.MatchLdsfld(typeof(MainMenu), nameof(MainMenu.buttonColor)), + x => x.MatchNop() + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + + c.GotoNext(MoveType.After, + x => x.MatchLdstr("Settings"), + x => x.MatchCall(typeof(LocaleTerms), nameof(LocaleTerms.Loc)), + x => x.MatchLdsfld(typeof(MainMenu), nameof(MainMenu.buttonColor)) + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + + c.GotoNext(MoveType.After, + x => x.MatchLdstr("Start Relay Server"), + x => x.MatchLdsfld(typeof(MainMenu), nameof(MainMenu.buttonColor)) + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + + c.GotoNext(MoveType.After, + x => x.MatchLdstr("Extensions"), + x => x.MatchLdsfld(typeof(MainMenu), nameof(MainMenu.buttonColor)) + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + + // New Labyrinth Session Button + c.GotoNext(MoveType.After, + x => x.MatchCall(typeof(Utils), nameof(Utils.rand)), + x => x.MatchSub(), + x => x.MatchCall(typeof(Color), nameof(Color.Lerp)) + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + + c.GotoNext(MoveType.After, + x => x.MatchLdstr("Exit"), + x => x.MatchCall(typeof(LocaleTerms), nameof(LocaleTerms.Loc)), + x => x.MatchLdsfld(typeof(MainMenu), nameof(MainMenu.exitButtonColor)) + ); + c.Remove(); + c.Remove(); + c.Emit(OpCodes.Ldarg_0); + c.Emit(OpCodes.Ldloca_S, (byte)0); + c.Emit(OpCodes.Ldloca_S, (byte)4); + c.EmitDelegate(ButtonDrawExecution); + } + + // I have no idea why it was implemented with two Y position indexers and fixing it would kind of be pointless, just gonna keep them equal + // If I don't do this at least two buttons could overlap + public static bool ButtonDrawExecution(int id, int x, int y, int width, int height, string text, Color color, MainMenu self, ref int yPosIndexOne, ref int yPostIndexTwo) + { + var loadButtonData = new ButtonData(id, x, y, width, height, text, color, Math.Max(yPosIndexOne, yPostIndexTwo)); + var drawMainMenuButton = new DrawMainMenuButtonEvent(self, loadButtonData); + EventManager.InvokeAll(drawMainMenuButton); + yPosIndexOne = yPostIndexTwo = loadButtonData.YPositionIndex; + + if(!drawMainMenuButton.Cancelled) + return Button.doButton( + loadButtonData.Id, + loadButtonData.X, + loadButtonData.Y, + loadButtonData.Width, + loadButtonData.Height, + loadButtonData.Text, + loadButtonData.Color + ); + return false; + } + + public class ButtonData + { + public const int DefaultButtonTopPadding = 15; + + public int Id; + public int X; + public int Y; + public int Width; + public int Height; + public string Text; + public Color Color; + public int YPositionIndex; + + private MainMenuButtonType _buttonType = MainMenuButtonType.Undefined; + public MainMenuButtonType ButtonType + { + get + { + if(_buttonType == MainMenuButtonType.Undefined) + { + if(Enum.IsDefined(typeof(MainMenuButtonType), Id)) + _buttonType = (MainMenuButtonType)Id; + else _buttonType = MainMenuButtonType.Unknown; + } + return _buttonType; + } + } + + public ButtonData(int id, int x, int y, int width, int height, string text, Color color, int yPosIndex) + { + Id = id; + X = x; + Y = y; + Width = width; + Height = height; + Text = text; + Color = color; + YPositionIndex = yPosIndex; + } + + public bool Is(MainMenuButtonType button) => ButtonType == button; + } +} diff --git a/PathfinderAPI/GUI/PluginInfo.cs b/PathfinderAPI/GUI/PluginInfo.cs new file mode 100644 index 00000000..80f165b3 --- /dev/null +++ b/PathfinderAPI/GUI/PluginInfo.cs @@ -0,0 +1,308 @@ +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Reflection; +using BepInEx; +using BepInEx.Hacknet; +using Hacknet; +using Hacknet.Gui; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Pathfinder.Meta; + +namespace Pathfinder.GUI; + +public class PluginInfo +{ + private static List _loadOrderCache; + private static ReadOnlyCollection _readonlyLoadOrderCache; + protected static ReadOnlyCollection LoadOrderCache => _readonlyLoadOrderCache ??= new ReadOnlyCollection(_loadOrderCache); + + public readonly HacknetPlugin Plugin; + public readonly string Name; + public readonly string Guid; + public readonly SemanticVersioning.Version Version; + public readonly bool Enabled; + public readonly int LoadOrder; + public readonly string ConfigPath; + private bool _configPathExists; + public bool ConfigPathExists { get => _configPathExists; protected set => _configPathExists = value; } + public readonly string Description; + public readonly ReadOnlyCollection SoftDependencies; + public readonly ReadOnlyCollection HardDependencies; + public readonly ReadOnlyCollection Incompatibles; + public readonly ReadOnlyCollection Authors; + public readonly string TeamName; + private ReadOnlyDictionary _readonlyWebsites; + public ReadOnlyDictionary Websites => _readonlyWebsites ??= new ReadOnlyDictionary(_websites); + private readonly SortedDictionary _websites = new SortedDictionary(); + private readonly Texture2D ImageTexture; + + private readonly PFButton CheckForUpdateButton; + private readonly PFButton UpdateButton; + private readonly Func CheckForUpdateAction; + private readonly Func UpdateAction; + private readonly Func CanPerformUpdate; + private readonly Func CanCheckForUpdate; + + public PluginInfo(PluginListScreen screen, HacknetPlugin plugin) + { + Plugin = plugin; + var metadata = MetadataHelper.GetMetadata(Plugin); + if(metadata == null) + throw new InvalidOperationException($"Plugin {Plugin.GetType().FullName} lacks a BepInPlugin attribute."); + Name = metadata.Name; + Guid = metadata.GUID; + Version = metadata.Version; + ConfigPath = Plugin.Config.ConfigFilePath; + ConfigPathExists = File.Exists(ConfigPath); + var pluginDataAttrib = MetadataHelper.GetAttributes(Plugin).FirstOrDefault(); + if(pluginDataAttrib != null) + { + Description = pluginDataAttrib.Description; + Authors = new ReadOnlyCollection(pluginDataAttrib.Authors); + TeamName = pluginDataAttrib.TeamName; + if(pluginDataAttrib.ImageName != null) + ImageTexture = LoadTexture(screen.ScreenManager.GraphicsDevice, Path.Combine(Paths.PluginPath, pluginDataAttrib.ImageName)); + } + if(ImageTexture == null) + { + ImageTexture = LoadTexture(screen.ScreenManager.GraphicsDevice, Path.Combine(Paths.PluginPath, Guid)); + } + foreach(var website in MetadataHelper.GetAttributes(Plugin)) + _websites[website.WebsiteName] = website.WebsiteUrl; + var softDeps = new List(); + var hardDeps = new List(); + foreach(var deps in MetadataHelper.GetDependencies(Plugin.GetType())) + { + if(deps.Flags == BepInDependency.DependencyFlags.SoftDependency) + softDeps.Add($"{deps.DependencyGUID}{(deps.VersionRange?.ToString()?.Length > 0 ? $" ({deps.VersionRange})" : "")}"); + else + hardDeps.Add($"{deps.DependencyGUID}{(deps.VersionRange?.ToString()?.Length > 0 ? $" ({deps.VersionRange})" : "")}"); + } + SoftDependencies = new ReadOnlyCollection(softDeps); + HardDependencies = new ReadOnlyCollection(hardDeps); + var incompats = new List(); + foreach(var incomp in MetadataHelper.GetAttributes(Plugin)) + incompats.Add(incomp.IncompatibilityGUID); + Incompatibles = new ReadOnlyCollection(incompats); + + if(_loadOrderCache == null) + { + _loadOrderCache = new List(); + foreach(var pluginPair in HacknetChainloader.Instance.Plugins) + _loadOrderCache.Add(pluginPair.Key); + } + + LoadOrder = _loadOrderCache.IndexOf(Guid); + if(LoadOrder != -1) + { + LoadOrder++; + Enabled = true; + } + + Button = new PFButton(0, 0, 0, 0, $"{(Enabled ? $"{LoadOrder}. " : "")}{Name} ({Guid} v{Version})") + { + }; + + ConfigPathButton = new PFButton(0,0,0,0,""); + + var updaterAttrb = MetadataHelper.GetAttributes(Plugin).FirstOrDefault(); + if(updaterAttrb != null && HacknetChainloader.Instance.Plugins.TryGetValue("com.Pathfinder.Updater", out var updaterInfo)) + { + var mainMenuOverrideType = updaterInfo.Instance.GetType().Assembly.GetType("PathfinderUpdater.MainMenuOverride"); + var methInfo = mainMenuOverrideType.GetMethod("PerformCheckAndUpdateButtonAsync", BindingFlags.NonPublic | BindingFlags.Static); + if(methInfo.GetParameters().FirstOrDefault() != null) + { + CheckForUpdateAction = (Func)methInfo.CreateDelegate(typeof(Func)); + UpdateAction = (Func)mainMenuOverrideType.GetMethod("PerformUpdateAndUpdateButtonAsync", BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate(typeof(Func)); + CanPerformUpdate = (Func)mainMenuOverrideType.GetProperty("CanPerformUpdate", BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod(true).CreateDelegate(typeof(Func)); + CanCheckForUpdate = (Func)mainMenuOverrideType.GetProperty("CanCheckForUpdate", BindingFlags.NonPublic | BindingFlags.Static).GetGetMethod(true).CreateDelegate(typeof(Func)); + CheckForUpdateButton = new PFButton(0,0,0,0, "Check For Update", new Color(255, 255, 87)); + UpdateButton = new PFButton(0,0,0,0, "Update"); + } + } + } + + public virtual int Width { get; } = 300; + public virtual int Height { get; } = 100; + + protected PFButton Button; + protected PFButton ConfigPathButton; + + public virtual bool DrawListElement(GameTime time, PluginListScreen screen, Point offset, bool isSelected) + { + var result = isSelected; + Button.X = offset.X; + Button.Y = offset.Y; + Button.Width = Width; + Button.Height = Height; + + if(isSelected) + Button.Color = GuiData.Default_Trans_Grey_Strong; + else + Button.Color = null; + + if(Button.Do()) + result = true; + + if(isSelected) + DrawData(time, screen, offset); + + return result; + } + + private int _panelId = -1; + + public virtual void DrawData(GameTime time, PluginListScreen screen, Point offset) + { + var viewWidth = screen.ScreenManager.GraphicsDevice.Viewport.Width; + var viewHeight = screen.ScreenManager.GraphicsDevice.Viewport.Height; + var xPos = offset.X + Width + 10; + var yPos = 10; + var rect = new Rectangle(xPos, yPos, viewWidth - xPos - 10, viewHeight - (yPos*2)); + ScrollablePanel.beginPanel( + _panelId > -1 + ? _panelId + : _panelId = PFButton.GetNextID(), + rect, + Vector2.Zero + ); + + RenderedRectangle.doRectangle(0, 0, rect.Width, rect.Height, new Color(0, 0, 0, 175)); + RenderedRectangle.doRectangleOutline(0, 0, rect.Width, rect.Height, 2, Color.Gray); + + var labelYPos = 10; + if(ImageTexture != null) + { + DrawTexture(new Vector2(10, labelYPos), ImageTexture); + labelYPos += ImageTexture.Height + 10; + } + const int yPosAdd = 40; + TextItem.doLabel(new Vector2(10, labelYPos), $"Name: {Name}", null); + labelYPos += yPosAdd; + TextItem.doLabel(new Vector2(10, labelYPos), $"Guid: {Guid}", null); + labelYPos += yPosAdd; + TextItem.doLabel(new Vector2(10, labelYPos), $"Version: {Version}", null); + labelYPos += yPosAdd; + if(CheckForUpdateButton != null && UpdateButton != null) + { + CheckForUpdateButton.X = 10; + CheckForUpdateButton.Y = labelYPos; + CheckForUpdateButton.Width = 160; + CheckForUpdateButton.Height = 30; + UpdateButton.X = CheckForUpdateButton.X + CheckForUpdateButton.Width + 10; + UpdateButton.Y = labelYPos; + UpdateButton.Width = 160; + UpdateButton.Height = 30; + labelYPos += CheckForUpdateButton.Height + 10; + + if(CheckForUpdateButton.Do() && CanCheckForUpdate()) + Task.Run(async () => await CheckForUpdateAction(UpdateButton, CheckForUpdateButton)); + + if(UpdateButton.Do() && CanPerformUpdate()) + Task.Run(async () => await UpdateAction(screen, UpdateButton)); + + } + TextItem.doLabel(new Vector2(10, labelYPos), $"Enabled: {Enabled}", null); + labelYPos += yPosAdd; + if(Enabled) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Load Order: {LoadOrder}", null); + labelYPos += yPosAdd; + } + const string configPathText = "Config Path: "; + TextItem.doLabel(new Vector2(10, labelYPos), configPathText, null); + var configTextSize = GuiData.font.MeasureString(configPathText); + ConfigPathButton.X = (int)configTextSize.X + 10; + ConfigPathButton.Y = labelYPos; + ConfigPathButton.Width = 900; + ConfigPathButton.Height = (int)configTextSize.Y; + ConfigPathButton.Text = ConfigPath; + if(ConfigPathButton.Do()) + { + if(!ConfigPathExists) File.Create(ConfigPath); + new Thread(() => new Process + { + StartInfo = new ProcessStartInfo(ConfigPath) + { + UseShellExecute = true + } + }.Start()).Start(); + } + labelYPos += yPosAdd; + TextItem.doLabel(new Vector2(10, labelYPos), $"Config Exists: {ConfigPathExists}", null); + labelYPos += yPosAdd; + if(Description != null) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Description: {Description}", null); + labelYPos += yPosAdd; + } + if(Authors != null) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Author{(Authors.Count > 1 ? "s" : "")}: {string.Join(", ", Authors)}", null); + labelYPos += yPosAdd; + } + if(TeamName != null) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Team Name: {TeamName}", null); + labelYPos += yPosAdd; + } + if(HardDependencies.Count > 0) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Hard Dependenc{(HardDependencies.Count > 1 ? "ies" : "y")}: {string.Join(", ", HardDependencies)}", null); + labelYPos += yPosAdd; + } + if(SoftDependencies.Count > 0) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Soft Dependenc{(SoftDependencies.Count > 1 ? "ies" : "y")}: {string.Join(", ", SoftDependencies)}", null); + labelYPos += yPosAdd; + } + if(Incompatibles.Count > 0) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Incompatible{(Incompatibles.Count > 1 ? "s" : "")}: {string.Join(", ", Incompatibles)}", null); + labelYPos += yPosAdd; + } + if(Websites.Count > 1) + { + TextItem.doLabel(new Vector2(10, labelYPos), $"Websites: ", null); + labelYPos += yPosAdd; + } + foreach(var webPair in Websites) + { + TextItem.doLabel(new Vector2(40, labelYPos), $"{webPair.Key}: {webPair.Value}", null); + labelYPos += yPosAdd; + } + + + ScrollablePanel.endPanel(_panelId, Vector2.Zero, rect, 0); + } + + protected void DrawTexture(Vector2 position, Texture2D texture, Color? color = null) + { + GuiData.spriteBatch.Draw(texture, position, color ?? Color.White); + } + + protected Texture2D LoadTexture(GraphicsDevice device, string fileName) + { + // TODO: validate if file is actually an image file perhaps? https://stackoverflow.com/a/49683945 + if(!File.Exists(fileName)) foreach(var ext in new[] { ".bmp", ".gif", ".jpg", ".jpeg", ".png", ".tga", ".tif", ".tiff" }) + { + var imgPath = Path.Combine(Paths.PluginPath, Guid) + ext; + if(File.Exists(imgPath)) + { + fileName = imgPath; + break; + } + } + if(!File.Exists(fileName)) return null; + try + { + using(FileStream fs = File.Open(fileName, FileMode.Open)) + return Texture2D.FromStream(device, fs); + } + catch(Exception ex) + { + throw ex; + } + } +} \ No newline at end of file diff --git a/PathfinderAPI/GUI/PluginListScreen.cs b/PathfinderAPI/GUI/PluginListScreen.cs new file mode 100644 index 00000000..9320d08c --- /dev/null +++ b/PathfinderAPI/GUI/PluginListScreen.cs @@ -0,0 +1,86 @@ +using BepInEx.Hacknet; +using Hacknet; +using Hacknet.Gui; +using Microsoft.Xna.Framework; +using Pathfinder.Event; +using Pathfinder.Event.Menu; +using Pathfinder.Meta.Load; +using Pathfinder.Util; + +namespace Pathfinder.GUI; + +public class PluginListScreen : GameScreen +{ + private List _pluginDataList = new List(); + + public string SelectedGuid; + + private PFButton BackButton = new PFButton(10, 10, 220, 30, $"<- {LocaleTerms.Loc("Back")}", Color.Gray); + + public override void Draw(GameTime gameTime) + { + base.Draw(gameTime); + PostProcessor.begin(); + ScreenManager.FadeBackBufferToBlack(255); + GuiData.startDraw(); + PatternDrawer.draw( + new Rectangle(0, 0, ScreenManager.GraphicsDevice.Viewport.Width, ScreenManager.GraphicsDevice.Viewport.Height), + 0.5f, + Color.Black, + new Color(2, 2, 2), + GuiData.spriteBatch + ); + if (BackButton.Do()) + ExitScreen(); + + int defX = 10, defY = 50; + foreach(var pluginData in _pluginDataList) + { + if(pluginData.DrawListElement(gameTime, this, new Point(defX, defY), pluginData.Guid == SelectedGuid)) + SelectedGuid = pluginData.Guid; + defY += pluginData.Height + 10; + } + + + GuiData.endDraw(); + PostProcessor.end(); + } + + public override void HandleInput(InputState input) + { + base.HandleInput(input); + GuiData.doInput(input); + } + + public override void LoadContent() + { + base.LoadContent(); + foreach(var plugin in HacknetChainloader.Instance.Plugins) + _pluginDataList.Add(new PluginInfo(this, (HacknetPlugin)plugin.Value.Instance)); + } + + private static PFButton _pluginListButton = new PFButton(0,0,0,0, "Plugins"); + + [Initialize] + internal static void Initialize() + { + EventManager.AddHandler(OnMainMenuButtonDraw); + } + + public static void OnMainMenuButtonDraw(DrawMainMenuButtonEvent evt) + { + if(!evt.Data.Is(MainMenuButtonType.Extensions)) return; + + _pluginListButton.Color = MainMenu.buttonColor; + _pluginListButton.X = evt.Data.X; + _pluginListButton.Y = evt.Data.Y; + _pluginListButton.Width = evt.Data.Width; + _pluginListButton.Height = evt.Data.Height; + if(_pluginListButton.Do()) + evt.MainMenu.ScreenManager.AddScreen(new PluginListScreen()); + + evt.Data.Y += evt.Data.Height + DrawMainMenuButtonEvent.ButtonData.DefaultButtonTopPadding; + evt.Data.YPositionIndex = evt.Data.Y; + + } +} \ No newline at end of file diff --git a/PathfinderAPI/Meta/PluginInfoAttribute.cs b/PathfinderAPI/Meta/PluginInfoAttribute.cs new file mode 100644 index 00000000..040a7ede --- /dev/null +++ b/PathfinderAPI/Meta/PluginInfoAttribute.cs @@ -0,0 +1,16 @@ +namespace Pathfinder.Meta; + +public class PluginInfoAttribute : System.Attribute +{ + public string Description; + public string ImageName; + public string[] Authors; + public string TeamName; + + public PluginInfoAttribute(string description, string teamName = null, string author = null) + { + Description = description; + if(author != null) Authors = new []{ author }; + TeamName = teamName; + } +} \ No newline at end of file diff --git a/PathfinderAPI/Meta/PluginWebsiteAttribute.cs b/PathfinderAPI/Meta/PluginWebsiteAttribute.cs new file mode 100644 index 00000000..87bd2d18 --- /dev/null +++ b/PathfinderAPI/Meta/PluginWebsiteAttribute.cs @@ -0,0 +1,14 @@ +namespace Pathfinder.Meta; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class PluginWebsiteAttribute : System.Attribute +{ + public string WebsiteName; + public string WebsiteUrl; + + public PluginWebsiteAttribute(string websiteName, string websiteUrl) + { + WebsiteName = websiteName; + WebsiteUrl = websiteUrl; + } +} \ No newline at end of file diff --git a/PathfinderAPI/PathfinderAPI.csproj b/PathfinderAPI/PathfinderAPI.csproj index 853862da..dfb9028d 100644 --- a/PathfinderAPI/PathfinderAPI.csproj +++ b/PathfinderAPI/PathfinderAPI.csproj @@ -13,6 +13,8 @@ $(LibsDir)HacknetPathfinder.exe + + diff --git a/PathfinderAPI/PathfinderAPIPlugin.cs b/PathfinderAPI/PathfinderAPIPlugin.cs index 372b7e69..ec36e729 100644 --- a/PathfinderAPI/PathfinderAPIPlugin.cs +++ b/PathfinderAPI/PathfinderAPIPlugin.cs @@ -10,6 +10,15 @@ namespace Pathfinder; [BepInPlugin(ModGUID, ModName, HacknetChainloader.VERSION)] [BepInDependency("com.Pathfinder.Updater", BepInDependency.DependencyFlags.SoftDependency)] +[PluginInfo("An extensive modding API for Hacknet that enables practically limitless programable extensions to the game.", + Authors = new string[] + { + "Windows10CE (Araon)", "Spartan322 (George)", "Fayti1703", "Arkhist", "SoundOfScooting", + "CanadaHonk", "Seeker1437", "MaowImpl (Aidan)" + } +)] +[PluginWebsite("Github", "https://github.com/Arkhist/Hacknet-Pathfinder")] +[PluginWebsite("Documentation", "https://arkhist.github.io/Hacknet-Pathfinder/")] [Updater( "https://api.github.com/repos/Arkhist/Hacknet-Pathfinder/releases", "Pathfinder.Release.zip", diff --git a/PathfinderUpdater/MainMenuOverride.cs b/PathfinderUpdater/MainMenuOverride.cs index dd94b81e..66f99ddb 100644 --- a/PathfinderUpdater/MainMenuOverride.cs +++ b/PathfinderUpdater/MainMenuOverride.cs @@ -42,36 +42,39 @@ internal static void OptionsSaved(CustomOptionsSaveEvent args) popupScreen.NoRestartPrompt.Value = PathfinderUpdaterPlugin.NoRestartPrompt.Value; } - internal static async Task PerformCheckAndUpdateButtonAsync() + internal static async Task PerformCheckAndUpdateButtonAsync(PFButton updateButton = null, PFButton checkButton = null) { - PerformUpdate.Text = UPDATE_STRING; + if(updateButton == null) updateButton = PerformUpdate; + if(checkButton == null) checkButton = CheckForUpdate; + updateButton.Text = UPDATE_STRING; CanCheckForUpdate = false; - var oldText = CheckForUpdate.Text; - CheckForUpdate.Text = "Finding Latest Update..."; + var oldText = checkButton.Text; + checkButton.Text = "Finding Latest Update..."; CanPerformUpdate = PathfinderUpdaterPlugin.NeedsUpdate = (await PathfinderUpdaterPlugin.PerformCheckAsync(true)).Length > 0; - CheckForUpdate.Text = oldText; + checkButton.Text = oldText; CanCheckForUpdate = true; - PerformUpdate.Text += $" (v{PathfinderUpdaterPlugin.PathfinderUpdater.LatestVersion})"; + updateButton.Text += $" (v{PathfinderUpdaterPlugin.PathfinderUpdater.LatestVersion})"; } - private static async Task PerformUpdateAndUpdateButtonAsync(MainMenu menu) + private static async Task PerformUpdateAndUpdateButtonAsync(GameScreen menu, PFButton updateButton = null) { + if(updateButton == null) updateButton = PerformUpdate; var couldCheckForUpdate = CanCheckForUpdate; CanCheckForUpdate = false; CanPerformUpdate = false; - var oldText = PerformUpdate.Text; - PerformUpdate.Text = "Currently Updating..."; + var oldText = updateButton.Text; + updateButton.Text = "Currently Updating..."; await PathfinderUpdaterPlugin.PerformUpdateAsync(); - PerformUpdate.Text = oldText; + updateButton.Text = oldText; if(!menu.ScreenManager.screens.Contains(popupScreen) && !PathfinderUpdaterPlugin.NoRestartPrompt.Value) menu.ScreenManager.AddScreen(popupScreen ??= new RestartPopupScreen()); CanPerformUpdate = !menu.ScreenManager.screens.Contains(popupScreen); CanCheckForUpdate = couldCheckForUpdate; } - private static bool CanCheckForUpdate = true; - private static bool CanPerformUpdate; + private static bool CanCheckForUpdate { get; set; } = true; + private static bool CanPerformUpdate { get; set; } internal static void OnDrawMainMenu(MainMenuEvent args) { if(CheckForUpdate.Do() && CanCheckForUpdate) diff --git a/PathfinderUpdater/PathfinderUpdaterPlugin.cs b/PathfinderUpdater/PathfinderUpdaterPlugin.cs index 71eb798e..30bfd5f8 100644 --- a/PathfinderUpdater/PathfinderUpdaterPlugin.cs +++ b/PathfinderUpdater/PathfinderUpdaterPlugin.cs @@ -13,6 +13,13 @@ namespace PathfinderUpdater; [BepInPlugin(ModGUID, ModName, HacknetChainloader.VERSION)] +[PluginInfo("An extension to Pathfinder which automatically updates BepInEx.Hacknet, PathfinderAPI, and PathfinderUpdater.", + Authors = new string[] + { + "Windows10CE (Araon)", "Spartan322 (George)" + } +)] +[PluginWebsite("Github", "https://github.com/Arkhist/Hacknet-Pathfinder")] [Updater( "https://api.github.com/repos/Arkhist/Hacknet-Pathfinder/releases", "Pathfinder.Release.zip",