using System; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI; namespace RoR2.UI; [ExecuteAlways] public class DifficultyBarController : MonoBehaviour { [Serializable] public struct SegmentDef { [Tooltip("The default English string to use for the element at design time.")] public string debugString; [Tooltip("The final language token to use for this element at runtime.")] public string token; [Tooltip("The color to use for the panel.")] public Color color; } private class SegmentImageAnimation { public Image segmentImage; public float age; public float duration; public AnimationCurve colorCurve; public Color color0; public Color color1; public void Update(float t) { segmentImage.color = Color.Lerp(color0, color1, colorCurve.Evaluate(t)); } } [Header("Component References")] public RectTransform viewPort; public RectTransform segmentContainer; public Image difficultyBarBackdrop; [Tooltip("How wide each segment should be.")] [Header("Layout")] public float elementWidth; public float levelsPerSegment; public float debugTime; [Header("Segment Parameters")] public SegmentDef[] segmentDefs; [Tooltip("The prefab to instantiate for each segment.")] public GameObject segmentPrefab; [Header("Colors")] public float pastSaturationMultiplier; public float pastValueMultiplier; public Color pastLabelColor; public float currentSaturationMultiplier; public float currentValueMultiplier; public Color currentLabelColor; public float upcomingSaturationMultiplier; public float upcomingValueMultiplier; public Color upcomingLabelColor; [Tooltip("If the increasedDifficultyBarFX user setting is enabled and the next difficulty color's value is below this, flash white instead.")] public float backdropFlashWhiteThreshold = 0.43f; [Header("Animations")] public AnimationCurve fadeAnimationCurve; public float fadeAnimationDuration = 1f; public AnimationCurve flashAnimationCurve; public float flashAnimationDuration = 0.5f; private int currentSegmentIndex = -1; private static readonly Color labelFadedColor = Color.Lerp(Color.gray, Color.white, 0.5f); [SerializeField] private string onDifficultyChangeSoundString = "Play_wDifficulty_New"; [Header("Final Segment")] public Sprite finalSegmentSprite; private HUD hud; private float scrollX; private float scrollXRaw; private float lastSegmentScroll; [Tooltip("Do not set this manually. Regenerate the children instead.")] public Image[] images; [Tooltip("Do not set this manually. Regenerate the children instead.")] public TextMeshProUGUI[] labels; public RawImage[] wormGearImages; public float UVScaleToScrollX; public float gearUVOffset; private readonly List playingAnimations = new List(); private static Color ColorMultiplySaturationAndValue(ref Color col, float saturationMultiplier, float valueMultiplier) { Color.RGBToHSV(col, out var H, out var S, out var V); return Color.HSVToRGB(H, S * saturationMultiplier, V * valueMultiplier); } private void OnCurrentSegmentIndexChanged(int newSegmentIndex) { if (!Application.isPlaying) { return; } int num = newSegmentIndex - 1; float width = viewPort.rect.width; int i = 0; for (int num2 = images.Length - 1; i < num2; i++) { Image obj = images[i]; TextMeshProUGUI textMeshProUGUI = labels[i]; RectTransform rectTransform = obj.rectTransform; bool flag2 = (obj.enabled = rectTransform.offsetMax.x + scrollX >= 0f && rectTransform.offsetMin.x + scrollX <= width); textMeshProUGUI.enabled = flag2; } int num3 = images.Length - 1; Image obj2 = images[num3]; TextMeshProUGUI textMeshProUGUI2 = labels[num3]; bool flag4 = (obj2.enabled = obj2.rectTransform.offsetMax.x + scrollX >= 0f); textMeshProUGUI2.enabled = flag4; for (int j = 0; j <= num; j++) { images[j].color = ColorMultiplySaturationAndValue(ref segmentDefs[j].color, pastSaturationMultiplier, pastValueMultiplier); labels[j].color = pastLabelColor; } for (int k = newSegmentIndex + 1; k < images.Length; k++) { images[k].color = ColorMultiplySaturationAndValue(ref segmentDefs[k].color, upcomingSaturationMultiplier, upcomingValueMultiplier); labels[k].color = upcomingLabelColor; } Image image = ((num != -1) ? images[num] : null); Image image2 = ((newSegmentIndex != -1) ? images[newSegmentIndex] : null); TextMeshProUGUI textMeshProUGUI3 = ((newSegmentIndex != -1) ? labels[newSegmentIndex] : null); if ((bool)image) { playingAnimations.Add(new SegmentImageAnimation { age = 0f, duration = fadeAnimationDuration, segmentImage = image, colorCurve = fadeAnimationCurve, color0 = segmentDefs[num].color, color1 = ColorMultiplySaturationAndValue(ref segmentDefs[num].color, pastSaturationMultiplier, pastValueMultiplier) }); } Color color = ColorMultiplySaturationAndValue(ref segmentDefs[newSegmentIndex].color, currentSaturationMultiplier, currentValueMultiplier); if ((bool)image2) { playingAnimations.Add(new SegmentImageAnimation { age = 0f, duration = flashAnimationDuration, segmentImage = image2, colorCurve = flashAnimationCurve, color0 = color, color1 = Color.white }); } if ((bool)hud && (bool)difficultyBarBackdrop) { UserProfile userProfile = hud.localUserViewer.userProfile; if (userProfile != null && userProfile.increasedDifficultyBarFX) { Color.RGBToHSV(segmentDefs[newSegmentIndex].color, out var _, out var _, out var V); playingAnimations.Add(new SegmentImageAnimation { age = 0f, duration = flashAnimationDuration, segmentImage = difficultyBarBackdrop, colorCurve = flashAnimationCurve, color0 = difficultyBarBackdrop.color, color1 = ((V < backdropFlashWhiteThreshold) ? Color.white : color) }); if (newSegmentIndex > 0) { Util.PlaySound(onDifficultyChangeSoundString, RoR2Application.instance.gameObject); } } } if ((bool)textMeshProUGUI3) { textMeshProUGUI3.color = currentLabelColor; } } private void SetSegmentScroll(float segmentScroll) { if (segmentScroll == lastSegmentScroll) { return; } lastSegmentScroll = segmentScroll; float num = segmentDefs.Length + 2; if (segmentScroll > num) { segmentScroll = num - 1f + (segmentScroll - Mathf.Floor(segmentScroll)); } scrollXRaw = (segmentScroll - 1f) * (0f - elementWidth); scrollX = Mathf.Floor(scrollXRaw); int num2 = currentSegmentIndex; currentSegmentIndex = Mathf.Clamp(Mathf.FloorToInt(segmentScroll), 0, segmentContainer.childCount - 1); if (num2 != currentSegmentIndex) { OnCurrentSegmentIndexChanged(currentSegmentIndex); } Vector2 offsetMin = segmentContainer.offsetMin; offsetMin.x = scrollX; segmentContainer.offsetMin = offsetMin; if ((bool)segmentContainer && segmentContainer.childCount > 0) { int num3 = segmentContainer.childCount - 1; RectTransform rectTransform = (RectTransform)segmentContainer.GetChild(num3); RectTransform rectTransform2 = (RectTransform)rectTransform.Find("Label"); _ = labels[num3]; if (segmentScroll >= (float)(num3 - 1)) { float num4 = elementWidth; Vector2 offsetMin2 = rectTransform.offsetMin; offsetMin2.x = CalcSegmentStartX(num3); rectTransform.offsetMin = offsetMin2; Vector2 offsetMax = rectTransform.offsetMax; offsetMax.x = offsetMin2.x + num4; rectTransform.offsetMax = offsetMax; rectTransform2.anchorMin = new Vector2(0f, 0f); rectTransform2.anchorMax = new Vector2(0f, 1f); rectTransform2.offsetMin = new Vector2(0f, 0f); rectTransform2.offsetMax = new Vector2(elementWidth, 0f); } else { rectTransform.offsetMax = rectTransform.offsetMin + new Vector2(elementWidth, 0f); SetLabelDefaultDimensions(rectTransform2); } } } private float CalcSegmentStartX(int i) { return (float)i * elementWidth; } private float CalcSegmentEndX(int i) { return (float)(i + 1) * elementWidth; } private void SetLabelDefaultDimensions(RectTransform labelRectTransform) { labelRectTransform.anchorMin = new Vector2(0f, 0f); labelRectTransform.anchorMax = new Vector2(1f, 1f); labelRectTransform.pivot = new Vector2(0.5f, 0.5f); labelRectTransform.offsetMin = new Vector2(0f, 0f); labelRectTransform.offsetMax = new Vector2(0f, 0f); } private void SetSegmentCount(uint desiredCount) { if ((bool)segmentContainer && (bool)segmentPrefab) { uint num = (uint)segmentContainer.childCount; if (images == null || images.Length != desiredCount) { images = new Image[desiredCount]; labels = new TextMeshProUGUI[desiredCount]; } int i = 0; for (int num2 = Mathf.Min(images.Length, segmentContainer.childCount); i < num2; i++) { Transform child = segmentContainer.GetChild(i); images[i] = child.GetComponent(); labels[i] = child.Find("Label").GetComponent(); } while (num > desiredCount) { UnityEngine.Object.DestroyImmediate(segmentContainer.GetChild((int)(num - 1)).gameObject); num--; } for (; num < desiredCount; num++) { GameObject gameObject = UnityEngine.Object.Instantiate(segmentPrefab, segmentContainer); gameObject.SetActive(value: true); images[i] = gameObject.GetComponent(); labels[i] = gameObject.transform.Find("Label").GetComponent(); i++; } } } private void SetupSegments() { if ((bool)segmentContainer && (bool)segmentPrefab) { SetSegmentCount((uint)segmentDefs.Length); for (int i = 0; i < segmentContainer.childCount; i++) { SetupSegment((RectTransform)segmentContainer.GetChild(i), ref segmentDefs[i], i); } SetupFinalSegment((RectTransform)segmentContainer.GetChild(segmentContainer.childCount - 1)); } } private static void ScaleLabelToWidth(TextMeshProUGUI label, float width) { RectTransform obj = (RectTransform)label.transform; float x = label.textBounds.size.x; Vector3 localScale = obj.localScale; localScale.x = width / x; obj.localScale = localScale; } private void SetupFinalSegment(RectTransform segmentTransform) { TextMeshProUGUI[] array = segmentTransform.GetComponentsInChildren(); int num = 4; if (array.Length < num) { TextMeshProUGUI[] array2 = new TextMeshProUGUI[num]; for (int i = 0; i < array.Length; i++) { array2[i] = array[i]; } for (int j = array.Length; j < num; j++) { array2[j] = UnityEngine.Object.Instantiate(array[0].gameObject, segmentTransform).GetComponent(); } array = array2; } int k = 0; for (int num2 = array.Length; k < num2; k++) { TextMeshProUGUI obj = array[k]; obj.enableWordWrapping = false; obj.overflowMode = TextOverflowModes.Overflow; obj.alignment = TextAlignmentOptions.MidlineLeft; obj.text = Language.GetString(segmentDefs[segmentDefs.Length - 1].token); obj.enableAutoSizing = true; Vector3 localPosition = obj.transform.localPosition; localPosition.x = (float)k * elementWidth; obj.transform.localPosition = localPosition; } segmentTransform.GetComponent().sprite = finalSegmentSprite; } private void SetupSegment(RectTransform segmentTransform, ref SegmentDef segmentDef, int i) { Vector2 offsetMin = segmentTransform.offsetMin; Vector2 offsetMax = segmentTransform.offsetMax; offsetMin.x = CalcSegmentStartX(i); offsetMax.x = CalcSegmentEndX(i); segmentTransform.offsetMin = offsetMin; segmentTransform.offsetMax = offsetMax; segmentTransform.GetComponent().color = segmentDef.color; ((RectTransform)segmentTransform.Find("Label")).GetComponent().token = segmentDef.token; } private void Awake() { hud = GetComponentInParent(); SetupSegments(); } private void DoBarUpdates(float deltaTime) { if ((bool)Run.instance) { SetSegmentScroll((Run.instance.ambientLevel - 1f) / levelsPerSegment); } if (Application.isPlaying) { RunAnimations(deltaTime); } } private void Update() { DoBarUpdates(Time.deltaTime); UpdateGears(); } private void UpdateGears() { RawImage[] array = wormGearImages; foreach (RawImage obj in array) { Rect uvRect = obj.uvRect; float num = Mathf.Sign(uvRect.width); uvRect.x = scrollXRaw * UVScaleToScrollX * num + ((num < 0f) ? gearUVOffset : 0f); obj.uvRect = uvRect; } } private void RunAnimations(float deltaTime) { for (int num = playingAnimations.Count - 1; num >= 0; num--) { SegmentImageAnimation segmentImageAnimation = playingAnimations[num]; segmentImageAnimation.age += deltaTime; float num2 = Mathf.Clamp01(segmentImageAnimation.age / segmentImageAnimation.duration); segmentImageAnimation.Update(num2); if (num2 >= 1f) { playingAnimations.RemoveAt(num); } } } }