diff --git a/Blackout/AssetHandler.cs b/Blackout/AssetHandler.cs new file mode 100644 index 0000000..7d213ee --- /dev/null +++ b/Blackout/AssetHandler.cs @@ -0,0 +1,95 @@ +using System.Reflection; +using UnityEngine; + +namespace Blackout; + +/* + + Kindly stolen from SDraw's leap motion mod. (MIT) + https://github.com/SDraw/ml_mods_cvr/blob/master/ml_lme/AssetsHandler.cs + + *thank u sdraw, i wont be murderer now* + +*/ + + +static class AssetsHandler +{ + static readonly List ms_assets = new List() + { + "blackout_controller.asset" + }; + + static Dictionary ms_loadedAssets = new Dictionary(); + static Dictionary ms_loadedObjects = new Dictionary(); + + public static void Load() + { + Assembly l_assembly = Assembly.GetExecutingAssembly(); + string l_assemblyName = l_assembly.GetName().Name; + + foreach (string l_assetName in ms_assets) + { + try + { + Stream l_assetStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + l_assetName); + if (l_assetStream != null) + { + MemoryStream l_memorySteam = new MemoryStream((int)l_assetStream.Length); + l_assetStream.CopyTo(l_memorySteam); + AssetBundle l_assetBundle = AssetBundle.LoadFromMemory(l_memorySteam.ToArray(), 0); + if (l_assetBundle != null) + { + l_assetBundle.hideFlags |= HideFlags.DontUnloadUnusedAsset; + ms_loadedAssets.Add(l_assetName, l_assetBundle); + } + else + MelonLoader.MelonLogger.Warning("Unable to load bundled '" + l_assetName + "' asset"); + } + else + MelonLoader.MelonLogger.Warning("Unable to get bundled '" + l_assetName + "' asset stream"); + } + catch (System.Exception e) + { + MelonLoader.MelonLogger.Warning("Unable to load bundled '" + l_assetName + "' asset, reason: " + e.Message); + } + } + } + + public static GameObject GetAsset(string p_name) + { + GameObject l_result = null; + if (ms_loadedObjects.ContainsKey(p_name)) + { + l_result = UnityEngine.Object.Instantiate(ms_loadedObjects[p_name]); + l_result.SetActive(true); + l_result.hideFlags |= HideFlags.DontUnloadUnusedAsset; + } + else + { + foreach (var l_pair in ms_loadedAssets) + { + if (l_pair.Value.Contains(p_name)) + { + GameObject l_bundledObject = (GameObject)l_pair.Value.LoadAsset(p_name, typeof(GameObject)); + if (l_bundledObject != null) + { + ms_loadedObjects.Add(p_name, l_bundledObject); + l_result = UnityEngine.Object.Instantiate(l_bundledObject); + l_result.SetActive(true); + l_result.hideFlags |= HideFlags.DontUnloadUnusedAsset; + } + break; + } + } + } + return l_result; + } + + public static void Unload() + { + foreach (var l_pair in ms_loadedAssets) + UnityEngine.Object.Destroy(l_pair.Value); + ms_loadedAssets.Clear(); + } +} \ No newline at end of file diff --git a/Blackout/Blackout.csproj b/Blackout/Blackout.csproj index 5bbb158..6b9471a 100644 --- a/Blackout/Blackout.csproj +++ b/Blackout/Blackout.csproj @@ -8,6 +8,14 @@ false + + + + + + + + C:\Program Files (x86)\Steam\steamapps\common\ChilloutVR\MelonLoader\0Harmony.dll @@ -47,6 +55,21 @@ + + + True + True + Resource1.resx + + + + + + ResXFileCodeGenerator + Resource1.Designer.cs + + + diff --git a/Blackout/BlackoutController.cs b/Blackout/BlackoutController.cs index e843bf1..a583775 100644 --- a/Blackout/BlackoutController.cs +++ b/Blackout/BlackoutController.cs @@ -1,12 +1,7 @@ -using ABI.CCK.Components; -using ABI_RC.Core.Player; -using ABI_RC.Systems.IK; -using ABI_RC.Systems.IK.SubSystems; -using HarmonyLib; +using ABI_RC.Core.Player; +using ABI_RC.Core.UI; using MelonLoader; -using RootMotion.FinalIK; using UnityEngine; -using UnityEngine.Events; namespace Blackout; @@ -20,11 +15,21 @@ namespace Blackout; 1 - Drowsy (partial effect) 2 - Sleep (full effect) + After staying still for DrowsyModeTimer (minutes), you enter DrowsyMode. + This mode dims the screen to your selected dimming strength. + After continuing to stay still for SleepModeTimer (seconds), you enter SleepMode. + This mode overrenders mostly everything with black. + + Slight movement while in SleepMode will place you in DrowsyMode until SleepModeTimer is reached again. + Hard movement once entering DrowsyMode will fully wake you and return complete vision. + */ public class BlackoutController : MonoBehaviour { - public int BlackoutState = 0; + public static BlackoutController Instance; + + public BlackoutState CurrentState = BlackoutState.Awake; //degrees of movement to give partial vision public float drowsyThreshold = 1f; @@ -32,81 +37,190 @@ public class BlackoutController : MonoBehaviour public float wakeThreshold = 12f; //how long without movement until the screen dims - public float enterSleepTime = 3f; // MINUTES + public float DrowsyModeTimer = 3f; // MINUTES //how long should the wake state last before return - public float returnSleepTime = 10f; // SECONDS + public float SleepModeTimer = 10f; // SECONDS + + //how much does DrowsyMode affect the screen + public float DrowsyDimStrength = 0.5f; + + //this is uh, not work well- might rewrite now that i know how this should work + public bool HudMessages = false; + + public enum BlackoutState + { + Awake = 0, + Drowsy, + Sleeping, + } - //private BlackoutController instance; private Quaternion oldHeadRotation = Quaternion.identity; + private float angularMovement = 0f; + private float curTime = 0f; private float lastAwakeTime = 0f; private int nextUpdate = 1; + private Animator blackoutAnimator; + + public void ChangeBlackoutState(BlackoutState newState) + { + if (!blackoutAnimator) return; + if (newState == CurrentState) return; + + lastAwakeTime = curTime; + + switch (newState) + { + case BlackoutState.Awake: + blackoutAnimator.SetBool("BlackoutState.Drowsy", false); + blackoutAnimator.SetBool("BlackoutState.Sleeping", false); + blackoutAnimator.SetFloat("BlackoutSetting.DrowsyPartial", DrowsyDimStrength); + break; + case BlackoutState.Drowsy: + blackoutAnimator.SetBool("BlackoutState.Drowsy", true); + blackoutAnimator.SetBool("BlackoutState.Sleeping", false); + blackoutAnimator.SetFloat("BlackoutSetting.DrowsyPartial", DrowsyDimStrength); + break; + case BlackoutState.Sleeping: + blackoutAnimator.SetBool("BlackoutState.Drowsy", false); + blackoutAnimator.SetBool("BlackoutState.Sleeping", true); + blackoutAnimator.SetFloat("BlackoutSetting.DrowsyPartial", DrowsyDimStrength); + break; + default: + break; + } + BlackoutState prevState = CurrentState; + CurrentState = newState; + SendHUDMessage($"Exiting {prevState} and entering {newState} state."); + } void Update() { //only run once a second, angularMovement is "smoothed out" at high FPS otherwise - float curTime = Time.time; + //for the sake of responsivness while user is in a sleepy state, this might be removed to prevent confusion... + curTime = Time.time; if (!(curTime >= nextUpdate)) return; nextUpdate = Mathf.FloorToInt(curTime) + 1; //get difference between last frame rotation and current rotation Quaternion currentHeadRotation = PlayerSetup.Instance.GetActiveCamera().transform.rotation; - float angularMovement = Quaternion.Angle(oldHeadRotation, currentHeadRotation); + angularMovement = Quaternion.Angle(oldHeadRotation, currentHeadRotation); oldHeadRotation = currentHeadRotation; - // These are SOFT movements + //handle current state + switch (CurrentState) + { + case BlackoutState.Awake: + HandleAwakeState(); + break; + case BlackoutState.Drowsy: + HandleDrowsyState(); + break; + case BlackoutState.Sleeping: + HandleSleepingState(); + break; + default: + break; + } + + //debug + //MelonLogger.Msg("curTime " + curTime); + //MelonLogger.Msg("lastAwakeTime " + lastAwakeTime); + //MelonLogger.Msg("timeleft " + GetNextStateTimer()); + //MelonLogger.Msg("current state " + CurrentState); + } + + //initialize BlackoutInstance object + void Start() + { + Instance = this; + + GameObject blackoutAsset = AssetsHandler.GetAsset("Assets/BundledAssets/Blackout/Blackout.prefab"); + GameObject blackoutGO = Instantiate(blackoutAsset, new Vector3(0, 0, 0), Quaternion.identity); + + if (blackoutGO != null) + { + blackoutGO.name = "BlackoutInstance"; + blackoutAnimator = blackoutGO.GetComponent(); + SetupBlackoutInstance(); + } + } + + void OnEnabled() + { + if (!blackoutAnimator) return; + blackoutAnimator.gameObject.SetActive(true); + } + + void OnDisabled() + { + ChangeBlackoutState(BlackoutState.Awake); + if (!blackoutAnimator) return; + blackoutAnimator.gameObject.SetActive(false); + } + + public void SetupBlackoutInstance() + { + blackoutAnimator.transform.parent = PlayerSetup.Instance.GetActiveCamera().transform; + blackoutAnimator.transform.localPosition = Vector3.zero; + blackoutAnimator.transform.localRotation = Quaternion.identity; + blackoutAnimator.transform.localScale = Vector3.one; + } + + private float GetNextStateTimer() + { + switch (CurrentState) + { + case BlackoutState.Awake: + return (lastAwakeTime + DrowsyModeTimer * 60 - curTime); + case BlackoutState.Drowsy: + return (lastAwakeTime + SleepModeTimer - curTime); + case BlackoutState.Sleeping: + return 0f; + default: + return 0f; + } + } + + //broken, needs to run next frame + private void SendHUDMessage(string message) + { + MelonLogger.Msg(message); + if (!CohtmlHud.Instance || !HudMessages) return; + CohtmlHud.Instance.ViewDropTextImmediate("Blackout", message, GetNextStateTimer().ToString() + " seconds till next state change."); + } + + private void HandleAwakeState() + { + //small movement should reset sleep timer if (angularMovement > drowsyThreshold) { lastAwakeTime = curTime; - if (BlackoutState == 2) - { - BlackoutState = 1; - MelonLogger.Msg("Exited Sleep state and entered Drowsy state."); - } } - - // These are HARD movements + //enter drowsy mode after few minutes + if (curTime > lastAwakeTime + DrowsyModeTimer * 60) + { + ChangeBlackoutState(BlackoutState.Drowsy); + } + } + private void HandleDrowsyState() + { + //hard movement should exit drowsy state if (angularMovement > wakeThreshold) { - lastAwakeTime = curTime; - - if (BlackoutState == 0) return; - BlackoutState = 0; - MelonLogger.Msg("Exited anystate and entered Awake state."); + ChangeBlackoutState(BlackoutState.Awake); } - - MelonLogger.Msg($"BlackoutState " + BlackoutState); - MelonLogger.Msg($"curTime " + curTime); - MelonLogger.Msg($"lastAwakeTime " + lastAwakeTime); - - if (BlackoutState == 2) return; - - //check if we should enter/return to sleep mode - if (curTime > lastAwakeTime + (BlackoutState == 0 ? enterSleepTime * 60 : returnSleepTime)) + //enter full sleep mode + if (curTime > lastAwakeTime + SleepModeTimer) { - BlackoutState = 2; - MelonLogger.Msg("Entered sleep state."); + ChangeBlackoutState(BlackoutState.Sleeping); } } - - void Start() + private void HandleSleepingState() { - MelonLogger.Msg(Application.streamingAssetsPath); - - var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, "blackoutfader")); - if (myLoadedAssetBundle == null) + //small movement should enter drowsy state + if (angularMovement > drowsyThreshold) { - Debug.Log("Failed to load AssetBundle!"); - return; + ChangeBlackoutState(BlackoutState.Drowsy); } - - var prefab = myLoadedAssetBundle.LoadAsset("MyObject"); - Instantiate(prefab); - - myLoadedAssetBundle.Unload(false); - - prefab.transform.parent = PlayerSetup.Instance.GetActiveCamera().transform; - prefab.transform.localPosition = Vector3.zero; - prefab.transform.localRotation = Quaternion.identity; - prefab.transform.localScale = Vector3.zero; } } \ No newline at end of file diff --git a/Blackout/Main.cs b/Blackout/Main.cs index 98800a2..e4fa78a 100644 --- a/Blackout/Main.cs +++ b/Blackout/Main.cs @@ -1,59 +1,89 @@ -using ABI.CCK.Components; -using ABI_RC.Core.Player; -using ABI_RC.Systems.IK; -using ABI_RC.Systems.IK.SubSystems; +using ABI_RC.Core.Player; using HarmonyLib; using MelonLoader; -using RootMotion.FinalIK; using UnityEngine; -using UnityEngine.Events; namespace Blackout; public class Blackout : MelonMod { - BlackoutController m_blackoutController = null; - + private static bool inVR; private static MelonPreferences_Category m_categoryBlackout; - private static MelonPreferences_Entry m_entryEnabled; - private static MelonPreferences_Entry m_entryDrowsyThreshold; - private static MelonPreferences_Entry m_entryAwakeThreshold; - private static MelonPreferences_Entry m_entryEnterSleepTime; - private static MelonPreferences_Entry m_entryReturnSleepTime; + private static MelonPreferences_Entry m_entryEnabled, m_entryHudMessages; + //private static MelonPreferences_Entry m_entryVROnly; + private static MelonPreferences_Entry + m_entryDrowsyThreshold, m_entryAwakeThreshold, + m_entryDrowsyModeTimer, m_entrySleepModeTimer, + m_entryDrowsyDimStrength; public override void OnApplicationStart() { m_categoryBlackout = MelonPreferences.CreateCategory(nameof(Blackout)); m_entryEnabled = m_categoryBlackout.CreateEntry("Enabled", true, description: "Dim screen when sleeping."); + m_entryHudMessages = m_categoryBlackout.CreateEntry("Hud Messages", false, description: "Notify on state change."); m_entryDrowsyThreshold = m_categoryBlackout.CreateEntry("Drowsy Threshold", 1f, description: "Degrees of movement to return partial vision."); m_entryAwakeThreshold = m_categoryBlackout.CreateEntry("Awake Threshold", 12f, description: "Degrees of movement to return full vision."); - m_entryEnterSleepTime = m_categoryBlackout.CreateEntry("Enter Sleep Time", 3f, description: "How many minutes without movement until enter sleep mode."); - m_entryReturnSleepTime = m_categoryBlackout.CreateEntry("Return Sleep Time", 10f, description: "How many seconds should the wake state last before return."); + m_entryDrowsyModeTimer = m_categoryBlackout.CreateEntry("Enter Drowsy Time", 3f, description: "How many minutes without movement until enter drowsy mode."); + m_entrySleepModeTimer = m_categoryBlackout.CreateEntry("Enter Sleep Time", 10f, description: "How many seconds without movement until enter sleep mode."); + m_entryDrowsyDimStrength = m_categoryBlackout.CreateEntry("Drowsy Dim Strength", 0.5f, description: "How strong of a dimming effect should drowsy mode have."); + //m_entryVROnly = m_categoryBlackout.CreateEntry("VR Only", false, description: "Only enable mod in VR."); m_categoryBlackout.SaveToFile(false); m_entryEnabled.OnValueChangedUntyped += OnEnabled; + m_entryHudMessages.OnValueChangedUntyped += OnUpdateSettings; + m_entryDrowsyThreshold.OnValueChangedUntyped += OnUpdateSettings; + m_entryAwakeThreshold.OnValueChangedUntyped += OnUpdateSettings; + m_entryDrowsyModeTimer.OnValueChangedUntyped += OnUpdateSettings; + m_entrySleepModeTimer.OnValueChangedUntyped += OnUpdateSettings; + m_entryDrowsyDimStrength.OnValueChangedUntyped += OnUpdateSettings; + //m_entryVROnly.OnValueChangedUntyped += OnUpdateSettings; MelonLoader.MelonCoroutines.Start(WaitForLocalPlayer()); } System.Collections.IEnumerator WaitForLocalPlayer() { + //load blackout_controller.asset + AssetsHandler.Load(); + while (PlayerSetup.Instance == null) yield return null; - m_blackoutController = PlayerSetup.Instance.gameObject.AddComponent(); + inVR = PlayerSetup.Instance._inVr; + PlayerSetup.Instance.gameObject.AddComponent(); + + //update BlackoutController settings after it initializes + yield return new WaitForEndOfFrame(); + OnUpdateSettings(); } - public void OnEnabled() + private void OnEnabled() { - if (!m_blackoutController) return; - m_blackoutController.enabled = m_entryEnabled.Value; + if (!BlackoutController.Instance) return; + BlackoutController.Instance.enabled = m_entryEnabled.Value; } - public void OnUpdateSettings() + private void OnUpdateSettings() { - if (!m_blackoutController) return; - m_blackoutController.drowsyThreshold = m_entryDrowsyThreshold.Value; - m_blackoutController.wakeThreshold = m_entryAwakeThreshold.Value; - m_blackoutController.enterSleepTime = m_entryEnterSleepTime.Value; - m_blackoutController.returnSleepTime = m_entryReturnSleepTime.Value; + if (!BlackoutController.Instance) return; + BlackoutController.Instance.drowsyThreshold = m_entryDrowsyThreshold.Value; + BlackoutController.Instance.wakeThreshold = m_entryAwakeThreshold.Value; + BlackoutController.Instance.DrowsyModeTimer = m_entryDrowsyModeTimer.Value; + BlackoutController.Instance.SleepModeTimer = m_entrySleepModeTimer.Value; + BlackoutController.Instance.DrowsyDimStrength = m_entryDrowsyDimStrength.Value; + BlackoutController.Instance.HudMessages = m_entryHudMessages.Value; + } + + //Support for changing VRMode during runtime. + [HarmonyPatch] + private class HarmonyPatches + { + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), "CalibrateAvatar")] + private static void CheckVRModeSwitch() + { + if (inVR != PlayerSetup.Instance._inVr) + { + BlackoutController.Instance.SetupBlackoutInstance(); + } + } } } \ No newline at end of file diff --git a/Blackout/Properties/AssemblyInfo.cs b/Blackout/Properties/AssemblyInfo.cs index 659ebc4..728d4a7 100644 --- a/Blackout/Properties/AssemblyInfo.cs +++ b/Blackout/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ -using MelonLoader; +using Blackout.Properties; +using MelonLoader; using System.Reflection; -using Blackout.Properties; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] diff --git a/Blackout/Resource1.Designer.cs b/Blackout/Resource1.Designer.cs new file mode 100644 index 0000000..5c2b3b3 --- /dev/null +++ b/Blackout/Resource1.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Blackout { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resource1 { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resource1() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Blackout.Resource1", typeof(Resource1).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] blackout_controller { + get { + object obj = ResourceManager.GetObject("blackout_controller", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/Blackout/Resource1.resx b/Blackout/Resource1.resx new file mode 100644 index 0000000..4003b7c --- /dev/null +++ b/Blackout/Resource1.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Resources\blackout_controller.asset;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Blackout/resources/blackout_controller.asset b/Blackout/resources/blackout_controller.asset new file mode 100644 index 0000000..52e58cc Binary files /dev/null and b/Blackout/resources/blackout_controller.asset differ