< Summary

Class:PlayerName
Assembly:PlayerName
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/PlayerName/PlayerName.cs
Covered lines:115
Uncovered lines:29
Coverable lines:144
Total lines:354
Line coverage:79.8% (115 of 144)
Covered branches:0
Total branches:0
Covered methods:29
Total methods:36
Method coverage:80.5% (29 of 36)

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%72800%
SetIsTalking(...)0%2100%
SetYOffset(...)0%2100%
ScreenSpaceRect(...)0%2100%
ScreenSpacePos(...)0%110100%
OnNamesOpacityChanged(...)0%110100%
OnNamesVisibleChanged(...)0%330100%
OnProfanityFilterChanged(...)0%110100%
AsyncSetName()0%4.064084.62%
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{
 114    internal static readonly int TALKING_ANIMATOR_BOOL = Animator.StringToHash("Talking");
 15    internal const float MINIMUM_ALPHA_TO_SHOW = 1f / 32f;
 16    internal const float ALPHA_TRANSITION_STEP_PER_SECOND = 1f / 0.25f;
 17    internal const float ALPHA_STEPS = 32f;
 18    internal const float TARGET_ALPHA_SHOW = 1;
 19    internal const float TARGET_ALPHA_HIDE = 0;
 20    internal const int BACKGROUND_HEIGHT = 30;
 21    internal const int BACKGROUND_EXTRA_WIDTH = 10;
 22
 23    [SerializeField] internal Canvas canvas;
 24    [SerializeField] internal CanvasGroup canvasGroup;
 25    [SerializeField] internal TextMeshProUGUI nameText;
 26    [SerializeField] internal Image background;
 27    [SerializeField] internal Transform pivot;
 28    [SerializeField] internal Animator talkingAnimator;
 29    [SerializeField] internal List<CanvasRenderer> canvasRenderers;
 30    [SerializeField] internal Material nameMaterial;
 31    [SerializeField] internal Material nameOnTopMaterial;
 32    [SerializeField] internal Material bgOnTopMaterial;
 33
 100234    internal BaseVariable<float> namesOpacity => DataStore.i.HUDs.avatarNamesOpacity;
 100235    internal BaseVariable<bool> namesVisible => DataStore.i.HUDs.avatarNamesVisible;
 36    internal BaseVariable<bool> profanityFilterEnabled;
 37
 38    internal bool forceShow;
 39    internal Color backgroundOriginalColor;
 34040    internal List<string> hideConstraints = new ();
 34041    internal string currentName = "";
 42
 43    private float alpha;
 44    private float targetAlpha;
 45    private bool renderersVisible;
 46    private bool isNameClaimed;
 47    private bool isUserGuest;
 48    private IProfanityFilter profanityFilter;
 49
 50    // TODO: remove this property, is only used on tests
 51    internal float Alpha
 52    {
 353        get => alpha;
 54
 55        set
 56        {
 357            alpha = Mathf.Clamp01(value);
 58
 359            if (alpha < 0.01f)
 60            {
 261                UpdateVisuals(0);
 262                SetRenderersVisible(false);
 263                return;
 64            }
 65
 166            UpdateVisuals(alpha);
 167            SetRenderersVisible(true);
 168        }
 69    }
 70
 71    // TODO: remove this property, is only used on tests
 72    internal float TargetAlpha
 73    {
 074        get => targetAlpha;
 375        set => targetAlpha = Mathf.Clamp01(value);
 76    }
 77
 78    private void Awake()
 79    {
 33480        backgroundOriginalColor = background.color;
 33481        profanityFilterEnabled = DataStore.i.settings.profanityChatFilteringEnabled;
 33482        namesVisible.OnChange += OnNamesVisibleChanged;
 33483        namesOpacity.OnChange += OnNamesOpacityChanged;
 33484        profanityFilterEnabled.OnChange += OnProfanityFilterChanged;
 85
 33486        OnNamesVisibleChanged(namesVisible.Get(), true);
 33487        OnNamesOpacityChanged(namesOpacity.Get(), 1);
 33488        Show(true);
 33489    }
 90
 91    private void OnDestroy()
 92    {
 33493        namesVisible.OnChange -= OnNamesVisibleChanged;
 33494        namesOpacity.OnChange -= OnNamesOpacityChanged;
 33495        profanityFilterEnabled.OnChange -= OnProfanityFilterChanged;
 33496    }
 97
 98    public void SetName(string name, bool isClaimed, bool isGuest) =>
 999        AsyncSetName(name, isClaimed, isGuest).Forget();
 100
 101    public void Show(bool instant = false)
 102    {
 344103        targetAlpha = TARGET_ALPHA_SHOW;
 344104        SetRenderersVisible(true);
 105
 344106        if (instant)
 334107            alpha = TARGET_ALPHA_SHOW;
 344108    }
 109
 110    public void Hide(bool instant = false)
 111    {
 18112        targetAlpha = TARGET_ALPHA_HIDE;
 113
 18114        if (instant && !forceShow)
 115        {
 4116            alpha = TARGET_ALPHA_HIDE;
 4117            SetRenderersVisible(false);
 118        }
 18119    }
 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();
 135
 0136        background.color = new Color(backgroundOriginalColor.r, backgroundOriginalColor.g, backgroundOriginalColor.b, fo
 0137        background.material = forceShow ? bgOnTopMaterial : null;
 0138        nameText.fontSharedMaterial = forceShow ? nameOnTopMaterial : nameMaterial;
 0139        this.forceShow = forceShow;
 140
 0141        if (this.forceShow)
 0142            SetRenderersVisible(true);
 0143    }
 144
 145    public void SetIsTalking(bool talking)
 146    {
 0147        talkingAnimator.SetBool(TALKING_ANIMATOR_BOOL, talking);
 0148    }
 149
 150    public void SetYOffset(float yOffset)
 151    {
 0152        transform.localPosition = Vector3.up * yOffset;
 0153    }
 154
 155    public Rect ScreenSpaceRect(Camera mainCamera)
 156    {
 0157        Vector3 origin = mainCamera.WorldToScreenPoint(background.transform.position);
 0158        Vector2 size = background.rectTransform.sizeDelta;
 0159        return new Rect(origin.x, Screen.height - origin.y, size.x, size.y);
 160    }
 161
 162    public Vector3 ScreenSpacePos(Camera mainCamera)
 163    {
 24164        return mainCamera.WorldToScreenPoint(background.transform.position);
 165    }
 166
 167    internal void OnNamesOpacityChanged(float current, float previous)
 168    {
 340169        background.color = new Color(backgroundOriginalColor.r, backgroundOriginalColor.g, backgroundOriginalColor.b, cu
 340170    }
 171
 172    internal void OnNamesVisibleChanged(bool current, bool previous)
 173    {
 336174        canvas.enabled = current || forceShow;
 336175    }
 176
 177    private void OnProfanityFilterChanged(bool current, bool previous)
 178    {
 2179        SetName(currentName, isNameClaimed, isUserGuest);
 2180    }
 181
 182    private async UniTaskVoid AsyncSetName(string name, bool isClaimed, bool isGuest)
 183    {
 9184        currentName = name;
 9185        isNameClaimed = isClaimed;
 9186        isUserGuest = isGuest;
 187
 9188        if (string.IsNullOrEmpty(name))
 189        {
 2190            background.rectTransform.sizeDelta = Vector3.zero;
 2191            nameText.text = name;
 2192            return;
 193        }
 194
 7195        name = await FilterName(currentName);
 7196        nameText.text = GetNameWithColorCodes(isClaimed, isGuest, name);
 7197        background.rectTransform.sizeDelta = new Vector2(nameText.GetPreferredValues().x + BACKGROUND_EXTRA_WIDTH, BACKG
 9198    }
 199
 200    private async UniTask<string> FilterName(string name)
 201    {
 7202        profanityFilter ??= Environment.i.serviceLocator.Get<IProfanityFilter>();
 203
 7204        return IsProfanityFilteringEnabled()
 205            ? await profanityFilter.Filter(name)
 206            : name;
 7207    }
 208
 209    private bool IsProfanityFilteringEnabled() =>
 7210        DataStore.i.settings.profanityChatFilteringEnabled.Get();
 211
 212    private void Update()
 213    {
 35214        Update(Time.deltaTime);
 35215    }
 216
 217    private void SetRenderersVisible(bool value)
 218    {
 387219        if (renderersVisible == value)
 49220            return;
 221
 2366222        canvasRenderers.ForEach(c => c.SetAlpha(value ? 1f : 0f));
 338223        renderersVisible = value;
 338224    }
 225
 226    internal void Update(float deltaTime)
 227    {
 38228        if (hideConstraints.Count > 0 || currentName == null)
 229        {
 0230            UpdateVisuals(0);
 0231            return;
 232        }
 233
 38234        float finalTargetAlpha = forceShow ? TARGET_ALPHA_SHOW : targetAlpha;
 38235        alpha = Mathf.MoveTowards(alpha, finalTargetAlpha, ALPHA_TRANSITION_STEP_PER_SECOND * deltaTime);
 236
 38237        if (CheckAndUpdateVisibility(alpha))
 2238            return;
 239
 36240        Vector3 cameraPosition = CommonScriptableObjects.cameraPosition.Get();
 36241        Vector3 cameraRight = CommonScriptableObjects.cameraRight.Get();
 36242        Quaternion cameraRotation = DataStore.i.camera.rotation.Get();
 243
 244        /*
 245         * TODO: We could obtain distance to player from the AvatarLODController but that coupling it's overkill and ugl
 246         * instead we should have a provider so all the subsystems can use it
 247         */
 36248        float distanceToCamera = Vector3.Distance(cameraPosition, gameObject.transform.position);
 36249        ScalePivotByDistance(distanceToCamera);
 36250        LookAtCamera(cameraRight, cameraRotation.eulerAngles);
 36251        pivot.transform.localPosition = Vector3.up * GetPivotYOffsetByDistance(distanceToCamera);
 252
 36253        float resolvedAlpha = forceShow ? TARGET_ALPHA_SHOW : ResolveAlphaByDistance(alpha, distanceToCamera, forceShow)
 254
 36255        if (CheckAndUpdateVisibility(resolvedAlpha))
 0256            return;
 257
 36258        SetRenderersVisible(true);
 36259        float resolvedAlphaStep = GetNearestAlphaStep(resolvedAlpha);
 36260        float canvasAlphaStep = GetNearestAlphaStep(canvasGroup.alpha);
 261
 36262        if (Math.Abs(resolvedAlphaStep - canvasAlphaStep) > Mathf.Epsilon)
 7263            UpdateVisuals(resolvedAlpha);
 264
 265        bool CheckAndUpdateVisibility(float checkAlpha)
 266        {
 146267            if (checkAlpha >= MINIMUM_ALPHA_TO_SHOW) return false;
 4268            if (!renderersVisible) return true;
 269
 0270            UpdateVisuals(0);
 0271            SetRenderersVisible(false);
 272
 0273            return true;
 274        }
 36275    }
 276
 277    internal void LookAtCamera(Vector3 cameraRight, Vector3 cameraEulerAngles)
 278    {
 37279        Transform cachedTransform = transform;
 37280        cachedTransform.right = -cameraRight; // This will set the Y rotation
 281
 282        // Now we use the negated X axis rotation to make the rotation steady in horizont
 37283        Vector3 finalEulerAngle = cachedTransform.localEulerAngles;
 37284        finalEulerAngle.x = -cameraEulerAngles.x;
 37285        cachedTransform.localEulerAngles = finalEulerAngle;
 37286    }
 287
 288    internal static float ResolveAlphaByDistance(float alphaValue, float distanceToCamera, bool forceShow)
 289    {
 36290        if (forceShow)
 0291            return alphaValue;
 292
 293        const float MIN_DISTANCE = 5;
 294
 36295        if (distanceToCamera < MIN_DISTANCE)
 36296            return alphaValue;
 297
 0298        return alphaValue * Mathf.Lerp(1, 0, (distanceToCamera - MIN_DISTANCE) / (DataStore.i.avatarsLOD.LODDistance.Get
 299    }
 300
 301    internal void UpdateVisuals(float resolvedAlpha)
 302    {
 10303        canvasGroup.alpha = resolvedAlpha;
 10304    }
 305
 306    internal float GetNearestAlphaStep(float alpha) =>
 72307        Mathf.Floor(alpha * ALPHA_STEPS);
 308
 309    internal void ScalePivotByDistance(float distanceToCamera)
 310    {
 36311        pivot.transform.localScale = Vector3.one * 0.15f * distanceToCamera;
 36312    }
 313
 314    internal float GetPivotYOffsetByDistance(float distanceToCamera)
 315    {
 316        const float NEAR_Y_OFFSET = 0f;
 317        const float FAR_Y_OFFSET = 0.1f;
 318        const float MAX_DISTANCE = 5;
 319
 36320        if (distanceToCamera >= MAX_DISTANCE)
 0321            return FAR_Y_OFFSET;
 322
 36323        return Mathf.Lerp(NEAR_Y_OFFSET, FAR_Y_OFFSET, distanceToCamera / MAX_DISTANCE);
 324    }
 325
 326    private string GetNameWithColorCodes(bool isClaimed, bool isGuest, string name)
 327    {
 7328        string text = name;
 329
 7330        if (isClaimed)
 1331            text = $"<color=#FFFFFF>{name}</color>";
 332        else
 333        {
 6334            if (isGuest)
 335            {
 1336                string[] split = name.Split('#', StringSplitOptions.RemoveEmptyEntries);
 337
 1338                text = split.Length > 1
 339                    ? $"<color=#A09BA8>{split[0]}</color><color=#716B7C>#{split[1]}</color>"
 340                    : $"<color=#A09BA8>{name}</color>";
 341            }
 342            else
 343            {
 5344                string[] split = name.Split('#');
 345
 5346                text = split.Length > 1
 347                    ? $"<color=#CFCDD4>{split[0]}</color><color=#A09BA8>#{split[1]}</color>"
 348                    : $"<color=#CFCDD4>{name}</color>";
 349            }
 350        }
 351
 7352        return text;
 353    }
 354}