commit 74e53205f4b5ebcf145939489e80e8416c6937bc Author: Exil Productions Date: Tue Nov 11 03:10:52 2025 +0100 Initial Code diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/Core.cs b/Core.cs new file mode 100644 index 0000000..5eb5b52 --- /dev/null +++ b/Core.cs @@ -0,0 +1,75 @@ +using MelonLoader; +using System.Collections; +using System.Reflection; +using UnityEngine; +using VMM.ModRegistry; +using VMM.ModRegistry.Settings; +using VMM.ModRegistry.Settings.Types; +using VMM.UI; + +[assembly: MelonInfo(typeof(VMM.Core), "VigilModManager", "1.0.0", "Exil_S", null)] +[assembly: MelonGame("Singularity Studios", "Vigil")] + +namespace VMM +{ + internal class Core : MelonMod + { + public static MelonLogger.Instance Logger { get; private set; } + + public override void OnInitializeMelon() + { + Logger = LoggerInstance; + LoggerInstance.Msg("Initialized."); + } + + public override void OnSceneWasLoaded(int buildIndex, string sceneName) + { + if (buildIndex != 0) return; // Main Menu only + + InitializeManagers(); + SetupModLoading(); + } + + public override void OnApplicationQuit() + { + Cleanup(); + } + + private void InitializeManagers() + { + UIManager.Instance = new UIManager(); + UIManager.Instance.Initialize(); + + ModManager.Instance = new ModManager(); + } + + private void SetupModLoading() + { + ModManager.Instance.onFinishedLoading += OnLoadedMods; + ModManager.Instance.LoadMods(); + } + + private void Cleanup() + { + if (ModManager.Instance != null) + { + ModManager.Instance.onFinishedLoading -= OnLoadedMods; + } + } + + //test setting for testing + private void OnLoadedMods() + { + var settings = new ModSettings(); + var testToggle = new ToggleSetting + { + Name = "Test Toggle", + Value = true, + OnChanged = value => LoggerInstance.Msg($"Test Toggle pressed! State {value}") + }; + settings.AddSetting(testToggle); + + ModManager.Instance.RegisterSettings(Assembly.GetExecutingAssembly(), settings); + } + } +} \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ModRegistry/ModEntry.cs b/ModRegistry/ModEntry.cs new file mode 100644 index 0000000..38cedeb --- /dev/null +++ b/ModRegistry/ModEntry.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using VMM.ModRegistry.Settings; + +namespace VMM.ModRegistry +{ + public class ModEntry + { + public string Name { get; set; } + public string Version { get; set; } + public string Author { get; set; } + public ModSettings Settings { get; set; } + + internal Assembly assembly { get; set; } + } +} diff --git a/ModRegistry/ModManager.cs b/ModRegistry/ModManager.cs new file mode 100644 index 0000000..65c7678 --- /dev/null +++ b/ModRegistry/ModManager.cs @@ -0,0 +1,70 @@ +using MelonLoader; +using System.Reflection; +using VMM.ModRegistry.Settings; +using VMM.UI; + +namespace VMM.ModRegistry +{ + public class ModManager + { + public static ModManager Instance { get; internal set; } + + public Action onFinishedLoading; + + internal List LoadedMods = new List(); + + public ModManager() + { + if (Instance == null) + Instance = this; + else + Core.Logger.Warning("Duplicate ModManager Detected"); + } + + internal void LoadMods() + { + Core.Logger.Msg("=== Installed Melon Mods ==="); + foreach (var mod in MelonMod.RegisteredMelons.OrderBy(m => m.Info.Name)) + { + Core.Logger.Msg($"Name: {mod.Info.Name} | Version: {mod.Info.Version} | Author: {mod.Info.Author}"); + + var modEntry = new ModEntry + { + Name = mod.Info.Name, + Version = mod.Info.Version, + Author = mod.Info.Author, + assembly = mod.GetType().Assembly, + Settings = null + }; + + LoadedMods.Add(modEntry); + UIManager.Instance.AddModButton(modEntry); + } + Core.Logger.Msg("============================"); + onFinishedLoading?.Invoke(); + } + + public ModEntry GetModEntryByName(string modName) + { + return LoadedMods.FirstOrDefault(m => + m.Name.Equals(modName, StringComparison.OrdinalIgnoreCase)); + } + + public List GetLoadedMods() => LoadedMods; + + public void RegisterSettings(Assembly modAssembly, ModSettings settings) + { + var mod = LoadedMods.Find(m => m.assembly == modAssembly); + if (mod != null) + { + mod.Settings = new ModSettings(); + mod.Settings = settings; + Core.Logger.Msg($"Registered settings for mod: {mod.Name}"); + } + else + { + Core.Logger.Error("Mod not found for the provided assembly."); + } + } + } +} diff --git a/ModRegistry/Settings/ISettingsElement.cs b/ModRegistry/Settings/ISettingsElement.cs new file mode 100644 index 0000000..6854835 --- /dev/null +++ b/ModRegistry/Settings/ISettingsElement.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace VMM.ModRegistry.Settings +{ + public interface ISettingsElement + { + string Name { get; set; } + object GetValue(); + void SetValue(object value); + } +} diff --git a/ModRegistry/Settings/ModSettings.cs b/ModRegistry/Settings/ModSettings.cs new file mode 100644 index 0000000..095c329 --- /dev/null +++ b/ModRegistry/Settings/ModSettings.cs @@ -0,0 +1,24 @@ +namespace VMM.ModRegistry.Settings +{ + public class ModSettings + { + private readonly List settings = new(); + + public void AddSetting(ISettingsElement newSetting) + { + settings.Add(newSetting); + } + + public T GetSetting(string name) where T : class, ISettingsElement + { + foreach (var setting in settings) + { + if(setting.Name == name && setting is T typed) + return typed; + } + return null; + } + + public IEnumerable GetAllSettings() => settings; + } +} diff --git a/ModRegistry/Settings/SettingsElement.cs b/ModRegistry/Settings/SettingsElement.cs new file mode 100644 index 0000000..b6d8ca9 --- /dev/null +++ b/ModRegistry/Settings/SettingsElement.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; +using VMM.ModRegistry.Settings; + +namespace VMM.ModRegistry.Settings +{ + public abstract class SettingsElement : ISettingsElement + { + public string Name { get; set; } + public T Value { get; set; } + + public Action OnChanged { get; set; } + + public object GetValue() => Value; + + public void SetValue(object value) + { + if (value is T typedValue) + { + Value = typedValue; + OnChanged?.Invoke(typedValue); + } + else + { + throw new InvalidCastException($"Invalid value type. Expected {typeof(T)}, got {value?.GetType()}"); + } + } + + } +} \ No newline at end of file diff --git a/ModRegistry/Settings/Types/SliderSetting.cs b/ModRegistry/Settings/Types/SliderSetting.cs new file mode 100644 index 0000000..95d7f67 --- /dev/null +++ b/ModRegistry/Settings/Types/SliderSetting.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace VMM.ModRegistry.Settings.Types +{ + public class SliderSetting : SettingsElement + { + public float Min { get; set; } + public float Max { get; set; } + } +} diff --git a/ModRegistry/Settings/Types/ToggleSetting.cs b/ModRegistry/Settings/Types/ToggleSetting.cs new file mode 100644 index 0000000..314cb71 --- /dev/null +++ b/ModRegistry/Settings/Types/ToggleSetting.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace VMM.ModRegistry.Settings.Types +{ + public class ToggleSetting : SettingsElement + { + } +} diff --git a/Patches/MainMenuManager.cs b/Patches/MainMenuManager.cs new file mode 100644 index 0000000..5c0afa0 --- /dev/null +++ b/Patches/MainMenuManager.cs @@ -0,0 +1,25 @@ +using System; +using HarmonyLib; +using FMODUnity; + +namespace VMM.Patches +{ + internal static class MainMenuManager + { + private static readonly System.Reflection.FieldInfo field = AccessTools.Field(typeof(global::MainMenuManager), "buttonPress"); + + public static EventReference GetButtonPress(global::MainMenuManager instance) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + + if (field == null) + { + Core.Logger.Error("Could not find field 'buttonPress' in MainMenuManager!"); + return default; + } + + return (EventReference)field.GetValue(instance); + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..07cc6b6 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# VigilModManager \ No newline at end of file diff --git a/UI/Components/ModEntryButton.cs b/UI/Components/ModEntryButton.cs new file mode 100644 index 0000000..7c81cab --- /dev/null +++ b/UI/Components/ModEntryButton.cs @@ -0,0 +1,27 @@ +using FMODUnity; +using UnityEngine; +using VMM.ModRegistry; + +namespace VMM.UI.Components +{ + internal class ModEntryButton : MonoBehaviour + { + public ModEntry mod; + + UnityEngine.UI.Button button; + EventReference buttonPress; + + void Awake() + { + button = GetComponent(); + button.onClick.AddListener(ShowModInfo); + buttonPress = VMM.Patches.MainMenuManager.GetButtonPress(global::MainMenuManager.instance); + } + + void ShowModInfo() + { + ModInfoDisplayer.Instance.UpdateModInfo(mod); + AudioManager.instance.PlayOneShot(buttonPress, transform.position); + } + } +} diff --git a/UI/Components/ModInfoDisplayer.cs b/UI/Components/ModInfoDisplayer.cs new file mode 100644 index 0000000..0cf8e14 --- /dev/null +++ b/UI/Components/ModInfoDisplayer.cs @@ -0,0 +1,64 @@ +using TMPro; +using UnityEngine; +using VMM.ModRegistry; + +namespace VMM.UI.Components +{ + internal class ModInfoDisplayer : MonoBehaviour + { + internal static ModInfoDisplayer Instance; + + internal TextMeshProUGUI modName; + internal TextMeshProUGUI modAuthor; + internal TextMeshProUGUI modVersion; + internal UnityEngine.UI.Button settingsButton; + internal ModEntry currentMod; + + void Awake() + { + if(Instance == null) + { + Instance = this; + settingsButton.onClick.AddListener(OnSettingsButtonPressed); + transform.gameObject.SetActive(false); + } + else + { + Destroy(this); + Core.Logger.Warning("For some reason there were two Intances of ModInfoDisplayer???"); + } + } + + public void UpdateModInfo(ModEntry mod) + { + if(mod.Settings == null) + { + settingsButton.gameObject.SetActive(false); + } + else if(mod.Settings != null && !settingsButton.gameObject.activeSelf) + { + settingsButton.gameObject.SetActive(true); + } + + transform.gameObject.SetActive(true); + modName.text = $"Name: {mod.Name}"; + modAuthor.text = $"Author: {mod.Author}"; + modVersion.text = $"Version: {mod.Version}"; + currentMod = mod; + } + + public void HandleClose() + { + if(gameObject.activeSelf) + gameObject.SetActive(false); + } + + void OnSettingsButtonPressed() + { + if(currentMod == null) + return; + + ModSettingsManager.Instance.OpenSettings(currentMod); + } + } +} diff --git a/UI/Components/ModSettingsManager.cs b/UI/Components/ModSettingsManager.cs new file mode 100644 index 0000000..614179e --- /dev/null +++ b/UI/Components/ModSettingsManager.cs @@ -0,0 +1,73 @@ +using TMPro; +using UnityEngine; +using UnityEngine.UI; +using VMM.ModRegistry; +using VMM.ModRegistry.Settings.Types; +using VMM.UI.Helpers; + +namespace VMM.UI.Components +{ + internal class ModSettingsManager : MonoBehaviour + { + public static ModSettingsManager Instance; + + public TextMeshProUGUI titleText; + + RectTransform settingsContainer; + + public void Init() + { + if (Instance == null) + { + Instance = this; + settingsContainer = transform.GetChild(0).GetComponent(); + settingsContainer.gameObject.SetActive(false); + } + else + { + Core.Logger.Warning("Form some reason there's a second instance for the ModSettingsManager"); + Destroy(this); + } + } + + void OnDisable() + { + ClearSettings(); + } + + void ClearSettings() + { + for(int i = 0; i < settingsContainer.childCount; i++) + { + Destroy(settingsContainer.GetChild(i).gameObject); + } + } + + public void OpenSettings(ModEntry mod) + { + if(mod.Settings == null) + { + Core.Logger.Warning($"Mod '{mod.Name}' Does not Contain Any Settings"); + return; + } + ClearSettings(); + var settings = mod.Settings.GetAllSettings(); + if (settings != null && settings.Count() <= 0) + { + Core.Logger.Warning($"Settings Reference of '{mod.Name}' were found but no Settings were found inside List"); + return; + } + titleText.text = $"{mod.Name} Settings"; + foreach(var setting in settings) + { + if(setting is SliderSetting slider) + UIBuilder.CreateSlider("Slider Setting", settingsContainer, slider.Name, slider.Max, slider.Min, slider.Value, (float value) => slider.OnChanged.Invoke(value)); + if (setting is ToggleSetting toggle) + UIBuilder.CreateToggle("Toggle Setting", settingsContainer, toggle.Name, toggle.Value, (bool value) => toggle.OnChanged.Invoke(value)); + } + settingsContainer.gameObject.SetActive(true); + UIManager.Instance.OpenSettings(); + } + + } +} diff --git a/UI/Components/UILayoutRebuilder.cs b/UI/Components/UILayoutRebuilder.cs new file mode 100644 index 0000000..0dfdee6 --- /dev/null +++ b/UI/Components/UILayoutRebuilder.cs @@ -0,0 +1,19 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace VMM.UI.Components +{ + internal class UILayoutRebuilder : MonoBehaviour + { + RectTransform parentRect; + + void OnEnable() + { + if(parentRect == null) + { + parentRect = transform.parent.GetComponent(); + } + LayoutRebuilder.ForceRebuildLayoutImmediate(parentRect); + } + } +} diff --git a/UI/Helpers/UIBuilder.cs b/UI/Helpers/UIBuilder.cs new file mode 100644 index 0000000..2939285 --- /dev/null +++ b/UI/Helpers/UIBuilder.cs @@ -0,0 +1,401 @@ +using System.Text; +using TMPro; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.SceneManagement; +using UnityEngine.UI; +using VMM.UI.Components; + +namespace VMM.UI.Helpers +{ + internal static class UIBuilder + { + const string TABS_CONTAINER_PATH = "Canvas/Margins"; + const string BUTTON_PATH = TABS_CONTAINER_PATH + "/Main Menu/Play Button"; + const string OPTIONS_PATH = TABS_CONTAINER_PATH + "/Options"; + const string DIVIDER_PATH = OPTIONS_PATH + "/Divider"; + const string SLIDER_PATH = OPTIONS_PATH + "/Master Volume"; + + public static RectTransform CreateTab(string name, float x, float y) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var tabObj = new GameObject(name, typeof(RectTransform)); + tabObj.transform.SetParent(GameObject.Find(TABS_CONTAINER_PATH).transform, false); + tabObj.layer = LayerMask.NameToLayer("UI"); + tabObj.SetActive(false); + var tabRect = tabObj.GetComponent(); + tabRect.sizeDelta = new Vector2(x, y); + + return tabRect; + } + + public static RectTransform CreateTab(string name, RectTransform parent, float x, float y) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var tabObj = new GameObject(name, typeof(RectTransform)); + tabObj.transform.SetParent(parent, false); + tabObj.layer = LayerMask.NameToLayer("UI"); + var tabRect = tabObj.GetComponent(); + tabRect.sizeDelta = new Vector2(x, y); + + return tabRect; + } + + public static RectTransform CreateButton(string name, RectTransform parent, string buttonText, UnityAction callback) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var newButton = UnityEngine.Object.Instantiate(GameObject.Find(BUTTON_PATH), parent); + newButton.name = name; + var buttonComp = newButton.GetComponent(); + buttonComp.onClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + if (callback != null) + buttonComp.onClick.AddListener(callback); + var selectedText = newButton.transform.Find("Selected Text").GetComponent(); + selectedText.text = $"[ {buttonText} ] "; + var unselectedText = newButton.transform.Find("Unselected Text").GetComponent(); + unselectedText.text = buttonText; + newButton.AddComponent(); + + return newButton.GetComponent(); + } + + public static RectTransform CreateText(string name, RectTransform parent, string text, Color color, out TextMeshProUGUI textMesh, int fontSize = 14) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + textMesh = null; + return null; + } + + var newText = UnityEngine.Object.Instantiate(GameObject.Find(BUTTON_PATH + "/Unselected Text"), parent); + newText.name = name; + UnityEngine.Object.DestroyImmediate(newText.GetComponent()); + var textComp = newText.GetComponent(); + textComp.text = text; + if(color != Color.yellow) + textComp.color = color; + textComp.fontSize = fontSize; + textMesh = textComp; + newText.AddComponent(); + + return newText.GetComponent(); + } + + public static RectTransform CreateWindow(string name, RectTransform parent, string title, out TextMeshProUGUI titleText, float width, UnityAction onClose) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + titleText = null; + return null; + } + + var newWindow = UnityEngine.Object.Instantiate(GameObject.Find(OPTIONS_PATH), parent); + newWindow.name = name; + for (int i = 0; i < newWindow.transform.childCount; i++) + { + var child = newWindow.transform.GetChild(i); + if(child.name != "Header") + { + UnityEngine.Object.Destroy(child.gameObject); + } + } + var text = newWindow.transform.GetChild(0).GetChild(0).GetComponent(); + text.text = title; + titleText = text; + var closeButton = newWindow.transform.GetChild(0).GetChild(1).GetChild(0).GetComponent(); + closeButton.onClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + if(onClose != null) + closeButton.onClick.AddListener(onClose); + else + closeButton.gameObject.SetActive(false); + var headerRect = newWindow.transform.GetChild(0).GetComponent(); + headerRect.sizeDelta = new Vector2(width, headerRect.sizeDelta.y); + var closeContainer = headerRect.GetChild(1).GetComponent(); + closeContainer.anchorMin = new Vector2(1, 0.5f); + closeContainer.anchorMax = new Vector2(1, 0.5f); + closeContainer.pivot = new Vector2(1, 0.5f); + closeContainer.anchoredPosition = new Vector2(10f, 10f); + var buttonRect = closeContainer.GetChild(0).GetComponent(); + buttonRect.anchorMin = new Vector2(0.5f, 0.5f); + buttonRect.anchorMax = new Vector2(0.5f, 0.5f); + buttonRect.pivot = new Vector2(0.5f, 0.5f); + buttonRect.anchoredPosition = Vector2.zero; + var windowRect = newWindow.GetComponent(); + if (newWindow.activeSelf == false) + newWindow.SetActive(true); + newWindow.AddComponent(); + + return windowRect; + } + + public static RectTransform CreateDivider(string name, RectTransform parent, Color color) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var newDivider = UnityEngine.Object.Instantiate(GameObject.Find(DIVIDER_PATH), parent); + newDivider.name = name; + var textComp = newDivider.GetComponent(); + StringBuilder stringBuilder = new StringBuilder(); + float parentWidth = parent.sizeDelta.x; + int dashCount = Mathf.FloorToInt(parentWidth / 10f); + for (int i = 0; i < dashCount; i++) + { + stringBuilder.Append("-"); + } + textComp.text = stringBuilder.ToString(); + textComp.color = color; + var rect = newDivider.GetComponent(); + rect.anchorMin = new Vector2(0f, rect.anchorMin.y); + rect.anchorMax = new Vector2(1f, rect.anchorMax.y); + rect.offsetMin = new Vector2(0f, rect.offsetMin.y); + rect.offsetMax = new Vector2(0f, rect.offsetMax.y); + newDivider.AddComponent(); + + return rect; + } + + public static RectTransform CreateVerticalLayoutArea(string name, RectTransform parent, float spacing = 8f, RectOffset padding = null, TextAnchor alignment = TextAnchor.MiddleCenter, bool controlChildSize = true, bool forceExpand = false) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var layoutObj = new GameObject(name, typeof(RectTransform)); + layoutObj.transform.SetParent(parent, false); + layoutObj.layer = LayerMask.NameToLayer("UI"); + var rect = layoutObj.GetComponent(); + rect.sizeDelta = Vector2.zero; + var layout = layoutObj.AddComponent(); + layout.childAlignment = alignment; + layout.spacing = spacing; + layout.padding = padding ?? new RectOffset(0, 0, 0, 0); + layout.childControlWidth = controlChildSize; + layout.childControlHeight = controlChildSize; + layout.childForceExpandWidth = forceExpand; + layout.childForceExpandHeight = forceExpand; + var fitter = layoutObj.AddComponent(); + fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize; + layoutObj.AddComponent(); + + return rect; + } + + public static RectTransform CreateHorizontalLayoutArea(string name, RectTransform parent, float spacing = 8f, RectOffset padding = null, TextAnchor alignment = TextAnchor.MiddleCenter, bool controlChildSize = true, bool forceExpand = false) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var layoutObj = new GameObject(name, typeof(RectTransform)); + layoutObj.transform.SetParent(parent, false); + layoutObj.layer = LayerMask.NameToLayer("UI"); + var rect = layoutObj.GetComponent(); + rect.sizeDelta = Vector2.zero; + var layout = layoutObj.AddComponent(); + layout.childAlignment = alignment; + layout.spacing = spacing; + layout.padding = padding ?? new RectOffset(0, 0, 0, 0); + layout.childControlWidth = controlChildSize; + layout.childControlHeight = controlChildSize; + layout.childForceExpandWidth = forceExpand; + layout.childForceExpandHeight = forceExpand; + var fitter = layoutObj.AddComponent(); + fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize; + layoutObj.AddComponent(); + + return rect; + } + + public static RectTransform CreateSlider(string name, RectTransform parent, string sliderText, float maxValue, float minValue, float value, UnityAction onValueChanged) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var newSlider = UnityEngine.Object.Instantiate(GameObject.Find(SLIDER_PATH), parent); + newSlider.name = name; + UnityEngine.Object.Destroy(newSlider.GetComponent()); + newSlider.transform.GetChild(0).GetComponent().text = sliderText; + var sliderComp = newSlider.transform.GetChild(2).GetComponent(); + sliderComp.minValue = minValue; + sliderComp.maxValue = maxValue; + sliderComp.value = value; + sliderComp.onValueChanged = new UnityEngine.UI.Slider.SliderEvent(); + sliderComp.onValueChanged.AddListener(onValueChanged); + var rectComp = newSlider.GetComponent(); + newSlider.AddComponent(); + + return rectComp; + } + + public static RectTransform CreateToggle(string name, RectTransform parent, string toggleText, bool value, UnityAction onValueChanged) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + return null; + } + + var rootToggle = CreateHorizontalLayoutArea(name, parent, 15, null, TextAnchor.MiddleLeft, false, true); + var toggleContainer = new GameObject("ToggleContainer", typeof(RectTransform)); + toggleContainer.transform.SetParent(rootToggle.transform, false); + toggleContainer.layer = LayerMask.NameToLayer("UI"); + var toggleContainerRect = toggleContainer.GetComponent(); + toggleContainerRect.sizeDelta = new Vector2(40f, 40f); + var toggleShadow = new GameObject("ToggleShadow", typeof(RectTransform)); + toggleShadow.transform.SetParent(toggleContainer.transform, false); + toggleShadow.layer = LayerMask.NameToLayer("UI"); + var toggleShadowRect = toggleShadow.GetComponent(); + toggleShadowRect.sizeDelta = new Vector2(40f, 40f); + var toggleShadowImage = toggleShadow.AddComponent(); + toggleShadowImage.color = Color.black; + var toggleGraphic = new GameObject("ToggleGraphic", typeof(RectTransform)); + toggleGraphic.transform.SetParent(toggleShadow.transform, false); + toggleGraphic.layer = LayerMask.NameToLayer("UI"); + var toggleGraphicRect = toggleGraphic.GetComponent(); + toggleGraphicRect.sizeDelta = new Vector2(30f, 30f); + var toggleGraphicImage = toggleGraphic.AddComponent(); + toggleGraphicImage.color = Color.yellow; + var toggleComp = toggleContainer.AddComponent(); + toggleComp.transition = Selectable.Transition.None; + toggleComp.targetGraphic = toggleShadowImage; + toggleComp.graphic = toggleGraphicImage; + toggleComp.isOn = value; + toggleComp.onValueChanged.AddListener(onValueChanged); + CreateText("ToggleText", rootToggle, toggleText, Color.yellow, out TextMeshProUGUI text, 28); + text.alignment = TextAlignmentOptions.BaselineLeft; + + return rootToggle; + } + + public static RectTransform CreateVerticalScrollView(string name, RectTransform parent, float width, float height, out RectTransform contentRect, float contentPadding = 8f) + { + if (!InMainMenu()) + { + Core.Logger.Warning("Tried Creating UI outside Main Menu, Aborting."); + contentRect = null; + return null; + } + + // Root ScrollView object + var scrollViewObj = new GameObject(name, typeof(RectTransform), typeof(ScrollRect), typeof(Image)); + scrollViewObj.transform.SetParent(parent, false); + scrollViewObj.layer = LayerMask.NameToLayer("UI"); + + var scrollViewRect = scrollViewObj.GetComponent(); + scrollViewRect.sizeDelta = new Vector2(width, height); + + // Make background invisible + var scrollViewImage = scrollViewObj.GetComponent(); + scrollViewImage.color = Color.clear; + + // Viewport + var viewportObj = new GameObject("Viewport", typeof(RectTransform), typeof(UnityEngine.UI.Mask), typeof(Image)); + viewportObj.transform.SetParent(scrollViewObj.transform, false); + viewportObj.layer = LayerMask.NameToLayer("UI"); + + var viewportRect = viewportObj.GetComponent(); + viewportRect.anchorMin = Vector2.zero; + viewportRect.anchorMax = Vector2.one; + viewportRect.offsetMin = Vector2.zero; + viewportRect.offsetMax = Vector2.zero; + + var viewportImage = viewportObj.GetComponent(); + viewportImage.color = Color.clear; + + var mask = viewportObj.GetComponent(); + mask.showMaskGraphic = false; + + // Content container + var contentObj = new GameObject("Content", typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter)); + contentObj.transform.SetParent(viewportObj.transform, false); + contentObj.layer = LayerMask.NameToLayer("UI"); + + var contentRectComp = contentObj.GetComponent(); + contentRectComp.anchorMin = new Vector2(0, 1); + contentRectComp.anchorMax = new Vector2(1, 1); + contentRectComp.pivot = new Vector2(0.5f, 1); + contentRectComp.anchoredPosition = Vector2.zero; + contentRectComp.sizeDelta = new Vector2(0, 0); + + var layout = contentObj.GetComponent(); + layout.childAlignment = TextAnchor.UpperCenter; + layout.spacing = contentPadding; + layout.childForceExpandHeight = false; + layout.childForceExpandWidth = true; + + var fitter = contentObj.GetComponent(); + fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained; + + // ScrollRect setup + var scrollRect = scrollViewObj.GetComponent(); + scrollRect.content = contentRectComp; + scrollRect.viewport = viewportRect; + scrollRect.horizontal = false; + scrollRect.vertical = true; + scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; + + // Scrollbar + var scrollbarObj = new GameObject("Scrollbar", typeof(RectTransform), typeof(Scrollbar), typeof(Image)); + scrollbarObj.transform.SetParent(scrollViewObj.transform, false); + scrollbarObj.layer = LayerMask.NameToLayer("UI"); + + var scrollbarRect = scrollbarObj.GetComponent(); + scrollbarRect.anchorMin = new Vector2(1, 0); + scrollbarRect.anchorMax = new Vector2(1, 1); + scrollbarRect.pivot = new Vector2(1, 1); + scrollbarRect.sizeDelta = new Vector2(20f, 0); + scrollbarRect.anchoredPosition = Vector2.zero; + + var scrollbarImage = scrollbarObj.GetComponent(); + scrollbarImage.color = Color.yellow; + + var scrollbar = scrollbarObj.GetComponent(); + scrollbar.direction = Scrollbar.Direction.BottomToTop; + + scrollRect.verticalScrollbar = scrollbar; + + scrollViewObj.AddComponent(); + + contentRect = contentRectComp; + + return scrollViewRect; + } + + + static bool InMainMenu() + { + return SceneManager.GetActiveScene().buildIndex == 0; + } + } +} diff --git a/UI/UIManager.cs b/UI/UIManager.cs new file mode 100644 index 0000000..61fa803 --- /dev/null +++ b/UI/UIManager.cs @@ -0,0 +1,127 @@ +using Steamworks; +using TMPro; +using UnityEngine; +using UnityEngine.Events; +using VMM.ModRegistry; +using VMM.UI.Components; +using VMM.UI.Helpers; + +namespace VMM.UI +{ + internal class UIManager + { + public static UIManager Instance { get; set; } + + private RectTransform modsListTab; + private RectTransform modSettingsTab; + private RectTransform mainMenu; + private RectTransform mainMenuTitle; + private RectTransform modWindowRect; + private RectTransform modInfoWindowRect; + + public UIManager() + { + if (Instance == null) + Instance = this; + else + Core.Logger.Warning("Duplicate UIManager Detected"); + } + + public void Initialize() + { + Core.Logger.Msg("Initializing UI..."); + CacheUIReferences(); + + modsListTab = UIBuilder.CreateTab("Mods List", 700f, 500f); + SetupModsListTab(); + + modSettingsTab = UIBuilder.CreateTab("Mod Settings", 700f, 500f); + SetupModSettingsTab(); + + mainMenuTitle.localScale = new Vector3(0.9f, 0.9f, 0.9f); + + var modsButton = UIBuilder.CreateButton("Mods Button", mainMenu, "Mods", OpenModsMenu); + modsButton.transform.SetSiblingIndex(2); + Core.Logger.Msg("Created Mods Button."); + } + + private void CacheUIReferences() + { + var canvas = GameObject.Find("Canvas"); + if (canvas == null) + { + Core.Logger.Error("UIManager: Canvas not found in scene."); + return; + } + var margins = canvas.transform.Find("Margins"); + if (margins == null) + { + Core.Logger.Error("UIManager: Canvas/Margins not found."); + return; + } + var menu = margins.Find("Main Menu"); + if (menu == null) + { + Core.Logger.Error("UIManager: Main Menu not found under Canvas/Margins."); + return; + } + mainMenu = menu.GetComponent(); + mainMenuTitle = mainMenu.Find("Title").GetComponent(); + } + + private void SetupModsListTab() + { + var windowContent = UIBuilder.CreateHorizontalLayoutArea("Window Content", modsListTab, 100f, controlChildSize: false); + modWindowRect = UIBuilder.CreateWindow("Mods List Window", windowContent, "Installed Mods", out TextMeshProUGUI titleText1, 500f, OpenMainMenu); + + modInfoWindowRect = UIBuilder.CreateWindow("Mod Info Window", windowContent, "Mod Info", out TextMeshProUGUI titleText2, 500f, null); + var content = UIBuilder.CreateVerticalLayoutArea("Content", modInfoWindowRect, 10f, null, TextAnchor.MiddleCenter, true, true); + UIBuilder.CreateText("Mod Name", content, "Name: N/A", Color.yellow, out TextMeshProUGUI text, 40); + text.alignment = TextAlignmentOptions.Center; + UIBuilder.CreateText("Mod Author", content, "Author: N/A", Color.yellow, out TextMeshProUGUI text2, 40); + text2.alignment = TextAlignmentOptions.Center; + UIBuilder.CreateText("Mod Version", content, "Version: N/A", Color.yellow, out TextMeshProUGUI text3, 40); + text3.alignment = TextAlignmentOptions.Center; + var settingsButton = UIBuilder.CreateButton("Settings Button", content, "Settings", null).GetComponent(); + + var mid = modInfoWindowRect.gameObject.AddComponent(); + mid.modName = text; + mid.modAuthor = text2; + mid.modVersion = text3; + mid.settingsButton = settingsButton; + } + + private void SetupModSettingsTab() + { + var settingsWindow = UIBuilder.CreateWindow("Settings Window", modSettingsTab, "Mod Settings", out TextMeshProUGUI titleText, 1000f, OpenModsMenu); + var content = UIBuilder.CreateTab("Content", settingsWindow, 700f, 500f); + UIBuilder.CreateVerticalLayoutArea("Settings Container", content, 30, null, TextAnchor.UpperCenter, false, false); + var settigsManager = content.gameObject.AddComponent(); + settigsManager.titleText = titleText; + settigsManager.Init(); + } + + public void AddModButton(ModEntry mod) + { + var modButton = UIBuilder.CreateButton("Mod Button", modWindowRect, mod.Name, null); + var meb = modButton.gameObject.AddComponent(); + meb.mod = mod; + } + + public void OpenSettings() + { + MainMenuManager.instance.OpenTab(modSettingsTab.gameObject); + } + + public void OpenMainMenu() + { + MainMenuManager.instance.OpenTab(mainMenu.gameObject); + ModInfoDisplayer.Instance.HandleClose(); + } + + public void OpenModsMenu() + { + MainMenuManager.instance.OpenTab(modsListTab.gameObject); + } + } +} diff --git a/VMM.csproj b/VMM.csproj new file mode 100644 index 0000000..9ea8147 --- /dev/null +++ b/VMM.csproj @@ -0,0 +1,397 @@ + + + netstandard2.1 + enable + disable + VMM + default + false + 1.0.0.0 + 1.0.0.0 + en-US + VigilModManagerML + latest + enable + + + + + + + + + + + + + + + + + + + + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Assembly-CSharp-firstpass.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Assembly-CSharp.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\com.cqf.urpvolumetricfog.runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\com.rlabrecque.steamworks.net.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\FMODUnity.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\FMODUnityResonance.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\GPUInstancer.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\SteamAudioFMODStudio.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\SteamAudioUnity.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.AI.Navigation.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Burst.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Burst.Unsafe.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Collections.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Collections.LowLevel.ILSupport.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Mathematics.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Multiplayer.Center.Common.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Postprocessing.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.ProBuilder.Csg.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.ProBuilder.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.ProBuilder.KdTree.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.ProBuilder.Poly2Tri.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.ProBuilder.Stl.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Recorder.Base.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Recorder.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Rendering.LightTransport.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipeline.Universal.ShaderLibrary.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.Core.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.Core.Runtime.Shared.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.Core.ShaderLibrary.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.GPUDriven.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.Universal.2D.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.Universal.Config.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.Universal.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.RenderPipelines.Universal.Shaders.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.ScriptableBuildPipeline.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Splines.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.TextMeshPro.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.Timeline.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\Unity.VisualEffectGraph.Runtime.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.AccessibilityModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.AIModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.AMDModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.AndroidJNIModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.AnimationModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ARModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.AssetBundleModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.AudioModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ClothModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ClusterInputModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ClusterRendererModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ContentLoadModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.CoreModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.CrashReportingModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.DirectorModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.DSPGraphModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.GameCenterModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.GIModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.GraphicsStateCollectionSerializerModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.GridModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.HierarchyCoreModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.HotReloadModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ImageConversionModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.IMGUIModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.InputForUIModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.InputLegacyModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.InputModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.JSONSerializeModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.LocalizationModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.MarshallingModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.MultiplayerModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.NVIDIAModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ParticleSystemModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.PerformanceReportingModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.Physics2DModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.PhysicsModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.PropertiesModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ScreenCaptureModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.ShaderVariantAnalyticsModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.SharedInternalsModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.SpriteMaskModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.SpriteShapeModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.StreamingModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.SubstanceModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.SubsystemsModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.TerrainModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.TerrainPhysicsModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.TextCoreFontEngineModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.TextCoreTextEngineModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.TextRenderingModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.TilemapModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.TLSModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UI.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UIElementsModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UIModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UmbraModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityAnalyticsCommonModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityAnalyticsModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityConnectModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityCurlModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityTestProtocolModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityWebRequestAssetBundleModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityWebRequestAudioModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityWebRequestModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityWebRequestTextureModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.UnityWebRequestWWWModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.VehiclesModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.VFXModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.VideoModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.VirtualTexturingModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.VRModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.WindModule.dll + + + $(VIGIL_GAME_DIR)\Vigil_Data\Managed\UnityEngine.XRModule.dll + + + $(VIGIL_GAME_DIR)\MelonLoader\net35\MelonLoader.dll + + + $(VIGIL_GAME_DIR)\MelonLoader\net35\0Harmony.dll + + + + + + True + false + + + + + + \ No newline at end of file diff --git a/VigilModManager.sln b/VigilModManager.sln new file mode 100644 index 0000000..844d14d --- /dev/null +++ b/VigilModManager.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36623.8 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VMM", "VMM.csproj", "{81A6EEA5-5B4F-4B5A-A136-18C3E9DCB805}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {81A6EEA5-5B4F-4B5A-A136-18C3E9DCB805}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81A6EEA5-5B4F-4B5A-A136-18C3E9DCB805}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81A6EEA5-5B4F-4B5A-A136-18C3E9DCB805}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81A6EEA5-5B4F-4B5A-A136-18C3E9DCB805}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {50F4ABAB-2F25-43BB-9B46-9DED2834515D} + EndGlobalSection +EndGlobal