< Summary

Class:DCL.Bots.BotsController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/BotsController/BotsController.cs
Covered lines:11
Uncovered lines:181
Coverable lines:192
Total lines:454
Line coverage:5.7% (11 of 192)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
BotsController()0%110100%
EnsureGlobalSceneAndCatalog()0%30500%
BuildRandomizedCollectionsURL()0%12300%
PopulateCatalog(...)0%6002400%
InstantiateBotsAtWorldPos()0%20400%
PatchWorldPosInstantiationConfig(...)0%20400%
InstantiateBotsAtCoords()0%12300%
PatchCoordsInstantiationConfig(...)0%12300%
InstantiateBot(...)0%12300%
RemoveBot(...)0%6200%
ClearBots()0%6200%
GetRandomizedWearablesSet()0%1101000%
StartRandomMovement(...)0%30500%
PatchCoordsRandomMovementConfig(...)0%42600%
StopRandomMovement()0%6200%
RandomMovementRoutine()0%30500%
UpdateEntityTransform(...)0%2100%
GetPBTransform(...)0%2100%
Log(...)0%2100%

File(s)

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

#LineLine coverage
 1using System.Collections;
 2using System.Collections.Generic;
 3using DCL.Controllers;
 4using DCL.Interface;
 5using DCL.Models;
 6using DCL.Helpers;
 7using Google.Protobuf;
 8using UnityEngine;
 9using DCL.Configuration;
 10using Random = UnityEngine.Random;
 11
 12namespace DCL.Bots
 13{
 14    /// <summary>
 15    /// Bots Tool: BotsController
 16    ///
 17    /// Used to spawn bots/avatarShapes for debugging and profiling purposes.
 18    /// </summary>
 19    public class BotsController : IBotsController
 20    {
 21        private ParcelScene globalScene;
 66622        private List<string> randomizedCollections = new List<string>();
 66623        private List<string> instantiatedBots = new List<string>();
 66624        private List<string> eyesWearableIds = new List<string>();
 66625        private List<string> eyebrowsWearableIds = new List<string>();
 66626        private List<string> mouthWearableIds = new List<string>();
 66627        private List<string> hairWearableIds = new List<string>();
 66628        private List<string> facialWearableIds = new List<string>();
 66629        private List<string> upperBodyWearableIds = new List<string>();
 66630        private List<string> lowerBodyWearableIds = new List<string>();
 66631        private List<string> feetWearableIds = new List<string>();
 66632        private List<string> bodyshapeWearableIds = new List<string>();
 33
 34        private Coroutine movementRoutine = null;
 35
 36        /// <summary>
 37        /// Makes sure the Catalogue with all the wearables has already been loaded, otherwise it loads it
 38        /// </summary>
 39        private IEnumerator EnsureGlobalSceneAndCatalog()
 40        {
 041            if (globalScene != null)
 042                yield break;
 43
 044            globalScene = Environment.i.world.state.loadedScenes[Environment.i.world.state.globalSceneIds[0]] as ParcelS
 45
 046            CatalogController.wearableCatalog.Clear();
 47
 048            yield return WearablesFetchingHelper.GetRandomCollections(20, true, randomizedCollections);
 49
 050            List<WearableItem> wearableItems = new List<WearableItem>();
 051            yield return WearablesFetchingHelper.GetWearableItems(BuildRandomizedCollectionsURL(), wearableItems);
 52
 053            PopulateCatalog(wearableItems);
 054        }
 55
 56        string BuildRandomizedCollectionsURL()
 57        {
 058            if (randomizedCollections.Count == 0)
 059                return null;
 60
 061            string finalUrl = WearablesFetchingHelper.WEARABLES_FETCH_URL;
 62
 063            finalUrl += "collectionId=" + randomizedCollections[0];
 064            for (int i = 1; i < randomizedCollections.Count; i++)
 65            {
 066                finalUrl += "&collectionId=" + randomizedCollections[i];
 67            }
 68
 069            return finalUrl;
 70        }
 71
 72        /// <summary>
 73        /// Populates the catalogue and internal avatar-part divided collections for optimized randomization
 74        /// </summary>
 75        /// <param name="newWearables">The list of WearableItem objects to be added to the catalog</param>
 76        private void PopulateCatalog(List<WearableItem> newWearables)
 77        {
 078            foreach (var wearableItem in newWearables)
 79            {
 080                switch (wearableItem.data.category)
 81                {
 82                    case WearableLiterals.Categories.EYES:
 083                        eyesWearableIds.Add(wearableItem.id);
 084                        break;
 85                    case WearableLiterals.Categories.EYEBROWS:
 086                        eyebrowsWearableIds.Add(wearableItem.id);
 087                        break;
 88                    case WearableLiterals.Categories.MOUTH:
 089                        mouthWearableIds.Add(wearableItem.id);
 090                        break;
 91                    case WearableLiterals.Categories.FEET:
 092                        feetWearableIds.Add(wearableItem.id);
 093                        break;
 94                    case WearableLiterals.Categories.HAIR:
 095                        hairWearableIds.Add(wearableItem.id);
 096                        break;
 97                    case WearableLiterals.Categories.FACIAL:
 098                        facialWearableIds.Add(wearableItem.id);
 099                        break;
 100                    case WearableLiterals.Categories.LOWER_BODY:
 0101                        lowerBodyWearableIds.Add(wearableItem.id);
 0102                        break;
 103                    case WearableLiterals.Categories.UPPER_BODY:
 0104                        upperBodyWearableIds.Add(wearableItem.id);
 0105                        break;
 106                    case WearableLiterals.Categories.BODY_SHAPE:
 0107                        bodyshapeWearableIds.Add(wearableItem.id);
 108                        break;
 109                }
 110            }
 111
 0112            CatalogController.i.AddWearablesToCatalog(newWearables);
 0113        }
 114
 0115        private Vector3 playerUnityPosition => CommonScriptableObjects.playerUnityPosition.Get();
 0116        private Vector3 playerWorldPosition => CommonScriptableObjects.playerWorldPosition.Get();
 117        private WorldPosInstantiationConfig lastConfigUsed;
 118        /// <summary>
 119        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 120        /// </summary>
 121        /// <param name="config">The config file to be used</param>
 122        public IEnumerator InstantiateBotsAtWorldPos(WorldPosInstantiationConfig config)
 123        {
 0124            yield return EnsureGlobalSceneAndCatalog();
 125
 0126            PatchWorldPosInstantiationConfig(config);
 127
 0128            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 129
 0130            Vector3 randomizedAreaPosition = new Vector3();
 0131            for (int i = 0; i < config.amount; i++)
 132            {
 0133                randomizedAreaPosition.Set(Random.Range(config.xPos, config.xPos + config.areaWidth), config.yPos, Rando
 0134                InstantiateBot(randomizedAreaPosition);
 135            }
 136
 0137            Log($"Finished instantiating {config.amount} avatars. They may take some time to appear while their wearable
 138
 0139            lastConfigUsed = config;
 140
 141            // TODO: Remove this and add to new entrypoint call in DebugController...
 142            // StartRandomMovement(0.5f);
 0143        }
 144
 145        private void PatchWorldPosInstantiationConfig(WorldPosInstantiationConfig config)
 146        {
 147            // TODO(Brian): Use nullable types here, this may fail.
 0148            if (config.xPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 149            {
 0150                Log($"X Position value wasn't provided... using player's current X Position.");
 0151                config.xPos = playerUnityPosition.x;
 152            }
 153
 0154            if (config.yPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 155            {
 0156                Log($"Y Position value wasn't provided... using player's current Y Position.");
 0157                config.yPos = playerUnityPosition.y;
 158            }
 159
 0160            if (config.zPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 161            {
 0162                Log($"Z Position value wasn't provided... using player's current Z Position.");
 0163                config.zPos = playerUnityPosition.z;
 164            }
 0165        }
 166
 167        /// <summary>
 168        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 169        /// </summary>
 170        /// <param name="config">The config file to be used</param>
 171        public IEnumerator InstantiateBotsAtCoords(CoordsInstantiationConfig config)
 172        {
 0173            PatchCoordsInstantiationConfig(config);
 174
 0175            var worldPosConfig = new WorldPosInstantiationConfig()
 176            {
 177                amount = config.amount,
 178                xPos = config.xCoord * ParcelSettings.PARCEL_SIZE,
 179                yPos = playerUnityPosition.y - DCLCharacterController.i.characterController.height / 2,
 180                zPos = config.yCoord * ParcelSettings.PARCEL_SIZE,
 181                areaWidth = config.areaWidth,
 182                areaDepth = config.areaDepth
 183            };
 184
 0185            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 186
 0187            yield return InstantiateBotsAtWorldPos(worldPosConfig);
 0188        }
 189
 190        private void PatchCoordsInstantiationConfig(CoordsInstantiationConfig config)
 191        {
 192            // TODO(Brian): Use nullable types here, this may fail.
 0193            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 194            {
 0195                Log($"X Coordinate value wasn't provided... using player's current scene base X coordinate.");
 0196                config.xCoord = Mathf.Floor(playerWorldPosition.x / ParcelSettings.PARCEL_SIZE);
 197            }
 198
 0199            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 200            {
 0201                Log($"Y Coordinate value wasn't provided... using player's current scene base Y coordinate.");
 0202                config.yCoord = Mathf.Floor(playerWorldPosition.z / ParcelSettings.PARCEL_SIZE);
 203            }
 0204        }
 205
 206        /// <summary>
 207        /// Instantiates an entity with an AvatarShape component, with randomized wearables, at the given position
 208        /// </summary>
 209        /// <param name="position">The world position of the randomized bot</param>
 210        void InstantiateBot(Vector3 position)
 211        {
 0212            string entityId = "BOT-" + instantiatedBots.Count;
 213
 0214            AvatarModel avatarModel = new AvatarModel()
 215            {
 216                id = entityId,
 217                name = entityId,
 218                hairColor = Random.ColorHSV(0, 1, 0, 1, 0.25f, 0.9f),
 219                eyeColor = Random.ColorHSV(0, 1, 0, 1, 0f, 0.2f),
 220                skinColor = Random.ColorHSV(0, 1, 0.3f, 1, 0.4f, 0.9f),
 221                bodyShape = Random.Range(0, 2) == 0 ? WearableLiterals.BodyShapes.FEMALE : WearableLiterals.BodyShapes.M
 222                wearables = GetRandomizedWearablesSet()
 223            };
 224
 0225            globalScene.CreateEntity(entityId);
 0226            globalScene.EntityComponentCreateOrUpdateWithModel(entityId, CLASS_ID_COMPONENT.AVATAR_SHAPE, avatarModel);
 0227            UpdateEntityTransform(globalScene, entityId, position, Quaternion.identity, Vector3.one);
 228
 0229            instantiatedBots.Add(entityId);
 0230        }
 231
 232        /// <summary>
 233        ///Removes an instantiated bot. Every bot has its ID as its avatar name.
 234        /// </summary>
 235        /// <param name="targetEntityId">The target bot ID. Every bot has its ID as its avatar name.</param>
 236        public void RemoveBot(string targetEntityId)
 237        {
 0238            if (!instantiatedBots.Contains(targetEntityId))
 0239                return;
 240
 0241            globalScene.RemoveEntity(targetEntityId);
 0242            instantiatedBots.Remove(targetEntityId);
 0243        }
 244
 245        /// <summary>
 246        /// Removes all instantiated bots.
 247        /// </summary>
 248        public void ClearBots()
 249        {
 0250            while (instantiatedBots.Count > 0)
 251            {
 0252                RemoveBot(instantiatedBots[0]);
 253            }
 0254            Log("Removed all bots.");
 0255        }
 256
 257        /// <summary>
 258        /// Randomizes a whole avatar set of wearables and returns a list with all the wearable IDs
 259        /// </summary>
 260        List<string> GetRandomizedWearablesSet()
 261        {
 0262            var wearablesSet = new List<string>();
 263
 0264            if (eyesWearableIds.Count > 0)
 0265                wearablesSet.Add(eyesWearableIds[Random.Range(0, eyesWearableIds.Count)]);
 266
 0267            if (eyebrowsWearableIds.Count > 0)
 0268                wearablesSet.Add(eyebrowsWearableIds[Random.Range(0, eyebrowsWearableIds.Count)]);
 269
 0270            if (mouthWearableIds.Count > 0)
 0271                wearablesSet.Add(mouthWearableIds[Random.Range(0, mouthWearableIds.Count)]);
 272
 0273            if (hairWearableIds.Count > 0)
 0274                wearablesSet.Add(hairWearableIds[Random.Range(0, hairWearableIds.Count)]);
 275
 0276            if (facialWearableIds.Count > 0)
 0277                wearablesSet.Add(facialWearableIds[Random.Range(0, facialWearableIds.Count)]);
 278
 0279            if (upperBodyWearableIds.Count > 0)
 0280                wearablesSet.Add(upperBodyWearableIds[Random.Range(0, upperBodyWearableIds.Count)]);
 281
 0282            if (lowerBodyWearableIds.Count > 0)
 0283                wearablesSet.Add(lowerBodyWearableIds[Random.Range(0, lowerBodyWearableIds.Count)]);
 284
 0285            if (feetWearableIds.Count > 0)
 0286                wearablesSet.Add(feetWearableIds[Random.Range(0, feetWearableIds.Count)]);
 287
 0288            if (bodyshapeWearableIds.Count > 0)
 0289                wearablesSet.Add(bodyshapeWearableIds[Random.Range(0, bodyshapeWearableIds.Count)]);
 290
 0291            return wearablesSet;
 292        }
 293
 294        /// <summary>
 295        /// Starts a coroutine that traverses a % of the instantiated population at that moment and updates their waypoi
 296        /// </summary>
 297        /// <param name="populationNormalizedPercentage">The population % that will start moving, expressed normalized e
 298        /// <param name="waypointsUpdateTime">The time wait in seconds for each waypoints update</param>
 299        public void StartRandomMovement(CoordsRandomMovementConfig config)
 300        {
 0301            if (instantiatedBots.Count == 0)
 302            {
 0303                Log($"Can't start randomized movement if there are no bots instantiated. Please first instantiate some b
 0304                return;
 305            }
 306
 0307            PatchCoordsRandomMovementConfig(config);
 308
 0309            Log($"Starting randomized movement on {(config.populationNormalizedPercentage * 100)}% of the current popula
 310
 0311            StopRandomMovement();
 312
 0313            int instantiatedCount = instantiatedBots.Count;
 0314            int botsAmount = Mathf.Min(Mathf.FloorToInt(instantiatedCount * config.populationNormalizedPercentage), inst
 315
 0316            List<int> randomBotIndices = new List<int>();
 0317            for (int i = 0; i < botsAmount; i++)
 318            {
 0319                int randomIndex = Random.Range(0, instantiatedCount);
 320
 0321                if (botsAmount == instantiatedCount)
 322                {
 0323                    randomIndex = i;
 0324                }
 325                else
 326                {
 0327                    while (randomBotIndices.Contains(randomIndex))
 328                    {
 0329                        randomIndex = Random.Range(0, instantiatedCount);
 330                    }
 331                }
 332
 0333                randomBotIndices.Add(randomIndex);
 334            }
 335
 0336            movementRoutine = CoroutineStarter.Start(RandomMovementRoutine(randomBotIndices, config));
 0337        }
 338
 339        private const float WAYPOINTS_UPDATE_DEFAULT_TIME = 5f;
 340        private void PatchCoordsRandomMovementConfig(CoordsRandomMovementConfig config)
 341        {
 0342            config.populationNormalizedPercentage = Mathf.Clamp(config.populationNormalizedPercentage, 0f, 1f);
 343
 344            // TODO(Brian): Use nullable types here, this may fail.
 0345            if (config.waypointsUpdateTime == EnvironmentSettings.UNINITIALIZED_FLOAT)
 346            {
 0347                config.waypointsUpdateTime = WAYPOINTS_UPDATE_DEFAULT_TIME;
 0348                Log($"waypointsUpdateTime value wasn't provided... using default time: {config.waypointsUpdateTime}");
 349            }
 350
 0351            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 352            {
 0353                config.xCoord = Mathf.Floor(lastConfigUsed.xPos / ParcelSettings.PARCEL_SIZE);
 0354                Log($"X Coordinate value wasn't provided... using last bots spawning X coordinate: {config.xCoord}");
 355            }
 356
 0357            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 358            {
 0359                config.yCoord = Mathf.Floor(lastConfigUsed.zPos / ParcelSettings.PARCEL_SIZE);
 0360                Log($"Y Coordinate value wasn't provided... using last bots spawning Y coordinate: {config.yCoord}");
 361            }
 362
 0363            if (config.areaWidth == 0)
 364            {
 0365                config.areaWidth = lastConfigUsed.areaWidth;
 0366                Log($"Area width provided is 0... using last bots spawning area config width: {config.areaWidth}");
 367            }
 368
 0369            if (config.areaDepth == 0)
 370            {
 0371                config.areaWidth = lastConfigUsed.areaDepth;
 0372                Log($"Area depth provided is 0... using last bots spawning area config depth: {config.areaWidth}");
 373            }
 0374        }
 375
 376        public void StopRandomMovement()
 377        {
 0378            if (movementRoutine == null)
 0379                return;
 380
 0381            CoroutineStarter.Stop(movementRoutine);
 0382            Log("Stopped bots movement.");
 0383        }
 384
 385        private float lastMovementUpdateTime;
 386        IEnumerator RandomMovementRoutine(List<int> targetBots, CoordsRandomMovementConfig config)
 387        {
 0388            lastMovementUpdateTime = Time.timeSinceLevelLoad;
 0389            while (true)
 390            {
 0391                float currentTime = Time.timeSinceLevelLoad;
 0392                if (currentTime - lastMovementUpdateTime >= config.waypointsUpdateTime)
 393                {
 0394                    lastMovementUpdateTime = currentTime;
 0395                    foreach (int targetBotIndex in targetBots)
 396                    {
 397                        // Thanks to the avatars movement interpolation, we can just update their entity position to the
 398
 0399                        Vector3 position = new Vector3(config.xCoord * ParcelSettings.PARCEL_SIZE, lastConfigUsed.yPos, 
 0400                        Vector3 randomizedAreaPosition = new Vector3(Random.Range(position.x, position.x + config.areaWi
 401                            position.y, Random.Range(position.z, position.z + config.areaDepth));
 402
 403
 0404                        UpdateEntityTransform(globalScene, instantiatedBots[targetBotIndex], randomizedAreaPosition, Qua
 405                    }
 406                }
 407
 0408                yield return null;
 409            }
 410        }
 411
 412        void UpdateEntityTransform(ParcelScene scene, string entityId, Vector3 position, Quaternion rotation, Vector3 sc
 413        {
 0414            PB_Transform pB_Transform = GetPBTransform(position, rotation, scale);
 0415            scene.EntityComponentCreateOrUpdate(
 416                entityId,
 417                CLASS_ID_COMPONENT.TRANSFORM,
 418                System.Convert.ToBase64String(pB_Transform.ToByteArray())
 419            );
 0420        }
 421
 422        public PB_Transform GetPBTransform(Vector3 position, Quaternion rotation, Vector3 scale)
 423        {
 0424            PB_Transform pbTranf = new PB_Transform();
 0425            pbTranf.Position = new PB_Vector3();
 0426            pbTranf.Position.X = position.x;
 0427            pbTranf.Position.Y = position.y;
 0428            pbTranf.Position.Z = position.z;
 0429            pbTranf.Rotation = new PB_Quaternion();
 0430            pbTranf.Rotation.X = rotation.x;
 0431            pbTranf.Rotation.Y = rotation.y;
 0432            pbTranf.Rotation.Z = rotation.z;
 0433            pbTranf.Rotation.W = rotation.w;
 0434            pbTranf.Scale = new PB_Vector3();
 0435            pbTranf.Scale.X = scale.x;
 0436            pbTranf.Scale.Y = scale.y;
 0437            pbTranf.Scale.Z = scale.z;
 0438            return pbTranf;
 439        }
 440
 441        /// <summary>
 442        /// Logs the tool messages in console regardless of the "Debug.unityLogger.logEnabled" value.
 443        /// </summary>
 444        private void Log(string message)
 445        {
 0446            bool originalLogEnabled = Debug.unityLogger.logEnabled;
 0447            Debug.unityLogger.logEnabled = true;
 448
 0449            Debug.Log("BotsController - " + message);
 450
 0451            Debug.unityLogger.logEnabled = originalLogEnabled;
 0452        }
 453    }
 454}