NAK_CVR_Mods/Blackout/BlackoutController.cs
2022-10-17 20:00:40 -05:00

257 lines
No EOL
8.2 KiB
C#

using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.UI;
using MelonLoader;
using System.Text;
using UnityEngine;
namespace Blackout;
/*
Functionality heavily inspired by VRSleeper on Booth: https://booth.pm/ja/items/2151940
There are three states of "blackout":
0 - Awake (no effect)
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 static BlackoutController Instance;
public BlackoutState CurrentState = BlackoutState.Awake;
//degrees of movement to give partial vision
public float drowsyThreshold = 1f;
//degrees of movement to give complete vision
public float wakeThreshold = 12f;
//how long without movement until the screen dims
public float DrowsyModeTimer = 3f; // MINUTES
//how long should the wake state last before return
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;
//lower FPS while in sleep mode
public bool DropFPSOnSleep = false;
public enum BlackoutState
{
Awake = 0,
Drowsy,
Sleeping,
}
private GameObject activeModeCam;
private Quaternion oldHeadRotation = Quaternion.identity;
private float angularMovement = 0f;
private float curTime = 0f;
private float lastAwakeTime = 0f;
private int nextUpdate = 1;
private Animator blackoutAnimator;
private int targetFPS;
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.DrowsyStrength", DrowsyDimStrength);
break;
case BlackoutState.Drowsy:
blackoutAnimator.SetBool("BlackoutState.Drowsy", true);
blackoutAnimator.SetBool("BlackoutState.Sleeping", false);
blackoutAnimator.SetFloat("BlackoutSetting.DrowsyStrength", DrowsyDimStrength);
break;
case BlackoutState.Sleeping:
blackoutAnimator.SetBool("BlackoutState.Drowsy", false);
blackoutAnimator.SetBool("BlackoutState.Sleeping", true);
blackoutAnimator.SetFloat("BlackoutSetting.DrowsyStrength", DrowsyDimStrength);
break;
default:
break;
}
BlackoutState prevState = CurrentState;
CurrentState = newState;
SendHUDMessage($"Exiting {prevState} and entering {newState} state.");
ChangeTargetFPS();
}
//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) return;
blackoutGO.name = "BlackoutInstance";
blackoutAnimator = blackoutGO.GetComponent<Animator>();
SetupBlackoutInstance();
}
//Automatic State Change
void Update()
{
//only run once a second, angularMovement is "smoothed out" at high FPS otherwise
//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 = activeModeCam.transform.rotation;
angularMovement = Quaternion.Angle(oldHeadRotation, currentHeadRotation);
oldHeadRotation = currentHeadRotation;
//handle current state
switch (CurrentState)
{
case BlackoutState.Awake:
HandleAwakeState();
break;
case BlackoutState.Drowsy:
HandleDrowsyState();
break;
case BlackoutState.Sleeping:
HandleSleepingState();
break;
default:
break;
}
}
void OnEnable()
{
curTime = Time.time;
lastAwakeTime = curTime;
Camera.onPreRender += OnPreRender;
Camera.onPostRender += OnPostRender;
}
void OnDisable()
{
ChangeBlackoutState(BlackoutState.Awake);
Camera.onPreRender -= OnPreRender;
Camera.onPostRender -= OnPostRender;
}
void OnPreRender(Camera cam)
{
if (cam != activeModeCam.GetComponent<Camera>()) return;
blackoutAnimator.transform.localScale = Vector3.zero;
}
void OnPostRender(Camera cam)
{
blackoutAnimator.transform.localScale = Vector3.one;
}
public void SetupBlackoutInstance()
{
activeModeCam = PlayerSetup.Instance.GetActiveCamera();
blackoutAnimator.transform.parent = activeModeCam.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;
StringBuilder secondmessage = new StringBuilder();
if (enabled)
secondmessage = new StringBuilder(GetNextStateTimer().ToString() + " seconds till next state change.");
CohtmlHud.Instance.ViewDropTextImmediate("Blackout", message, secondmessage.ToString());
}
private void ChangeTargetFPS()
{
if (!DropFPSOnSleep) return;
//store target FPS to restore, i check each time just in case it changed
targetFPS = MetaPort.Instance.settings.GetSettingInt("GraphicsFramerateTarget", 0);
Application.targetFrameRate = (CurrentState == BlackoutState.Sleeping) ? 5 : targetFPS;
}
private void HandleAwakeState()
{
//small movement should reset sleep timer
if (angularMovement > drowsyThreshold)
{
lastAwakeTime = curTime;
}
//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)
{
ChangeBlackoutState(BlackoutState.Awake);
}
//enter full sleep mode
if (curTime > lastAwakeTime + SleepModeTimer)
{
ChangeBlackoutState(BlackoutState.Sleeping);
}
}
private void HandleSleepingState()
{
//small movement should enter drowsy state
if (angularMovement > drowsyThreshold)
{
ChangeBlackoutState(BlackoutState.Drowsy);
}
}
}