< Summary

Class:DCL.Bots.BotsController
Assembly:BotsController
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/BotsController/BotsController.cs
Covered lines:0
Uncovered lines:180
Coverable lines:180
Total lines:447
Line coverage:0% (0 of 180)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:20
Method coverage:0% (0 of 20)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
BotsController(...)0%2100%
EnsureGlobalSceneAndCatalog()0%56700%
BuildCollectionsURL()0%12300%
PopulateCatalog(...)0%5522300%
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%
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.Components;
 4using System.Linq;
 5using DCL.Controllers;
 6using DCL.Models;
 7using DCL.Helpers;
 8using UnityEngine;
 9using DCL.Configuration;
 10using DCLServices.WearablesCatalogService;
 11using Random = UnityEngine.Random;
 12
 13namespace DCL.Bots
 14{
 15    /// <summary>
 16    /// Bots Tool: BotsController
 17    ///
 18    /// Used to spawn bots/avatarShapes for debugging and profiling purposes.
 19    /// </summary>
 20    public class BotsController : IBotsController
 21    {
 22        private IParcelScene globalScene;
 023        private List<string> selectedCollections = new List<string>();
 024        private List<long> instantiatedBots = new List<long>();
 025        private List<string> eyesWearableIds = new List<string>();
 026        private List<string> eyebrowsWearableIds = new List<string>();
 027        private List<string> mouthWearableIds = new List<string>();
 028        private List<string> hairWearableIds = new List<string>();
 029        private List<string> facialWearableIds = new List<string>();
 030        private List<string> upperBodyWearableIds = new List<string>();
 031        private List<string> lowerBodyWearableIds = new List<string>();
 032        private List<string> feetWearableIds = new List<string>();
 033        private List<string> bodyshapeWearableIds = new List<string>();
 34
 35        private Coroutine movementRoutine = null;
 36        private IWearablesCatalogService wearablesCatalogService;
 37
 038        public BotsController(IWearablesCatalogService wearablesCatalogService)
 39        {
 040            this.wearablesCatalogService = wearablesCatalogService;
 041        }
 42
 43        /// <summary>
 44        /// Makes sure the Catalogue with all the wearables has already been loaded, otherwise it loads it
 45        /// </summary>
 46        private IEnumerator EnsureGlobalSceneAndCatalog(bool randomCollections = false)
 47        {
 048            if (globalScene != null)
 049                yield break;
 50
 051            globalScene = Environment.i.world.state.GetGlobalScenes().First();
 52
 053            wearablesCatalogService.WearablesCatalog.Clear();
 54
 55            // We stopped using random collections by default because the wearables API changes frequently and is very i
 056            if(randomCollections)
 057                yield return WearablesFetchingHelper.GetRandomCollections(50, selectedCollections);
 58
 59            // We add the base wearables collection to make sure we have at least 1 of each avatar body-part
 060            yield return WearablesFetchingHelper.GetBaseCollections(selectedCollections);
 61
 062            List<WearableItem> wearableItems = new List<WearableItem>();
 063            yield return WearablesFetchingHelper.GetWearableItems(BuildCollectionsURL(), wearableItems);
 64
 065            PopulateCatalog(wearableItems);
 066        }
 67
 68        string BuildCollectionsURL()
 69        {
 070            if (selectedCollections.Count == 0)
 071                return null;
 72
 073            string finalUrl = WearablesFetchingHelper.GetWearablesFetchURL();
 074            finalUrl += "collectionId=" + selectedCollections[0];
 075            for (int i = 1; i < selectedCollections.Count; i++)
 76            {
 077                finalUrl += "&collectionId=" + selectedCollections[i];
 78            }
 79
 080            return finalUrl;
 81        }
 82
 83        /// <summary>
 84        /// Populates the catalogue and internal avatar-part divided collections for optimized randomization
 85        /// </summary>
 86        /// <param name="newWearables">The list of WearableItem objects to be added to the catalog</param>
 87        private void PopulateCatalog(List<WearableItem> newWearables)
 88        {
 089            foreach (var wearableItem in newWearables)
 90            {
 091                switch (wearableItem.data.category)
 92                {
 93                    case WearableLiterals.Categories.EYES:
 094                        eyesWearableIds.Add(wearableItem.id);
 095                        break;
 96                    case WearableLiterals.Categories.EYEBROWS:
 097                        eyebrowsWearableIds.Add(wearableItem.id);
 098                        break;
 99                    case WearableLiterals.Categories.MOUTH:
 0100                        mouthWearableIds.Add(wearableItem.id);
 0101                        break;
 102                    case WearableLiterals.Categories.FEET:
 0103                        feetWearableIds.Add(wearableItem.id);
 0104                        break;
 105                    case WearableLiterals.Categories.HAIR:
 0106                        hairWearableIds.Add(wearableItem.id);
 0107                        break;
 108                    case WearableLiterals.Categories.FACIAL:
 0109                        facialWearableIds.Add(wearableItem.id);
 0110                        break;
 111                    case WearableLiterals.Categories.LOWER_BODY:
 0112                        lowerBodyWearableIds.Add(wearableItem.id);
 0113                        break;
 114                    case WearableLiterals.Categories.UPPER_BODY:
 0115                        upperBodyWearableIds.Add(wearableItem.id);
 0116                        break;
 117                    case WearableLiterals.Categories.BODY_SHAPE:
 0118                        bodyshapeWearableIds.Add(wearableItem.id);
 119                        break;
 120                }
 121            }
 122
 0123            wearablesCatalogService.AddWearablesToCatalog(newWearables);
 0124        }
 125
 0126        private Vector3 playerUnityPosition => CommonScriptableObjects.playerUnityPosition.Get();
 0127        private Vector3 playerWorldPosition => DataStore.i.player.playerWorldPosition.Get();
 128        private WorldPosInstantiationConfig lastConfigUsed;
 129        /// <summary>
 130        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 131        /// </summary>
 132        /// <param name="config">The config file to be used</param>
 133        public IEnumerator InstantiateBotsAtWorldPos(WorldPosInstantiationConfig config)
 134        {
 0135            yield return EnsureGlobalSceneAndCatalog();
 136
 0137            PatchWorldPosInstantiationConfig(config);
 138
 0139            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 140
 0141            Vector3 randomizedAreaPosition = new Vector3();
 0142            for (int i = 0; i < config.amount; i++)
 143            {
 0144                randomizedAreaPosition.Set(Random.Range(config.xPos, config.xPos + config.areaWidth), config.yPos, Rando
 0145                InstantiateBot(randomizedAreaPosition);
 146            }
 147
 0148            Log($"Finished instantiating {config.amount} avatars. They may take some time to appear while their wearable
 149
 0150            lastConfigUsed = config;
 151
 152            // TODO: Remove this and add to new entrypoint call in DebugController...
 153            // StartRandomMovement(0.5f);
 0154        }
 155
 156        private void PatchWorldPosInstantiationConfig(WorldPosInstantiationConfig config)
 157        {
 158            // TODO(Brian): Use nullable types here, this may fail.
 0159            if (config.xPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 160            {
 0161                Log($"X Position value wasn't provided... using player's current unity X Position: {playerUnityPosition.
 0162                config.xPos = playerUnityPosition.x;
 163            }
 164
 0165            if (config.yPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 166            {
 0167                Log($"Y Position value wasn't provided... using player's current unity Y Position: {playerUnityPosition.
 0168                config.yPos = playerUnityPosition.y;
 169            }
 170
 0171            if (config.zPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 172            {
 0173                Log($"Z Position value wasn't provided... using player's current unity Z Position: {playerUnityPosition.
 0174                config.zPos = playerUnityPosition.z;
 175            }
 0176        }
 177
 178        /// <summary>
 179        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 180        /// </summary>
 181        /// <param name="config">The config file to be used</param>
 182        public IEnumerator InstantiateBotsAtCoords(CoordsInstantiationConfig config)
 183        {
 0184            PatchCoordsInstantiationConfig(config);
 185
 0186            var worldPosConfig = new WorldPosInstantiationConfig()
 187            {
 188                amount = config.amount,
 189                xPos = config.xCoord * ParcelSettings.PARCEL_SIZE,
 190                yPos = playerUnityPosition.y - CHARACTER_HEIGHT / 2,
 191                zPos = config.yCoord * ParcelSettings.PARCEL_SIZE,
 192                areaWidth = config.areaWidth,
 193                areaDepth = config.areaDepth
 194            };
 195
 0196            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 197
 0198            yield return InstantiateBotsAtWorldPos(worldPosConfig);
 0199        }
 200
 201        private void PatchCoordsInstantiationConfig(CoordsInstantiationConfig config)
 202        {
 203            // TODO(Brian): Use nullable types here, this may fail.
 0204            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 205            {
 0206                config.xCoord = Mathf.Floor(playerWorldPosition.x / ParcelSettings.PARCEL_SIZE);
 0207                Log($"X Coordinate value wasn't provided... using player's current scene base X coordinate: {config.xCoo
 208            }
 209
 0210            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 211            {
 0212                config.yCoord = Mathf.Floor(playerWorldPosition.z / ParcelSettings.PARCEL_SIZE);
 0213                Log($"Y Coordinate value wasn't provided... using player's current scene base Y coordinate: {config.yCoo
 214            }
 0215        }
 216
 217        /// <summary>
 218        /// Instantiates an entity with an AvatarShape component, with randomized wearables, at the given position
 219        /// </summary>
 220        /// <param name="position">The world position of the randomized bot</param>
 221        void InstantiateBot(Vector3 position)
 222        {
 0223            long entityId = instantiatedBots.Count;
 224
 0225            AvatarModel avatarModel = new AvatarModel()
 226            {
 227                id = entityId.ToString(),
 228                name = entityId.ToString(),
 229                hairColor = Random.ColorHSV(0, 1, 0, 1, 0.25f, 0.9f),
 230                eyeColor = Random.ColorHSV(0, 1, 0, 1, 0f, 0.2f),
 231                skinColor = Random.ColorHSV(0, 1, 0.3f, 1, 0.4f, 0.9f),
 232                bodyShape = Random.Range(0, 2) == 0 ? WearableLiterals.BodyShapes.FEMALE : WearableLiterals.BodyShapes.M
 233                wearables = GetRandomizedWearablesSet()
 234            };
 235
 0236            var entity = globalScene.CreateEntity(entityId);
 0237            UpdateEntityTransform(globalScene, entityId, position, Quaternion.identity, Vector3.one);
 238
 0239            globalScene.componentsManagerLegacy.EntityComponentCreateOrUpdate(entityId, CLASS_ID_COMPONENT.AVATAR_SHAPE,
 240
 0241            instantiatedBots.Add(entityId);
 0242        }
 243
 244        /// <summary>
 245        ///Removes an instantiated bot. Every bot has its ID as its avatar name.
 246        /// </summary>
 247        /// <param name="targetEntityId">The target bot ID. Every bot has its ID as its avatar name.</param>
 248        public void RemoveBot(long targetEntityId)
 249        {
 0250            if (!instantiatedBots.Contains(targetEntityId))
 0251                return;
 252
 0253            globalScene.RemoveEntity(targetEntityId);
 0254            instantiatedBots.Remove(targetEntityId);
 0255        }
 256
 257        /// <summary>
 258        /// Removes all instantiated bots.
 259        /// </summary>
 260        public void ClearBots()
 261        {
 0262            while (instantiatedBots.Count > 0)
 263            {
 0264                RemoveBot(instantiatedBots[0]);
 265            }
 0266            Log("Removed all bots.");
 0267        }
 268
 269        /// <summary>
 270        /// Randomizes a whole avatar set of wearables and returns a list with all the wearable IDs
 271        /// </summary>
 272        List<string> GetRandomizedWearablesSet()
 273        {
 0274            var wearablesSet = new List<string>();
 275
 0276            if (eyesWearableIds.Count > 0)
 0277                wearablesSet.Add(eyesWearableIds[Random.Range(0, eyesWearableIds.Count)]);
 278
 0279            if (eyebrowsWearableIds.Count > 0)
 0280                wearablesSet.Add(eyebrowsWearableIds[Random.Range(0, eyebrowsWearableIds.Count)]);
 281
 0282            if (mouthWearableIds.Count > 0)
 0283                wearablesSet.Add(mouthWearableIds[Random.Range(0, mouthWearableIds.Count)]);
 284
 0285            if (hairWearableIds.Count > 0)
 0286                wearablesSet.Add(hairWearableIds[Random.Range(0, hairWearableIds.Count)]);
 287
 0288            if (facialWearableIds.Count > 0)
 0289                wearablesSet.Add(facialWearableIds[Random.Range(0, facialWearableIds.Count)]);
 290
 0291            if (upperBodyWearableIds.Count > 0)
 0292                wearablesSet.Add(upperBodyWearableIds[Random.Range(0, upperBodyWearableIds.Count)]);
 293
 0294            if (lowerBodyWearableIds.Count > 0)
 0295                wearablesSet.Add(lowerBodyWearableIds[Random.Range(0, lowerBodyWearableIds.Count)]);
 296
 0297            if (feetWearableIds.Count > 0)
 0298                wearablesSet.Add(feetWearableIds[Random.Range(0, feetWearableIds.Count)]);
 299
 0300            if (bodyshapeWearableIds.Count > 0)
 0301                wearablesSet.Add(bodyshapeWearableIds[Random.Range(0, bodyshapeWearableIds.Count)]);
 302
 0303            return wearablesSet;
 304        }
 305
 306        /// <summary>
 307        /// Starts a coroutine that traverses a % of the instantiated population at that moment and updates their waypoi
 308        /// </summary>
 309        /// <param name="populationNormalizedPercentage">The population % that will start moving, expressed normalized e
 310        /// <param name="waypointsUpdateTime">The time wait in seconds for each waypoints update</param>
 311        public void StartRandomMovement(CoordsRandomMovementConfig config)
 312        {
 0313            if (instantiatedBots.Count == 0)
 314            {
 0315                Log($"Can't start randomized movement if there are no bots instantiated. Please first instantiate some b
 0316                return;
 317            }
 318
 0319            PatchCoordsRandomMovementConfig(config);
 320
 0321            Log($"Starting randomized movement on {(config.populationNormalizedPercentage * 100)}% of the current popula
 322
 0323            StopRandomMovement();
 324
 0325            int instantiatedCount = instantiatedBots.Count;
 0326            int botsAmount = Mathf.Min(Mathf.FloorToInt(instantiatedCount * config.populationNormalizedPercentage), inst
 327
 0328            List<int> randomBotIndices = new List<int>();
 0329            for (int i = 0; i < botsAmount; i++)
 330            {
 0331                int randomIndex = Random.Range(0, instantiatedCount);
 332
 0333                if (botsAmount == instantiatedCount)
 334                {
 0335                    randomIndex = i;
 336                }
 337                else
 338                {
 0339                    while (randomBotIndices.Contains(randomIndex))
 340                    {
 0341                        randomIndex = Random.Range(0, instantiatedCount);
 342                    }
 343                }
 344
 0345                randomBotIndices.Add(randomIndex);
 346            }
 347
 0348            movementRoutine = CoroutineStarter.Start(RandomMovementRoutine(randomBotIndices, config));
 0349        }
 350
 351        private const float WAYPOINTS_UPDATE_DEFAULT_TIME = 5f;
 352        private const float CHARACTER_HEIGHT = 1.6f;
 353        private void PatchCoordsRandomMovementConfig(CoordsRandomMovementConfig config)
 354        {
 0355            config.populationNormalizedPercentage = Mathf.Clamp(config.populationNormalizedPercentage, 0f, 1f);
 356
 357            // TODO(Brian): Use nullable types here, this may fail.
 0358            if (config.waypointsUpdateTime == EnvironmentSettings.UNINITIALIZED_FLOAT)
 359            {
 0360                config.waypointsUpdateTime = WAYPOINTS_UPDATE_DEFAULT_TIME;
 0361                Log($"waypointsUpdateTime value wasn't provided... using default time: {config.waypointsUpdateTime}");
 362            }
 363
 0364            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 365            {
 0366                config.xCoord = Mathf.Floor(lastConfigUsed.xPos / ParcelSettings.PARCEL_SIZE);
 0367                Log($"X Coordinate value wasn't provided... using last bots spawning X coordinate: {config.xCoord}");
 368            }
 369
 0370            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 371            {
 0372                config.yCoord = Mathf.Floor(lastConfigUsed.zPos / ParcelSettings.PARCEL_SIZE);
 0373                Log($"Y Coordinate value wasn't provided... using last bots spawning Y coordinate: {config.yCoord}");
 374            }
 375
 0376            if (config.areaWidth == 0)
 377            {
 0378                config.areaWidth = lastConfigUsed.areaWidth;
 0379                Log($"Area width provided is 0... using last bots spawning area config width: {config.areaWidth}");
 380            }
 381
 0382            if (config.areaDepth == 0)
 383            {
 0384                config.areaWidth = lastConfigUsed.areaDepth;
 0385                Log($"Area depth provided is 0... using last bots spawning area config depth: {config.areaWidth}");
 386            }
 0387        }
 388
 389        public void StopRandomMovement()
 390        {
 0391            if (movementRoutine == null)
 0392                return;
 393
 0394            CoroutineStarter.Stop(movementRoutine);
 0395            Log("Stopped bots movement.");
 0396        }
 397
 398        private float lastMovementUpdateTime;
 399        IEnumerator RandomMovementRoutine(List<int> targetBots, CoordsRandomMovementConfig config)
 400        {
 0401            lastMovementUpdateTime = Time.timeSinceLevelLoad;
 0402            while (true)
 403            {
 0404                float currentTime = Time.timeSinceLevelLoad;
 0405                if (currentTime - lastMovementUpdateTime >= config.waypointsUpdateTime)
 406                {
 0407                    lastMovementUpdateTime = currentTime;
 0408                    foreach (int targetBotIndex in targetBots)
 409                    {
 410                        // Thanks to the avatars movement interpolation, we can just update their entity position to the
 411
 0412                        Vector3 position = new Vector3(config.xCoord * ParcelSettings.PARCEL_SIZE, lastConfigUsed.yPos, 
 0413                        Vector3 randomizedAreaPosition = new Vector3(Random.Range(position.x, position.x + config.areaWi
 414                            position.y, Random.Range(position.z, position.z + config.areaDepth));
 415
 416
 0417                        UpdateEntityTransform(globalScene, instantiatedBots[targetBotIndex], randomizedAreaPosition, Qua
 418                    }
 419                }
 420
 0421                yield return null;
 422            }
 423        }
 424
 425        void UpdateEntityTransform(IParcelScene scene, long entityId, Vector3 position, Quaternion rotation, Vector3 sca
 426        {
 0427            scene.componentsManagerLegacy.EntityComponentCreateOrUpdate(
 428                entityId,
 429                CLASS_ID_COMPONENT.TRANSFORM,
 430                DCLTransformUtils.EncodeTransform(position, rotation, scale)
 431            );
 0432        }
 433
 434        /// <summary>
 435        /// Logs the tool messages in console regardless of the "Debug.unityLogger.logEnabled" value.
 436        /// </summary>
 437        private void Log(string message)
 438        {
 0439            bool originalLogEnabled = Debug.unityLogger.logEnabled;
 0440            Debug.unityLogger.logEnabled = true;
 441
 0442            Debug.Log("BotsController - " + message);
 443
 0444            Debug.unityLogger.logEnabled = originalLogEnabled;
 0445        }
 446    }
 447}