From d033d3750e8d47260bc45b0effafc00332a41d80 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Wed, 12 Oct 2022 22:46:17 -0500 Subject: [PATCH] Testing Phase Basics are implemented. Needs playtesting before potential rewrite and implementation of avatar parameters & manual control. --- Blackout/AssetHandler.cs | 95 ++++++++ Blackout/Blackout.csproj | 23 ++ Blackout/BlackoutController.cs | 224 ++++++++++++++----- Blackout/Main.cs | 80 ++++--- Blackout/Properties/AssemblyInfo.cs | 4 +- Blackout/Resource1.Designer.cs | 73 ++++++ Blackout/Resource1.resx | 124 ++++++++++ Blackout/resources/blackout_controller.asset | Bin 0 -> 13454 bytes 8 files changed, 541 insertions(+), 82 deletions(-) create mode 100644 Blackout/AssetHandler.cs create mode 100644 Blackout/Resource1.Designer.cs create mode 100644 Blackout/Resource1.resx create mode 100644 Blackout/resources/blackout_controller.asset 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 0000000000000000000000000000000000000000..52e58cc994c225d484455efb230d12097c265d96 GIT binary patch literal 13454 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 literal 0 HcmV?d00001