diff --git a/.gitignore b/src/.gitignore similarity index 87% rename from .gitignore rename to src/.gitignore index 8a30d25..6e9599b 100644 --- a/.gitignore +++ b/src/.gitignore @@ -1,7 +1,7 @@ ## 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/main/VisualStudio.gitignore +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser @@ -23,7 +23,6 @@ mono_crash.* [Rr]eleases/ x64/ x86/ -[Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ @@ -62,9 +61,6 @@ project.lock.json project.fragment.lock.json artifacts/ -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - # StyleCop StyleCopReport.xml @@ -90,7 +86,6 @@ StyleCopReport.xml *.tmp_proj *_wpftmp.csproj *.log -*.tlog *.vspscc *.vssscc .builds @@ -143,9 +138,7 @@ _TeamCity* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info +coverage*[.json, .xml, .info] # Visual Studio code coverage results *.coverage @@ -294,17 +287,6 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -361,9 +343,6 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ -# Visual Studio History (VSHistory) files -.vshistory/ - # BeatPulse healthcheck temp database healthchecksdb @@ -372,27 +351,5 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml +.idea/ +*.DotSettings diff --git a/src/CactusPie.RamCleanerInterval.sln b/src/CactusPie.RamCleanerInterval.sln new file mode 100644 index 0000000..ecc3a54 --- /dev/null +++ b/src/CactusPie.RamCleanerInterval.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32616.157 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CactusPie.RamCleanerInterval", "CactusPie.RamCleanerInterval\CactusPie.RamCleanerInterval.csproj", "{9C38A2B1-5F09-49FE-AC57-9FBEF7717582}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9C38A2B1-5F09-49FE-AC57-9FBEF7717582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C38A2B1-5F09-49FE-AC57-9FBEF7717582}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C38A2B1-5F09-49FE-AC57-9FBEF7717582}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C38A2B1-5F09-49FE-AC57-9FBEF7717582}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {26F91A8D-3767-457A-821C-F66AFD620885} + EndGlobalSection +EndGlobal diff --git a/src/CactusPie.RamCleanerInterval/CactusPie.RamCleanerInterval.csproj b/src/CactusPie.RamCleanerInterval/CactusPie.RamCleanerInterval.csproj new file mode 100644 index 0000000..186853d --- /dev/null +++ b/src/CactusPie.RamCleanerInterval/CactusPie.RamCleanerInterval.csproj @@ -0,0 +1,50 @@ + + + + net472 + 1.0.0 + CactusPie + + + + + runtime + + + + + + ..\References\Aki.Common.dll + False + + + ..\References\EFT_Managed\Aki.Reflection.dll + False + + + ..\References\EFT_Managed\Assembly-CSharp.dll + False + + + ..\References\EFT_Managed\Comfort.dll + False + + + ..\References\EFT_Managed\Comfort.Unity.dll + False + + + ..\References\EFT_Managed\UnityEngine.dll + False + + + ..\References\EFT_Managed\UnityEngine.CoreModule.dll + False + + + ..\References\EFT_Managed\UnityEngine.IMGUIModule.dll + False + + + + diff --git a/src/CactusPie.RamCleanerInterval/ConfigurationManagerAttributes.cs b/src/CactusPie.RamCleanerInterval/ConfigurationManagerAttributes.cs new file mode 100644 index 0000000..f3027d0 --- /dev/null +++ b/src/CactusPie.RamCleanerInterval/ConfigurationManagerAttributes.cs @@ -0,0 +1,154 @@ +namespace CactusPie.RamCleanerInterval +{ + /// + /// Class that specifies how a setting should be displayed inside the ConfigurationManager settings window. + /// + /// Usage: + /// This class template has to be copied inside the plugin's project and referenced by its code directly. + /// make a new instance, assign any fields that you want to override, and pass it as a tag for your setting. + /// + /// If a field is null (default), it will be ignored and won't change how the setting is displayed. + /// If a field is non-null (you assigned a value to it), it will override default behavior. + /// + /// + /// + /// Here's an example of overriding order of settings and marking one of the settings as advanced: + /// + /// // Override IsAdvanced and Order + /// Config.Bind("X", "1", 1, new ConfigDescription("", null, new ConfigurationManagerAttributes { IsAdvanced = true, Order = 3 })); + /// // Override only Order, IsAdvanced stays as the default value assigned by ConfigManager + /// Config.Bind("X", "2", 2, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 1 })); + /// Config.Bind("X", "3", 3, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 2 })); + /// + /// + /// + /// + /// You can read more and see examples in the readme at https://github.com/BepInEx/BepInEx.ConfigurationManager + /// You can optionally remove fields that you won't use from this class, it's the same as leaving them null. + /// +#pragma warning disable 0169, 0414, 0649 + internal sealed class ConfigurationManagerAttributes + { + /// + /// Should the setting be shown as a percentage (only use with value range settings). + /// + public bool? ShowRangeAsPercent; + + /// + /// Custom setting editor (OnGUI code that replaces the default editor provided by ConfigurationManager). + /// See below for a deeper explanation. Using a custom drawer will cause many of the other fields to do nothing. + /// + public System.Action CustomDrawer; + + /// + /// Custom setting editor that allows polling keyboard input with the Input (or UnityInput) class. + /// Use either CustomDrawer or CustomHotkeyDrawer, using both at the same time leads to undefined behaviour. + /// + public CustomHotkeyDrawerFunc CustomHotkeyDrawer; + + /// + /// Custom setting draw action that allows polling keyboard input with the Input class. + /// Note: Make sure to focus on your UI control when you are accepting input so user doesn't type in the search box or in another setting (best to do this on every frame). + /// If you don't draw any selectable UI controls You can use `GUIUtility.keyboardControl = -1;` on every frame to make sure that nothing is selected. + /// + /// + /// CustomHotkeyDrawer = (ConfigEntryBase setting, ref bool isEditing) => + /// { + /// if (isEditing) + /// { + /// // Make sure nothing else is selected since we aren't focusing on a text box with GUI.FocusControl. + /// GUIUtility.keyboardControl = -1; + /// + /// // Use Input.GetKeyDown and others here, remember to set isEditing to false after you're done! + /// // It's best to check Input.anyKeyDown and set isEditing to false immediately if it's true, + /// // so that the input doesn't have a chance to propagate to the game itself. + /// + /// if (GUILayout.Button("Stop")) + /// isEditing = false; + /// } + /// else + /// { + /// if (GUILayout.Button("Start")) + /// isEditing = true; + /// } + /// + /// // This will only be true when isEditing is true and you hold any key + /// GUILayout.Label("Any key pressed: " + Input.anyKey); + /// } + /// + /// + /// Setting currently being set (if available). + /// + /// + /// Set this ref parameter to true when you want the current setting drawer to receive Input events. + /// The value will persist after being set, use it to see if the current instance is being edited. + /// Remember to set it to false after you are done! + /// + public delegate void CustomHotkeyDrawerFunc( + BepInEx.Configuration.ConfigEntryBase setting, + ref bool isCurrentlyAcceptingInput); + + /// + /// Show this setting in the settings screen at all? If false, don't show. + /// + public bool? Browsable; + + /// + /// Category the setting is under. Null to be directly under the plugin. + /// + public string Category; + + /// + /// If set, a "Default" button will be shown next to the setting to allow resetting to default. + /// + public object DefaultValue; + + /// + /// Force the "Reset" button to not be displayed, even if a valid DefaultValue is available. + /// + public bool? HideDefaultButton; + + /// + /// Force the setting name to not be displayed. Should only be used with a to get more space. + /// Can be used together with to gain even more space. + /// + public bool? HideSettingName; + + /// + /// Optional description shown when hovering over the setting. + /// Not recommended, provide the description when creating the setting instead. + /// + public string Description; + + /// + /// Name of the setting. + /// + public string DispName; + + /// + /// Order of the setting on the settings list relative to other settings in a category. + /// 0 by default, higher number is higher on the list. + /// + public int? Order; + + /// + /// Only show the value, don't allow editing it. + /// + public bool? ReadOnly; + + /// + /// If true, don't show the setting by default. User has to turn on showing advanced settings or search for it. + /// + public bool? IsAdvanced; + + /// + /// Custom converter from setting type to string for the built-in editor textboxes. + /// + public System.Func ObjToStr; + + /// + /// Custom converter from string to setting type for the built-in editor textboxes. + /// + public System.Func StrToObj; + } +} \ No newline at end of file diff --git a/src/CactusPie.RamCleanerInterval/CustomRamCleanerIntervalPlugin.cs b/src/CactusPie.RamCleanerInterval/CustomRamCleanerIntervalPlugin.cs new file mode 100644 index 0000000..479b2c8 --- /dev/null +++ b/src/CactusPie.RamCleanerInterval/CustomRamCleanerIntervalPlugin.cs @@ -0,0 +1,184 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Timers; +using Aki.Reflection.Utils; +using BepInEx; +using BepInEx.Configuration; +using JetBrains.Annotations; +using UnityEngine; + +namespace CactusPie.RamCleanerInterval +{ + [BepInPlugin("com.cactuspie.ramcleanerinterval", "CactusPie.RamCleanerInterval", "1.0.0")] + public class CustomRamCleanerIntervalPlugin : BaseUnityPlugin + { + private Timer _timer; + + private MethodInfo _emptyWorkingSetMethod; + + public static ConfigEntry CleanNow { get; set; } + + internal static ConfigEntry RamCleanerInterval { get; set; } + + internal static ConfigEntry IntervalEnabled { get; set; } + + internal static ConfigEntry OnlyInRaid { get; set; } + + [UsedImplicitly] + internal void Start() + { + const string sectionName = "Override RAM cleaner interval"; + + IntervalEnabled = Config.Bind + ( + sectionName, + "Interval enabled", + true, + new ConfigDescription + ( + "Whether or not we should use the custom RAM cleaner interval", + null, + new ConfigurationManagerAttributes + { + Order = 4, + } + ) + ); + + RamCleanerInterval = Config.Bind + ( + sectionName, + "Interval (seconds)", + 300, + new ConfigDescription + ( + "Number of seconds between each RAM cleaner execution. Changing this setting resets the interval", + new AcceptableValueRange(30, 900), + new ConfigurationManagerAttributes + { + Order = 3, + } + ) + ); + + CleanNow = Config.Bind( + sectionName, + "Clean now", + "Execute the RAM cleaner now", + new ConfigDescription( + "Execute the RAM cleaner now", + null, + new ConfigurationManagerAttributes + { + CustomDrawer = CleanNowButtonDrawer, + Order = 2, + } + )); + + OnlyInRaid = Config.Bind + ( + sectionName, + "Only in raid", + true, + new ConfigDescription + ( + "Only run the RAM cleaner in raid", + null, + new ConfigurationManagerAttributes + { + Order = 1, + } + ) + ); + + _timer = new Timer + { + AutoReset = true, + Interval = RamCleanerInterval.Value * 1000, + }; + + _timer.Elapsed += TimerOnElapsed; + + RamCleanerInterval.SettingChanged += RamCleanerIntervalOnSettingChanged; + IntervalEnabled.SettingChanged += IntervalEnabledOnSettingChanged; + + _emptyWorkingSetMethod = ( + from eftType in PatchConstants.EftTypes // Should be GClass693 as of 3.7.4 + let emptyWorkingSetMethod = eftType.GetMethod("EmptyWorkingSet") + where emptyWorkingSetMethod != null && !emptyWorkingSetMethod.GetParameters().Any() + select emptyWorkingSetMethod + ).Single(); + + if (IntervalEnabled.Value) + { + _timer.Start(); + } + else + { + _timer.Stop(); + } + } + + private void IntervalEnabledOnSettingChanged(object sender, EventArgs e) + { + if (IntervalEnabled.Value) + { + _timer.Start(); + } + else + { + _timer.Stop(); + } + } + + private void RamCleanerIntervalOnSettingChanged(object sender, EventArgs e) + { + ChangeInterval(RamCleanerInterval.Value); + } + + private void TimerOnElapsed(object sender, ElapsedEventArgs e) + { + ExecuteCleaner(); + } + + /// + /// Execute the ram cleaner + /// + /// If the ram cleaner should be executed even when not in raid + private void ExecuteCleaner(bool force = false) + { + if (!force && OnlyInRaid.Value && !GameHelper.IsInGame()) + { + return; + } + + Logger.LogInfo("Executing the RAM cleaner"); + + _emptyWorkingSetMethod.Invoke(null, null); + } + + private void CleanNowButtonDrawer(ConfigEntryBase entry) + { + bool button = GUILayout.Button("Clean now", GUILayout.ExpandWidth(true)); + if (button) + { + ExecuteCleaner(true); + } + } + + private void ChangeInterval(int interval) + { + if (_timer.Enabled) + { + _timer.Stop(); + _timer.Interval = interval * 1000; + _timer.Start(); + } + else + { + _timer.Interval = interval * 1000; + } + } + } +} diff --git a/src/CactusPie.RamCleanerInterval/GameHelper.cs b/src/CactusPie.RamCleanerInterval/GameHelper.cs new file mode 100644 index 0000000..4bb6f29 --- /dev/null +++ b/src/CactusPie.RamCleanerInterval/GameHelper.cs @@ -0,0 +1,17 @@ +using Comfort.Common; +using EFT; + +namespace CactusPie.RamCleanerInterval +{ + public static class GameHelper + { + public static bool IsInGame() + { + GameWorld gameWorld = Singleton.Instance; + + bool isInGame = gameWorld != null && gameWorld.MainPlayer != null && gameWorld.MainPlayer.Location != "hideout"; + + return isInGame; + } + } +} \ No newline at end of file diff --git a/src/NuGet.Config b/src/NuGet.Config new file mode 100644 index 0000000..da1fd08 --- /dev/null +++ b/src/NuGet.Config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file