using OctoCore; using OctoCore.Components; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using Unity.VisualScripting; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.Rendering.Universal; public class SpiralStaircase : MonoBehaviour { [System.Serializable] public class DoorsPair { public Transform Left; public Transform Right; } [System.Serializable] public class State { public bool PlayerInside; public int Turns; public float TotalAngle; [Range(0, 1)] public float Progression; [Range(0, 1)] public float TotalProgression; } [System.Serializable] public class References { public UniversalRendererData RendererData; public GameObject Stairs; public DoorsPair[] Doors; public GameObject[] Inside; public GameObject[] Outside; } [System.Serializable] public class Settings { public float Offset = -12.25f; public float StartAngle = 450; public float EndAngle = 720; public float AdditionalTopAngle = 360; } [System.Serializable] public class HallucinationTrigger { [Range(0, 1)] public float Progression; public UnityEvent OnReached; [HideInInspector] public bool Triggered; } [System.Serializable] public class Events { public UnityEvent OnInside; public UnityEvent OnTopReached; public UnityEvent OnBottomReached; public HallucinationTrigger[] HallucinationTriggers; } [SerializeField, OctoReadOnly] private State _state; [SerializeField] private References _references; [SerializeField] private Settings _settings; [SerializeField] private Events _events; private List _doorsCoroutine; private float _entryAngle = 0f; void Awake() { _doorsCoroutine = new List(); for (int i = 0; i < _references.Doors.Length; i++) _doorsCoroutine.Add(null); } void Start() { ManageInside(); _events.OnBottomReached?.Invoke(); } void OnDisable() { _state.PlayerInside = false; ManageInside(); } void Update() { if (_state.PlayerInside) Count(); } void OnTriggerEnter(Collider col) { _entryAngle = _state.TotalAngle; if (OctoPlayer.Instance && OctoPlayer.Instance.gameObject == col.gameObject) { if (!_state.PlayerInside) { _state.PlayerInside = true; ManageInside(); } } } void OnTriggerExit(Collider col) { if (OctoPlayer.Instance && OctoPlayer.Instance.gameObject == col.gameObject) { if (_state.PlayerInside) { _state.PlayerInside = false; ManageInside(); } } } public void OpendDoors(bool open) { for (int i = 0; i < _references.Doors.Length; i++) OpendDoor(i, open); } public void OpendDoor(int id) { OpendDoor(id, true); } public void CloseDoor(int id) { OpendDoor(id, false); } public void SetDoorOpened(int id) { OpendDoor(id, true, false); } public void SetDoorClosed(int id) { OpendDoor(id, false, false); } private void OpendDoor(int id, bool open, bool animate = true) { if (_doorsCoroutine[id] != null) StopCoroutine(_doorsCoroutine[id]); StartCoroutine(_doorsCoroutine[id] = OpenDoorCoroutine(id, open, animate)); } IEnumerator OpenDoorCoroutine(int id, bool open, bool animate) { const float duration = .6f; DoorsPair door = _references.Doors[id]; Quaternion originRotL = door.Left.transform.localRotation; Quaternion originRotR = door.Right.transform.localRotation; Quaternion targetRotL = Quaternion.Euler(0, open ? 0 : 13.1f, 0); Quaternion targetRotR = Quaternion.Euler(0, open ? 0 :-13.1f, 0); if (animate) { for (float t = 0; t < duration; t += Time.deltaTime) { float lerp = .5f - Mathf.Cos(t / duration * Mathf.PI) * .5f; door.Left.transform.localRotation = Quaternion.Lerp(originRotL, targetRotL, lerp); door.Right.transform.localRotation = Quaternion.Lerp(originRotR, targetRotR, lerp); yield return new WaitForEndOfFrame(); } } door.Left.transform.localRotation = targetRotL; door.Right.transform.localRotation = targetRotR; _doorsCoroutine[id] = null; } public void SimulateTop() { _state.Progression = 0; _state.PlayerInside = true; _state.Turns = (int)_settings.EndAngle / 90; Count(); ManageInside(); } public void SimulateBottom() { _state.Progression = 1; _state.PlayerInside = true; _state.Turns = 0; Count(); ManageInside(); } private void Count() { if (!OctoPlayer.Instance) return; Vector2[] steps = new Vector2[4] { Vector2.up, Vector2.left, Vector2.down, Vector2.right }; Vector2 nextStep = steps[_state.Turns % steps.Length]; Vector3 relativePos3D = transform.InverseTransformPoint(OctoPlayer.Instance.transform.position); Vector2 relativePos2D = new Vector2(relativePos3D.x, relativePos3D.z); float quarterAngle = Vector2.Angle(nextStep, relativePos2D) * Mathf.Sign(Vector3.Cross(nextStep, relativePos2D).z); _state.TotalAngle = Mathf.Max(0, _state.Turns * 90 + quarterAngle); if (quarterAngle > 90 && quarterAngle < 100) { _state.Turns++; } else if (quarterAngle < 0 && _state.Turns > 0) { _state.Turns--; } float progression = Mathf.Clamp01(Mathf.InverseLerp(_settings.StartAngle, _settings.EndAngle, _state.TotalAngle)); if (progression >= .5f && _state.Progression < .5f) { _events.OnTopReached?.Invoke(); } else if (progression < .5f && _state.Progression >= .5f) { _events.OnBottomReached?.Invoke(); } _state.Progression = progression; _state.TotalProgression = Mathf.Clamp01( Mathf.InverseLerp(_entryAngle, _settings.EndAngle + _settings.AdditionalTopAngle, _state.TotalAngle) ); foreach (var trigger in _events.HallucinationTriggers) { if (!trigger.Triggered && _state.TotalProgression >= trigger.Progression) { trigger.Triggered = true; trigger.OnReached?.Invoke(); } else if (trigger.Triggered && _state.TotalProgression < trigger.Progression) { trigger.Triggered = false; } } _references.Stairs.transform.localPosition = new Vector3(0, _state.Progression * _settings.Offset, 0); } private void ManageInside() { foreach (GameObject obj in _references.Inside) obj.SetActive(_state.PlayerInside); foreach (GameObject obj in _references.Outside) obj.SetActive(!_state.PlayerInside); _events.OnInside?.Invoke(_state.PlayerInside); /*Shader.SetGlobalFloat("_MaskEnabled", _state.PlayerInside ? 0 : 1); foreach (var feature in _references.RendererData.rendererFeatures) { // Stencil Mask // VisibleThroughMask // HiddenThroughMask if (feature.name == "HiddenThroughMask") { feature.SetActive(!_state.PlayerInside); break; } }*/ } }