443 lines
13 KiB
C#
443 lines
13 KiB
C#
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<SegmentImageAnimation> playingAnimations = new List<SegmentImageAnimation>();
|
|
|
|
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<Image>();
|
|
labels[i] = child.Find("Label").GetComponent<TextMeshProUGUI>();
|
|
}
|
|
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<Image>();
|
|
labels[i] = gameObject.transform.Find("Label").GetComponent<TextMeshProUGUI>();
|
|
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<TextMeshProUGUI>();
|
|
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<TextMeshProUGUI>();
|
|
}
|
|
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<Image>().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<Image>().color = segmentDef.color;
|
|
((RectTransform)segmentTransform.Find("Label")).GetComponent<LanguageTextMeshController>().token = segmentDef.token;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
hud = GetComponentInParent<HUD>();
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|