< Summary

Class:PlayerName
Assembly:PlayerName
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/PlayerName/PlayerName.cs
Covered lines:117
Uncovered lines:24
Coverable lines:141
Total lines:347
Line coverage:82.9% (117 of 141)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
PlayerName()0%110100%
PlayerName()0%110100%
Awake()0%110100%
OnDestroy()0%110100%
SetName(...)0%110100%
Show(...)0%220100%
Hide(...)0%330100%
AddVisibilityConstaint(...)0%2100%
RemoveVisibilityConstaint(...)0%2100%
SetForceShow(...)0%42600%
SetIsTalking(...)0%110100%
SetYOffset(...)0%110100%
ScreenSpaceRect(...)0%2100%
ScreenSpacePos(...)0%110100%
OnNamesOpacityChanged(...)0%110100%
OnNamesVisibleChanged(...)0%330100%
OnProfanityFilterChanged(...)0%110100%
AsyncSetName()0%4.324072.73%
FilterName()0%6.65060%
IsProfanityFilteringEnabled()0%110100%
Update()0%110100%
SetRenderersVisible(...)0%220100%
Update(...)0%9.189086.96%
LookAtCamera(...)0%110100%
ResolveAlphaByDistance(...)0%3.583060%
UpdateVisuals(...)0%110100%
GetNearestAlphaStep(...)0%110100%
ScalePivotByDistance(...)0%110100%
GetPivotYOffsetByDistance(...)0%2.152066.67%
GetNameWithColorCodes(...)0%550100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/PlayerName/PlayerName.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using Cysharp.Threading.Tasks;
 4using DCL;
 5using DCL.ProfanityFiltering;
 6using TMPro;
 7using UnityEngine;
 8using UnityEngine.UI;
 9using Environment = DCL.Environment;
 10
 11[Serializable]
 12public class PlayerName : MonoBehaviour, IPlayerName
 13{
 14    internal const int DEFAULT_CANVAS_SORTING_ORDER = 0;
 15    internal const int FORCE_CANVAS_SORTING_ORDER = 40;
 116    internal static readonly int TALKING_ANIMATOR_BOOL = Animator.StringToHash("Talking");
 17    internal const float MINIMUM_ALPHA_TO_SHOW = 1f / 32f;
 18    internal const float ALPHA_TRANSITION_STEP_PER_SECOND = 1f / 0.25f;
 19    internal const float ALPHA_STEPS = 32f;
 20    internal const float TARGET_ALPHA_SHOW = 1;
 21    internal const float TARGET_ALPHA_HIDE = 0;
 22    internal const int BACKGROUND_HEIGHT = 30;
 23    internal const int BACKGROUND_EXTRA_WIDTH = 10;
 24
 25    [SerializeField] internal Canvas canvas;
 26    [SerializeField] internal CanvasGroup canvasGroup;
 27    [SerializeField] internal TextMeshProUGUI nameText;
 28    [SerializeField] internal Image background;
 29    [SerializeField] internal Transform pivot;
 30    [SerializeField] internal Animator talkingAnimator;
 31    [SerializeField] internal List<CanvasRenderer> canvasRenderers;
 32
 108033    internal BaseVariable<float> namesOpacity => DataStore.i.HUDs.avatarNamesOpacity;
 108034    internal BaseVariable<bool> namesVisible => DataStore.i.HUDs.avatarNamesVisible;
 35    internal BaseVariable<bool> profanityFilterEnabled;
 36
 37    internal bool forceShow;
 38    internal Color backgroundOriginalColor;
 36939    internal List<string> hideConstraints = new ();
 36940    internal string currentName = "";
 41
 42    private float alpha;
 43    private float targetAlpha;
 44    private bool renderersVisible;
 45    private bool isNameClaimed;
 46    private bool isUserGuest;
 47    private IProfanityFilter profanityFilter;
 48
 49    // TODO: remove this property, is only used on tests
 50    internal float Alpha
 51    {
 352        get => alpha;
 53
 54        set
 55        {
 356            alpha = Mathf.Clamp01(value);
 57
 358            if (alpha < 0.01f)
 59            {
 260                UpdateVisuals(0);
 261                SetRenderersVisible(false);
 262                return;
 63            }
 64
 165            UpdateVisuals(alpha);
 166            SetRenderersVisible(true);
 167        }
 68    }
 69
 70    // TODO: remove this property, is only used on tests
 71    internal float TargetAlpha
 72    {
 073        get => targetAlpha;
 374        set => targetAlpha = Mathf.Clamp01(value);
 75    }
 76
 77    private void Awake()
 78    {
 36079        backgroundOriginalColor = background.color;
 36080        canvas.sortingOrder = DEFAULT_CANVAS_SORTING_ORDER;
 36081        profanityFilterEnabled = DataStore.i.settings.profanityChatFilteringEnabled;
 36082        namesVisible.OnChange += OnNamesVisibleChanged;
 36083        namesOpacity.OnChange += OnNamesOpacityChanged;
 36084        profanityFilterEnabled.OnChange += OnProfanityFilterChanged;
 85
 36086        OnNamesVisibleChanged(namesVisible.Get(), true);
 36087        OnNamesOpacityChanged(namesOpacity.Get(), 1);
 36088        Show(true);
 36089    }
 90
 91    private void OnDestroy()
 92    {
 36093        namesVisible.OnChange -= OnNamesVisibleChanged;
 36094        namesOpacity.OnChange -= OnNamesOpacityChanged;
 36095        profanityFilterEnabled.OnChange -= OnProfanityFilterChanged;
 36096    }
 97
 98    public void SetName(string name, bool isClaimed, bool isGuest) =>
 1899        AsyncSetName(name, isClaimed, isGuest).Forget();
 100
 101    public void Show(bool instant = false)
 102    {
 381103        targetAlpha = TARGET_ALPHA_SHOW;
 381104        SetRenderersVisible(true);
 105
 381106        if (instant)
 364107            alpha = TARGET_ALPHA_SHOW;
 381108    }
 109
 110    public void Hide(bool instant = false)
 111    {
 29112        targetAlpha = TARGET_ALPHA_HIDE;
 113
 29114        if (instant && !forceShow)
 115        {
 15116            alpha = TARGET_ALPHA_HIDE;
 15117            SetRenderersVisible(false);
 118        }
 29119    }
 120
 121    // Note: Ideally we should separate the LODController logic from this one so we don't have to add constraints
 122    public void AddVisibilityConstaint(string constraint)
 123    {
 0124        hideConstraints.Add(constraint);
 0125    }
 126
 127    public void RemoveVisibilityConstaint(string constraint)
 128    {
 0129        hideConstraints.Remove(constraint);
 0130    }
 131
 132    public void SetForceShow(bool forceShow)
 133    {
 0134        canvas.enabled = forceShow || namesVisible.Get();
 0135        canvas.sortingOrder = forceShow ? FORCE_CANVAS_SORTING_ORDER : DEFAULT_CANVAS_SORTING_ORDER;
 0136        background.color = new Color(backgroundOriginalColor.r, backgroundOriginalColor.g, backgroundOriginalColor.b, fo
 0137        this.forceShow = forceShow;
 138
 0139        if (this.forceShow)
 0140            SetRenderersVisible(true);
 0141    }
 142
 143    public void SetIsTalking(bool talking)
 144    {
 5145        talkingAnimator.SetBool(TALKING_ANIMATOR_BOOL, talking);
 5146    }
 147
 148    public void SetYOffset(float yOffset)
 149    {
 5150        transform.localPosition = Vector3.up * yOffset;
 5151    }
 152
 153    public Rect ScreenSpaceRect(Camera mainCamera)
 154    {
 0155        Vector3 origin = mainCamera.WorldToScreenPoint(background.transform.position);
 0156        Vector2 size = background.rectTransform.sizeDelta;
 0157        return new Rect(origin.x, Screen.height - origin.y, size.x, size.y);
 158    }
 159
 160    public Vector3 ScreenSpacePos(Camera mainCamera)
 161    {
 25162        return mainCamera.WorldToScreenPoint(background.transform.position);
 163    }
 164
 165    internal void OnNamesOpacityChanged(float current, float previous)
 166    {
 366167        background.color = new Color(backgroundOriginalColor.r, backgroundOriginalColor.g, backgroundOriginalColor.b, cu
 366168    }
 169
 170    internal void OnNamesVisibleChanged(bool current, bool previous)
 171    {
 362172        canvas.enabled = current || forceShow;
 362173    }
 174
 175    private void OnProfanityFilterChanged(bool current, bool previous)
 176    {
 2177        SetName(currentName, isNameClaimed, isUserGuest);
 2178    }
 179
 180    private async UniTaskVoid AsyncSetName(string name, bool isClaimed, bool isGuest)
 181    {
 18182        currentName = name;
 18183        isNameClaimed = isClaimed;
 18184        isUserGuest = isGuest;
 185
 18186        if (name == null) return;
 187
 18188        name = await FilterName(currentName);
 18189        nameText.text = GetNameWithColorCodes(isClaimed, isGuest, name);
 18190        background.rectTransform.sizeDelta = new Vector2(nameText.GetPreferredValues().x + BACKGROUND_EXTRA_WIDTH, BACKG
 18191    }
 192
 193    private async UniTask<string> FilterName(string name)
 194    {
 18195        profanityFilter ??= Environment.i.serviceLocator.Get<IProfanityFilter>();
 196
 18197        return IsProfanityFilteringEnabled()
 198            ? await profanityFilter.Filter(name)
 199            : name;
 18200    }
 201
 202    private bool IsProfanityFilteringEnabled() =>
 18203        DataStore.i.settings.profanityChatFilteringEnabled.Get();
 204
 205    private void Update()
 206    {
 47207        Update(Time.deltaTime);
 47208    }
 209
 210    private void SetRenderersVisible(bool value)
 211    {
 447212        if (renderersVisible == value)
 68213            return;
 214
 2653215        canvasRenderers.ForEach(c => c.SetAlpha(value ? 1f : 0f));
 379216        renderersVisible = value;
 379217    }
 218
 219    internal void Update(float deltaTime)
 220    {
 50221        if (hideConstraints.Count > 0 || currentName == null)
 222        {
 0223            UpdateVisuals(0);
 0224            return;
 225        }
 226
 50227        float finalTargetAlpha = forceShow ? TARGET_ALPHA_SHOW : targetAlpha;
 50228        alpha = Mathf.MoveTowards(alpha, finalTargetAlpha, ALPHA_TRANSITION_STEP_PER_SECOND * deltaTime);
 229
 50230        if (CheckAndUpdateVisibility(alpha))
 2231            return;
 232
 48233        Vector3 cameraPosition = CommonScriptableObjects.cameraPosition.Get();
 48234        Vector3 cameraRight = CommonScriptableObjects.cameraRight.Get();
 48235        Quaternion cameraRotation = DataStore.i.camera.rotation.Get();
 236
 237        /*
 238         * TODO: We could obtain distance to player from the AvatarLODController but that coupling it's overkill and ugl
 239         * instead we should have a provider so all the subsystems can use it
 240         */
 48241        float distanceToCamera = Vector3.Distance(cameraPosition, gameObject.transform.position);
 48242        ScalePivotByDistance(distanceToCamera);
 48243        LookAtCamera(cameraRight, cameraRotation.eulerAngles);
 48244        pivot.transform.localPosition = Vector3.up * GetPivotYOffsetByDistance(distanceToCamera);
 245
 48246        float resolvedAlpha = forceShow ? TARGET_ALPHA_SHOW : ResolveAlphaByDistance(alpha, distanceToCamera, forceShow)
 247
 48248        if (CheckAndUpdateVisibility(resolvedAlpha))
 0249            return;
 250
 48251        SetRenderersVisible(true);
 48252        float resolvedAlphaStep = GetNearestAlphaStep(resolvedAlpha);
 48253        float canvasAlphaStep = GetNearestAlphaStep(canvasGroup.alpha);
 254
 48255        if (Math.Abs(resolvedAlphaStep - canvasAlphaStep) > Mathf.Epsilon)
 8256            UpdateVisuals(resolvedAlpha);
 257
 258        bool CheckAndUpdateVisibility(float checkAlpha)
 259        {
 194260            if (checkAlpha >= MINIMUM_ALPHA_TO_SHOW) return false;
 4261            if (!renderersVisible) return true;
 262
 0263            UpdateVisuals(0);
 0264            SetRenderersVisible(false);
 265
 0266            return true;
 267        }
 48268    }
 269
 270    internal void LookAtCamera(Vector3 cameraRight, Vector3 cameraEulerAngles)
 271    {
 49272        Transform cachedTransform = transform;
 49273        cachedTransform.right = -cameraRight; // This will set the Y rotation
 274
 275        // Now we use the negated X axis rotation to make the rotation steady in horizont
 49276        Vector3 finalEulerAngle = cachedTransform.localEulerAngles;
 49277        finalEulerAngle.x = -cameraEulerAngles.x;
 49278        cachedTransform.localEulerAngles = finalEulerAngle;
 49279    }
 280
 281    internal static float ResolveAlphaByDistance(float alphaValue, float distanceToCamera, bool forceShow)
 282    {
 48283        if (forceShow)
 0284            return alphaValue;
 285
 286        const float MIN_DISTANCE = 5;
 287
 48288        if (distanceToCamera < MIN_DISTANCE)
 48289            return alphaValue;
 290
 0291        return alphaValue * Mathf.Lerp(1, 0, (distanceToCamera - MIN_DISTANCE) / (DataStore.i.avatarsLOD.LODDistance.Get
 292    }
 293
 294    internal void UpdateVisuals(float resolvedAlpha)
 295    {
 11296        canvasGroup.alpha = resolvedAlpha;
 11297    }
 298
 299    internal float GetNearestAlphaStep(float alpha) =>
 96300        Mathf.Floor(alpha * ALPHA_STEPS);
 301
 302    internal void ScalePivotByDistance(float distanceToCamera)
 303    {
 48304        pivot.transform.localScale = Vector3.one * 0.15f * distanceToCamera;
 48305    }
 306
 307    internal float GetPivotYOffsetByDistance(float distanceToCamera)
 308    {
 309        const float NEAR_Y_OFFSET = 0f;
 310        const float FAR_Y_OFFSET = 0.1f;
 311        const float MAX_DISTANCE = 5;
 312
 48313        if (distanceToCamera >= MAX_DISTANCE)
 0314            return FAR_Y_OFFSET;
 315
 48316        return Mathf.Lerp(NEAR_Y_OFFSET, FAR_Y_OFFSET, distanceToCamera / MAX_DISTANCE);
 317    }
 318
 319    private string GetNameWithColorCodes(bool isClaimed, bool isGuest, string name)
 320    {
 18321        string text = name;
 322
 18323        if (isClaimed)
 1324            text = $"<color=#FFFFFF>{name}</color>";
 325        else
 326        {
 17327            if (isGuest)
 328            {
 1329                string[] split = name.Split('#', StringSplitOptions.RemoveEmptyEntries);
 330
 1331                text = split.Length > 1
 332                    ? $"<color=#A09BA8>{split[0]}</color><color=#716B7C>#{split[1]}</color>"
 333                    : $"<color=#A09BA8>{name}</color>";
 334            }
 335            else
 336            {
 16337                string[] split = name.Split('#');
 338
 16339                text = split.Length > 1
 340                    ? $"<color=#CFCDD4>{split[0]}</color><color=#A09BA8>#{split[1]}</color>"
 341                    : $"<color=#CFCDD4>{name}</color>";
 342            }
 343        }
 344
 18345        return text;
 346    }
 347}