< 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:177
Coverable lines:177
Total lines:442
Line coverage:0% (0 of 177)
Covered branches:0
Total branches:0

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.Interface;
 7using DCL.Models;
 8using DCL.Helpers;
 9using Google.Protobuf;
 10using UnityEngine;
 11using DCL.Configuration;
 12using Random = UnityEngine.Random;
 13
 14namespace DCL.Bots
 15{
 16    /// <summary>
 17    /// Bots Tool: BotsController
 18    ///
 19    /// Used to spawn bots/avatarShapes for debugging and profiling purposes.
 20    /// </summary>
 21    public class BotsController : IBotsController
 22    {
 23        private IParcelScene globalScene;
 024        private List<string> selectedCollections = new List<string>();
 025        private List<long> instantiatedBots = new List<long>();
 026        private List<string> eyesWearableIds = new List<string>();
 027        private List<string> eyebrowsWearableIds = new List<string>();
 028        private List<string> mouthWearableIds = new List<string>();
 029        private List<string> hairWearableIds = new List<string>();
 030        private List<string> facialWearableIds = new List<string>();
 031        private List<string> upperBodyWearableIds = new List<string>();
 032        private List<string> lowerBodyWearableIds = new List<string>();
 033        private List<string> feetWearableIds = new List<string>();
 034        private List<string> bodyshapeWearableIds = new List<string>();
 35
 36        private Coroutine movementRoutine = null;
 37
 38        /// <summary>
 39        /// Makes sure the Catalogue with all the wearables has already been loaded, otherwise it loads it
 40        /// </summary>
 41        private IEnumerator EnsureGlobalSceneAndCatalog(bool randomCollections = false)
 42        {
 043            if (globalScene != null)
 044                yield break;
 45
 046            globalScene = Environment.i.world.state.GetGlobalScenes().First();
 47
 048            CatalogController.wearableCatalog.Clear();
 49
 50            // We stopped using random collections by default because the wearables API changes frequently and is very i
 051            if(randomCollections)
 052                yield return WearablesFetchingHelper.GetRandomCollections(50, selectedCollections);
 53
 54            // We add the base wearables collection to make sure we have at least 1 of each avatar body-part
 055            yield return WearablesFetchingHelper.GetBaseCollections(selectedCollections);
 56
 057            List<WearableItem> wearableItems = new List<WearableItem>();
 058            yield return WearablesFetchingHelper.GetWearableItems(BuildCollectionsURL(), wearableItems);
 59
 060            PopulateCatalog(wearableItems);
 061        }
 62
 63        string BuildCollectionsURL()
 64        {
 065            if (selectedCollections.Count == 0)
 066                return null;
 67
 068            string finalUrl = WearablesFetchingHelper.GetWearablesFetchURL();
 069            finalUrl += "collectionId=" + selectedCollections[0];
 070            for (int i = 1; i < selectedCollections.Count; i++)
 71            {
 072                finalUrl += "&collectionId=" + selectedCollections[i];
 73            }
 74
 075            return finalUrl;
 76        }
 77
 78        /// <summary>
 79        /// Populates the catalogue and internal avatar-part divided collections for optimized randomization
 80        /// </summary>
 81        /// <param name="newWearables">The list of WearableItem objects to be added to the catalog</param>
 82        private void PopulateCatalog(List<WearableItem> newWearables)
 83        {
 084            foreach (var wearableItem in newWearables)
 85            {
 086                switch (wearableItem.data.category)
 87                {
 88                    case WearableLiterals.Categories.EYES:
 089                        eyesWearableIds.Add(wearableItem.id);
 090                        break;
 91                    case WearableLiterals.Categories.EYEBROWS:
 092                        eyebrowsWearableIds.Add(wearableItem.id);
 093                        break;
 94                    case WearableLiterals.Categories.MOUTH:
 095                        mouthWearableIds.Add(wearableItem.id);
 096                        break;
 97                    case WearableLiterals.Categories.FEET:
 098                        feetWearableIds.Add(wearableItem.id);
 099                        break;
 100                    case WearableLiterals.Categories.HAIR:
 0101                        hairWearableIds.Add(wearableItem.id);
 0102                        break;
 103                    case WearableLiterals.Categories.FACIAL:
 0104                        facialWearableIds.Add(wearableItem.id);
 0105                        break;
 106                    case WearableLiterals.Categories.LOWER_BODY:
 0107                        lowerBodyWearableIds.Add(wearableItem.id);
 0108                        break;
 109                    case WearableLiterals.Categories.UPPER_BODY:
 0110                        upperBodyWearableIds.Add(wearableItem.id);
 0111                        break;
 112                    case WearableLiterals.Categories.BODY_SHAPE:
 0113                        bodyshapeWearableIds.Add(wearableItem.id);
 114                        break;
 115                }
 116            }
 117
 0118            CatalogController.i.AddWearablesToCatalog(newWearables);
 0119        }
 120
 0121        private Vector3 playerUnityPosition => CommonScriptableObjects.playerUnityPosition.Get();
 0122        private Vector3 playerWorldPosition => DataStore.i.player.playerWorldPosition.Get();
 123        private WorldPosInstantiationConfig lastConfigUsed;
 124        /// <summary>
 125        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 126        /// </summary>
 127        /// <param name="config">The config file to be used</param>
 128        public IEnumerator InstantiateBotsAtWorldPos(WorldPosInstantiationConfig config)
 129        {
 0130            yield return EnsureGlobalSceneAndCatalog();
 131
 0132            PatchWorldPosInstantiationConfig(config);
 133
 0134            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 135
 0136            Vector3 randomizedAreaPosition = new Vector3();
 0137            for (int i = 0; i < config.amount; i++)
 138            {
 0139                randomizedAreaPosition.Set(Random.Range(config.xPos, config.xPos + config.areaWidth), config.yPos, Rando
 0140                InstantiateBot(randomizedAreaPosition);
 141            }
 142
 0143            Log($"Finished instantiating {config.amount} avatars. They may take some time to appear while their wearable
 144
 0145            lastConfigUsed = config;
 146
 147            // TODO: Remove this and add to new entrypoint call in DebugController...
 148            // StartRandomMovement(0.5f);
 0149        }
 150
 151        private void PatchWorldPosInstantiationConfig(WorldPosInstantiationConfig config)
 152        {
 153            // TODO(Brian): Use nullable types here, this may fail.
 0154            if (config.xPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 155            {
 0156                Log($"X Position value wasn't provided... using player's current unity X Position: {playerUnityPosition.
 0157                config.xPos = playerUnityPosition.x;
 158            }
 159
 0160            if (config.yPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 161            {
 0162                Log($"Y Position value wasn't provided... using player's current unity Y Position: {playerUnityPosition.
 0163                config.yPos = playerUnityPosition.y;
 164            }
 165
 0166            if (config.zPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 167            {
 0168                Log($"Z Position value wasn't provided... using player's current unity Z Position: {playerUnityPosition.
 0169                config.zPos = playerUnityPosition.z;
 170            }
 0171        }
 172
 173        /// <summary>
 174        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 175        /// </summary>
 176        /// <param name="config">The config file to be used</param>
 177        public IEnumerator InstantiateBotsAtCoords(CoordsInstantiationConfig config)
 178        {
 0179            PatchCoordsInstantiationConfig(config);
 180
 0181            var worldPosConfig = new WorldPosInstantiationConfig()
 182            {
 183                amount = config.amount,
 184                xPos = config.xCoord * ParcelSettings.PARCEL_SIZE,
 185                yPos = playerUnityPosition.y - CHARACTER_HEIGHT / 2,
 186                zPos = config.yCoord * ParcelSettings.PARCEL_SIZE,
 187                areaWidth = config.areaWidth,
 188                areaDepth = config.areaDepth
 189            };
 190
 0191            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 192
 0193            yield return InstantiateBotsAtWorldPos(worldPosConfig);
 0194        }
 195
 196        private void PatchCoordsInstantiationConfig(CoordsInstantiationConfig config)
 197        {
 198            // TODO(Brian): Use nullable types here, this may fail.
 0199            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 200            {
 0201                config.xCoord = Mathf.Floor(playerWorldPosition.x / ParcelSettings.PARCEL_SIZE);
 0202                Log($"X Coordinate value wasn't provided... using player's current scene base X coordinate: {config.xCoo
 203            }
 204
 0205            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 206            {
 0207                config.yCoord = Mathf.Floor(playerWorldPosition.z / ParcelSettings.PARCEL_SIZE);
 0208                Log($"Y Coordinate value wasn't provided... using player's current scene base Y coordinate: {config.yCoo
 209            }
 0210        }
 211
 212        /// <summary>
 213        /// Instantiates an entity with an AvatarShape component, with randomized wearables, at the given position
 214        /// </summary>
 215        /// <param name="position">The world position of the randomized bot</param>
 216        void InstantiateBot(Vector3 position)
 217        {
 0218            long entityId = instantiatedBots.Count;
 219
 0220            AvatarModel avatarModel = new AvatarModel()
 221            {
 222                id = entityId.ToString(),
 223                name = entityId.ToString(),
 224                hairColor = Random.ColorHSV(0, 1, 0, 1, 0.25f, 0.9f),
 225                eyeColor = Random.ColorHSV(0, 1, 0, 1, 0f, 0.2f),
 226                skinColor = Random.ColorHSV(0, 1, 0.3f, 1, 0.4f, 0.9f),
 227                bodyShape = Random.Range(0, 2) == 0 ? WearableLiterals.BodyShapes.FEMALE : WearableLiterals.BodyShapes.M
 228                wearables = GetRandomizedWearablesSet()
 229            };
 230
 0231            var entity = globalScene.CreateEntity(entityId);
 0232            UpdateEntityTransform(globalScene, entityId, position, Quaternion.identity, Vector3.one);
 233
 0234            globalScene.componentsManagerLegacy.EntityComponentCreateOrUpdate(entityId, CLASS_ID_COMPONENT.AVATAR_SHAPE,
 235
 0236            instantiatedBots.Add(entityId);
 0237        }
 238
 239        /// <summary>
 240        ///Removes an instantiated bot. Every bot has its ID as its avatar name.
 241        /// </summary>
 242        /// <param name="targetEntityId">The target bot ID. Every bot has its ID as its avatar name.</param>
 243        public void RemoveBot(long targetEntityId)
 244        {
 0245            if (!instantiatedBots.Contains(targetEntityId))
 0246                return;
 247
 0248            globalScene.RemoveEntity(targetEntityId);
 0249            instantiatedBots.Remove(targetEntityId);
 0250        }
 251
 252        /// <summary>
 253        /// Removes all instantiated bots.
 254        /// </summary>
 255        public void ClearBots()
 256        {
 0257            while (instantiatedBots.Count > 0)
 258            {
 0259                RemoveBot(instantiatedBots[0]);
 260            }
 0261            Log("Removed all bots.");
 0262        }
 263
 264        /// <summary>
 265        /// Randomizes a whole avatar set of wearables and returns a list with all the wearable IDs
 266        /// </summary>
 267        List<string> GetRandomizedWearablesSet()
 268        {
 0269            var wearablesSet = new List<string>();
 270
 0271            if (eyesWearableIds.Count > 0)
 0272                wearablesSet.Add(eyesWearableIds[Random.Range(0, eyesWearableIds.Count)]);
 273
 0274            if (eyebrowsWearableIds.Count > 0)
 0275                wearablesSet.Add(eyebrowsWearableIds[Random.Range(0, eyebrowsWearableIds.Count)]);
 276
 0277            if (mouthWearableIds.Count > 0)
 0278                wearablesSet.Add(mouthWearableIds[Random.Range(0, mouthWearableIds.Count)]);
 279
 0280            if (hairWearableIds.Count > 0)
 0281                wearablesSet.Add(hairWearableIds[Random.Range(0, hairWearableIds.Count)]);
 282
 0283            if (facialWearableIds.Count > 0)
 0284                wearablesSet.Add(facialWearableIds[Random.Range(0, facialWearableIds.Count)]);
 285
 0286            if (upperBodyWearableIds.Count > 0)
 0287                wearablesSet.Add(upperBodyWearableIds[Random.Range(0, upperBodyWearableIds.Count)]);
 288
 0289            if (lowerBodyWearableIds.Count > 0)
 0290                wearablesSet.Add(lowerBodyWearableIds[Random.Range(0, lowerBodyWearableIds.Count)]);
 291
 0292            if (feetWearableIds.Count > 0)
 0293                wearablesSet.Add(feetWearableIds[Random.Range(0, feetWearableIds.Count)]);
 294
 0295            if (bodyshapeWearableIds.Count > 0)
 0296                wearablesSet.Add(bodyshapeWearableIds[Random.Range(0, bodyshapeWearableIds.Count)]);
 297
 0298            return wearablesSet;
 299        }
 300
 301        /// <summary>
 302        /// Starts a coroutine that traverses a % of the instantiated population at that moment and updates their waypoi
 303        /// </summary>
 304        /// <param name="populationNormalizedPercentage">The population % that will start moving, expressed normalized e
 305        /// <param name="waypointsUpdateTime">The time wait in seconds for each waypoints update</param>
 306        public void StartRandomMovement(CoordsRandomMovementConfig config)
 307        {
 0308            if (instantiatedBots.Count == 0)
 309            {
 0310                Log($"Can't start randomized movement if there are no bots instantiated. Please first instantiate some b
 0311                return;
 312            }
 313
 0314            PatchCoordsRandomMovementConfig(config);
 315
 0316            Log($"Starting randomized movement on {(config.populationNormalizedPercentage * 100)}% of the current popula
 317
 0318            StopRandomMovement();
 319
 0320            int instantiatedCount = instantiatedBots.Count;
 0321            int botsAmount = Mathf.Min(Mathf.FloorToInt(instantiatedCount * config.populationNormalizedPercentage), inst
 322
 0323            List<int> randomBotIndices = new List<int>();
 0324            for (int i = 0; i < botsAmount; i++)
 325            {
 0326                int randomIndex = Random.Range(0, instantiatedCount);
 327
 0328                if (botsAmount == instantiatedCount)
 329                {
 0330                    randomIndex = i;
 331                }
 332                else
 333                {
 0334                    while (randomBotIndices.Contains(randomIndex))
 335                    {
 0336                        randomIndex = Random.Range(0, instantiatedCount);
 337                    }
 338                }
 339
 0340                randomBotIndices.Add(randomIndex);
 341            }
 342
 0343            movementRoutine = CoroutineStarter.Start(RandomMovementRoutine(randomBotIndices, config));
 0344        }
 345
 346        private const float WAYPOINTS_UPDATE_DEFAULT_TIME = 5f;
 347        private const float CHARACTER_HEIGHT = 1.6f;
 348        private void PatchCoordsRandomMovementConfig(CoordsRandomMovementConfig config)
 349        {
 0350            config.populationNormalizedPercentage = Mathf.Clamp(config.populationNormalizedPercentage, 0f, 1f);
 351
 352            // TODO(Brian): Use nullable types here, this may fail.
 0353            if (config.waypointsUpdateTime == EnvironmentSettings.UNINITIALIZED_FLOAT)
 354            {
 0355                config.waypointsUpdateTime = WAYPOINTS_UPDATE_DEFAULT_TIME;
 0356                Log($"waypointsUpdateTime value wasn't provided... using default time: {config.waypointsUpdateTime}");
 357            }
 358
 0359            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 360            {
 0361                config.xCoord = Mathf.Floor(lastConfigUsed.xPos / ParcelSettings.PARCEL_SIZE);
 0362                Log($"X Coordinate value wasn't provided... using last bots spawning X coordinate: {config.xCoord}");
 363            }
 364
 0365            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 366            {
 0367                config.yCoord = Mathf.Floor(lastConfigUsed.zPos / ParcelSettings.PARCEL_SIZE);
 0368                Log($"Y Coordinate value wasn't provided... using last bots spawning Y coordinate: {config.yCoord}");
 369            }
 370
 0371            if (config.areaWidth == 0)
 372            {
 0373                config.areaWidth = lastConfigUsed.areaWidth;
 0374                Log($"Area width provided is 0... using last bots spawning area config width: {config.areaWidth}");
 375            }
 376
 0377            if (config.areaDepth == 0)
 378            {
 0379                config.areaWidth = lastConfigUsed.areaDepth;
 0380                Log($"Area depth provided is 0... using last bots spawning area config depth: {config.areaWidth}");
 381            }
 0382        }
 383
 384        public void StopRandomMovement()
 385        {
 0386            if (movementRoutine == null)
 0387                return;
 388
 0389            CoroutineStarter.Stop(movementRoutine);
 0390            Log("Stopped bots movement.");
 0391        }
 392
 393        private float lastMovementUpdateTime;
 394        IEnumerator RandomMovementRoutine(List<int> targetBots, CoordsRandomMovementConfig config)
 395        {
 0396            lastMovementUpdateTime = Time.timeSinceLevelLoad;
 0397            while (true)
 398            {
 0399                float currentTime = Time.timeSinceLevelLoad;
 0400                if (currentTime - lastMovementUpdateTime >= config.waypointsUpdateTime)
 401                {
 0402                    lastMovementUpdateTime = currentTime;
 0403                    foreach (int targetBotIndex in targetBots)
 404                    {
 405                        // Thanks to the avatars movement interpolation, we can just update their entity position to the
 406
 0407                        Vector3 position = new Vector3(config.xCoord * ParcelSettings.PARCEL_SIZE, lastConfigUsed.yPos, 
 0408                        Vector3 randomizedAreaPosition = new Vector3(Random.Range(position.x, position.x + config.areaWi
 409                            position.y, Random.Range(position.z, position.z + config.areaDepth));
 410
 411
 0412                        UpdateEntityTransform(globalScene, instantiatedBots[targetBotIndex], randomizedAreaPosition, Qua
 413                    }
 414                }
 415
 0416                yield return null;
 417            }
 418        }
 419
 420        void UpdateEntityTransform(IParcelScene scene, long entityId, Vector3 position, Quaternion rotation, Vector3 sca
 421        {
 0422            scene.componentsManagerLegacy.EntityComponentCreateOrUpdate(
 423                entityId,
 424                CLASS_ID_COMPONENT.TRANSFORM,
 425                DCLTransformUtils.EncodeTransform(position, rotation, scale)
 426            );
 0427        }
 428
 429        /// <summary>
 430        /// Logs the tool messages in console regardless of the "Debug.unityLogger.logEnabled" value.
 431        /// </summary>
 432        private void Log(string message)
 433        {
 0434            bool originalLogEnabled = Debug.unityLogger.logEnabled;
 0435            Debug.unityLogger.logEnabled = true;
 436
 0437            Debug.Log("BotsController - " + message);
 438
 0439            Debug.unityLogger.logEnabled = originalLogEnabled;
 0440        }
 441    }
 442}