< 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:11
Uncovered lines:181
Coverable lines:192
Total lines:456
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 MainScripts.DCL.WorldRuntime;
 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 ISceneMessageProcessor globalScene;
 12523        private List<string> randomizedCollections = new List<string>();
 12524        private List<string> instantiatedBots = new List<string>();
 12525        private List<string> eyesWearableIds = new List<string>();
 12526        private List<string> eyebrowsWearableIds = new List<string>();
 12527        private List<string> mouthWearableIds = new List<string>();
 12528        private List<string> hairWearableIds = new List<string>();
 12529        private List<string> facialWearableIds = new List<string>();
 12530        private List<string> upperBodyWearableIds = new List<string>();
 12531        private List<string> lowerBodyWearableIds = new List<string>();
 12532        private List<string> feetWearableIds = new List<string>();
 12533        private List<string> bodyshapeWearableIds = new List<string>();
 34
 35        private Coroutine movementRoutine = null;
 36
 37        /// <summary>
 38        /// Makes sure the Catalogue with all the wearables has already been loaded, otherwise it loads it
 39        /// </summary>
 40        private IEnumerator EnsureGlobalSceneAndCatalog()
 41        {
 042            if (globalScene != null)
 043                yield break;
 44
 045            globalScene = Environment.i.world.state.loadedScenes[Environment.i.world.state.globalSceneIds[0]] as ISceneM
 46
 047            CatalogController.wearableCatalog.Clear();
 48
 049            yield return WearablesFetchingHelper.GetRandomCollections(20, true, randomizedCollections);
 50
 051            List<WearableItem> wearableItems = new List<WearableItem>();
 052            yield return WearablesFetchingHelper.GetWearableItems(BuildRandomizedCollectionsURL(), wearableItems);
 53
 054            PopulateCatalog(wearableItems);
 055        }
 56
 57        string BuildRandomizedCollectionsURL()
 58        {
 059            if (randomizedCollections.Count == 0)
 060                return null;
 61
 062            string finalUrl = WearablesFetchingHelper.WEARABLES_FETCH_URL;
 63
 064            finalUrl += "collectionId=" + randomizedCollections[0];
 065            for (int i = 1; i < randomizedCollections.Count; i++)
 66            {
 067                finalUrl += "&collectionId=" + randomizedCollections[i];
 68            }
 69
 070            return finalUrl;
 71        }
 72
 73        /// <summary>
 74        /// Populates the catalogue and internal avatar-part divided collections for optimized randomization
 75        /// </summary>
 76        /// <param name="newWearables">The list of WearableItem objects to be added to the catalog</param>
 77        private void PopulateCatalog(List<WearableItem> newWearables)
 78        {
 079            foreach (var wearableItem in newWearables)
 80            {
 081                switch (wearableItem.data.category)
 82                {
 83                    case WearableLiterals.Categories.EYES:
 084                        eyesWearableIds.Add(wearableItem.id);
 085                        break;
 86                    case WearableLiterals.Categories.EYEBROWS:
 087                        eyebrowsWearableIds.Add(wearableItem.id);
 088                        break;
 89                    case WearableLiterals.Categories.MOUTH:
 090                        mouthWearableIds.Add(wearableItem.id);
 091                        break;
 92                    case WearableLiterals.Categories.FEET:
 093                        feetWearableIds.Add(wearableItem.id);
 094                        break;
 95                    case WearableLiterals.Categories.HAIR:
 096                        hairWearableIds.Add(wearableItem.id);
 097                        break;
 98                    case WearableLiterals.Categories.FACIAL:
 099                        facialWearableIds.Add(wearableItem.id);
 0100                        break;
 101                    case WearableLiterals.Categories.LOWER_BODY:
 0102                        lowerBodyWearableIds.Add(wearableItem.id);
 0103                        break;
 104                    case WearableLiterals.Categories.UPPER_BODY:
 0105                        upperBodyWearableIds.Add(wearableItem.id);
 0106                        break;
 107                    case WearableLiterals.Categories.BODY_SHAPE:
 0108                        bodyshapeWearableIds.Add(wearableItem.id);
 109                        break;
 110                }
 111            }
 112
 0113            CatalogController.i.AddWearablesToCatalog(newWearables);
 0114        }
 115
 0116        private Vector3 playerUnityPosition => CommonScriptableObjects.playerUnityPosition.Get();
 0117        private Vector3 playerWorldPosition => CommonScriptableObjects.playerWorldPosition.Get();
 118        private WorldPosInstantiationConfig lastConfigUsed;
 119        /// <summary>
 120        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 121        /// </summary>
 122        /// <param name="config">The config file to be used</param>
 123        public IEnumerator InstantiateBotsAtWorldPos(WorldPosInstantiationConfig config)
 124        {
 0125            yield return EnsureGlobalSceneAndCatalog();
 126
 0127            PatchWorldPosInstantiationConfig(config);
 128
 0129            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 130
 0131            Vector3 randomizedAreaPosition = new Vector3();
 0132            for (int i = 0; i < config.amount; i++)
 133            {
 0134                randomizedAreaPosition.Set(Random.Range(config.xPos, config.xPos + config.areaWidth), config.yPos, Rando
 0135                InstantiateBot(randomizedAreaPosition);
 136            }
 137
 0138            Log($"Finished instantiating {config.amount} avatars. They may take some time to appear while their wearable
 139
 0140            lastConfigUsed = config;
 141
 142            // TODO: Remove this and add to new entrypoint call in DebugController...
 143            // StartRandomMovement(0.5f);
 0144        }
 145
 146        private void PatchWorldPosInstantiationConfig(WorldPosInstantiationConfig config)
 147        {
 148            // TODO(Brian): Use nullable types here, this may fail.
 0149            if (config.xPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 150            {
 0151                Log($"X Position value wasn't provided... using player's current X Position.");
 0152                config.xPos = playerUnityPosition.x;
 153            }
 154
 0155            if (config.yPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 156            {
 0157                Log($"Y Position value wasn't provided... using player's current Y Position.");
 0158                config.yPos = playerUnityPosition.y;
 159            }
 160
 0161            if (config.zPos == EnvironmentSettings.UNINITIALIZED_FLOAT)
 162            {
 0163                Log($"Z Position value wasn't provided... using player's current Z Position.");
 0164                config.zPos = playerUnityPosition.z;
 165            }
 0166        }
 167
 168        /// <summary>
 169        /// Instantiates bots using the config file param values. It defaults some uninitialized values using the player
 170        /// </summary>
 171        /// <param name="config">The config file to be used</param>
 172        public IEnumerator InstantiateBotsAtCoords(CoordsInstantiationConfig config)
 173        {
 0174            PatchCoordsInstantiationConfig(config);
 175
 0176            var worldPosConfig = new WorldPosInstantiationConfig()
 177            {
 178                amount = config.amount,
 179                xPos = config.xCoord * ParcelSettings.PARCEL_SIZE,
 180                yPos = playerUnityPosition.y - CHARACTER_HEIGHT / 2,
 181                zPos = config.yCoord * ParcelSettings.PARCEL_SIZE,
 182                areaWidth = config.areaWidth,
 183                areaDepth = config.areaDepth
 184            };
 185
 0186            Log($"Instantiating {config.amount} randomized avatars inside a {config.areaWidth}x{config.areaDepth} area p
 187
 0188            yield return InstantiateBotsAtWorldPos(worldPosConfig);
 0189        }
 190
 191        private void PatchCoordsInstantiationConfig(CoordsInstantiationConfig config)
 192        {
 193            // TODO(Brian): Use nullable types here, this may fail.
 0194            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 195            {
 0196                Log($"X Coordinate value wasn't provided... using player's current scene base X coordinate.");
 0197                config.xCoord = Mathf.Floor(playerWorldPosition.x / ParcelSettings.PARCEL_SIZE);
 198            }
 199
 0200            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 201            {
 0202                Log($"Y Coordinate value wasn't provided... using player's current scene base Y coordinate.");
 0203                config.yCoord = Mathf.Floor(playerWorldPosition.z / ParcelSettings.PARCEL_SIZE);
 204            }
 0205        }
 206
 207        /// <summary>
 208        /// Instantiates an entity with an AvatarShape component, with randomized wearables, at the given position
 209        /// </summary>
 210        /// <param name="position">The world position of the randomized bot</param>
 211        void InstantiateBot(Vector3 position)
 212        {
 0213            string entityId = "BOT-" + instantiatedBots.Count;
 214
 0215            AvatarModel avatarModel = new AvatarModel()
 216            {
 217                id = entityId,
 218                name = entityId,
 219                hairColor = Random.ColorHSV(0, 1, 0, 1, 0.25f, 0.9f),
 220                eyeColor = Random.ColorHSV(0, 1, 0, 1, 0f, 0.2f),
 221                skinColor = Random.ColorHSV(0, 1, 0.3f, 1, 0.4f, 0.9f),
 222                bodyShape = Random.Range(0, 2) == 0 ? WearableLiterals.BodyShapes.FEMALE : WearableLiterals.BodyShapes.M
 223                wearables = GetRandomizedWearablesSet()
 224            };
 225
 0226            globalScene.CreateEntity(entityId);
 0227            globalScene.EntityComponentCreateOrUpdateWithModel(entityId, CLASS_ID_COMPONENT.AVATAR_SHAPE, avatarModel);
 0228            UpdateEntityTransform(globalScene, entityId, position, Quaternion.identity, Vector3.one);
 229
 0230            instantiatedBots.Add(entityId);
 0231        }
 232
 233        /// <summary>
 234        ///Removes an instantiated bot. Every bot has its ID as its avatar name.
 235        /// </summary>
 236        /// <param name="targetEntityId">The target bot ID. Every bot has its ID as its avatar name.</param>
 237        public void RemoveBot(string targetEntityId)
 238        {
 0239            if (!instantiatedBots.Contains(targetEntityId))
 0240                return;
 241
 0242            globalScene.RemoveEntity(targetEntityId);
 0243            instantiatedBots.Remove(targetEntityId);
 0244        }
 245
 246        /// <summary>
 247        /// Removes all instantiated bots.
 248        /// </summary>
 249        public void ClearBots()
 250        {
 0251            while (instantiatedBots.Count > 0)
 252            {
 0253                RemoveBot(instantiatedBots[0]);
 254            }
 0255            Log("Removed all bots.");
 0256        }
 257
 258        /// <summary>
 259        /// Randomizes a whole avatar set of wearables and returns a list with all the wearable IDs
 260        /// </summary>
 261        List<string> GetRandomizedWearablesSet()
 262        {
 0263            var wearablesSet = new List<string>();
 264
 0265            if (eyesWearableIds.Count > 0)
 0266                wearablesSet.Add(eyesWearableIds[Random.Range(0, eyesWearableIds.Count)]);
 267
 0268            if (eyebrowsWearableIds.Count > 0)
 0269                wearablesSet.Add(eyebrowsWearableIds[Random.Range(0, eyebrowsWearableIds.Count)]);
 270
 0271            if (mouthWearableIds.Count > 0)
 0272                wearablesSet.Add(mouthWearableIds[Random.Range(0, mouthWearableIds.Count)]);
 273
 0274            if (hairWearableIds.Count > 0)
 0275                wearablesSet.Add(hairWearableIds[Random.Range(0, hairWearableIds.Count)]);
 276
 0277            if (facialWearableIds.Count > 0)
 0278                wearablesSet.Add(facialWearableIds[Random.Range(0, facialWearableIds.Count)]);
 279
 0280            if (upperBodyWearableIds.Count > 0)
 0281                wearablesSet.Add(upperBodyWearableIds[Random.Range(0, upperBodyWearableIds.Count)]);
 282
 0283            if (lowerBodyWearableIds.Count > 0)
 0284                wearablesSet.Add(lowerBodyWearableIds[Random.Range(0, lowerBodyWearableIds.Count)]);
 285
 0286            if (feetWearableIds.Count > 0)
 0287                wearablesSet.Add(feetWearableIds[Random.Range(0, feetWearableIds.Count)]);
 288
 0289            if (bodyshapeWearableIds.Count > 0)
 0290                wearablesSet.Add(bodyshapeWearableIds[Random.Range(0, bodyshapeWearableIds.Count)]);
 291
 0292            return wearablesSet;
 293        }
 294
 295        /// <summary>
 296        /// Starts a coroutine that traverses a % of the instantiated population at that moment and updates their waypoi
 297        /// </summary>
 298        /// <param name="populationNormalizedPercentage">The population % that will start moving, expressed normalized e
 299        /// <param name="waypointsUpdateTime">The time wait in seconds for each waypoints update</param>
 300        public void StartRandomMovement(CoordsRandomMovementConfig config)
 301        {
 0302            if (instantiatedBots.Count == 0)
 303            {
 0304                Log($"Can't start randomized movement if there are no bots instantiated. Please first instantiate some b
 0305                return;
 306            }
 307
 0308            PatchCoordsRandomMovementConfig(config);
 309
 0310            Log($"Starting randomized movement on {(config.populationNormalizedPercentage * 100)}% of the current popula
 311
 0312            StopRandomMovement();
 313
 0314            int instantiatedCount = instantiatedBots.Count;
 0315            int botsAmount = Mathf.Min(Mathf.FloorToInt(instantiatedCount * config.populationNormalizedPercentage), inst
 316
 0317            List<int> randomBotIndices = new List<int>();
 0318            for (int i = 0; i < botsAmount; i++)
 319            {
 0320                int randomIndex = Random.Range(0, instantiatedCount);
 321
 0322                if (botsAmount == instantiatedCount)
 323                {
 0324                    randomIndex = i;
 0325                }
 326                else
 327                {
 0328                    while (randomBotIndices.Contains(randomIndex))
 329                    {
 0330                        randomIndex = Random.Range(0, instantiatedCount);
 331                    }
 332                }
 333
 0334                randomBotIndices.Add(randomIndex);
 335            }
 336
 0337            movementRoutine = CoroutineStarter.Start(RandomMovementRoutine(randomBotIndices, config));
 0338        }
 339
 340        private const float WAYPOINTS_UPDATE_DEFAULT_TIME = 5f;
 341        private const float CHARACTER_HEIGHT = 1.6f;
 342        private void PatchCoordsRandomMovementConfig(CoordsRandomMovementConfig config)
 343        {
 0344            config.populationNormalizedPercentage = Mathf.Clamp(config.populationNormalizedPercentage, 0f, 1f);
 345
 346            // TODO(Brian): Use nullable types here, this may fail.
 0347            if (config.waypointsUpdateTime == EnvironmentSettings.UNINITIALIZED_FLOAT)
 348            {
 0349                config.waypointsUpdateTime = WAYPOINTS_UPDATE_DEFAULT_TIME;
 0350                Log($"waypointsUpdateTime value wasn't provided... using default time: {config.waypointsUpdateTime}");
 351            }
 352
 0353            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 354            {
 0355                config.xCoord = Mathf.Floor(lastConfigUsed.xPos / ParcelSettings.PARCEL_SIZE);
 0356                Log($"X Coordinate value wasn't provided... using last bots spawning X coordinate: {config.xCoord}");
 357            }
 358
 0359            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 360            {
 0361                config.yCoord = Mathf.Floor(lastConfigUsed.zPos / ParcelSettings.PARCEL_SIZE);
 0362                Log($"Y Coordinate value wasn't provided... using last bots spawning Y coordinate: {config.yCoord}");
 363            }
 364
 0365            if (config.areaWidth == 0)
 366            {
 0367                config.areaWidth = lastConfigUsed.areaWidth;
 0368                Log($"Area width provided is 0... using last bots spawning area config width: {config.areaWidth}");
 369            }
 370
 0371            if (config.areaDepth == 0)
 372            {
 0373                config.areaWidth = lastConfigUsed.areaDepth;
 0374                Log($"Area depth provided is 0... using last bots spawning area config depth: {config.areaWidth}");
 375            }
 0376        }
 377
 378        public void StopRandomMovement()
 379        {
 0380            if (movementRoutine == null)
 0381                return;
 382
 0383            CoroutineStarter.Stop(movementRoutine);
 0384            Log("Stopped bots movement.");
 0385        }
 386
 387        private float lastMovementUpdateTime;
 388        IEnumerator RandomMovementRoutine(List<int> targetBots, CoordsRandomMovementConfig config)
 389        {
 0390            lastMovementUpdateTime = Time.timeSinceLevelLoad;
 0391            while (true)
 392            {
 0393                float currentTime = Time.timeSinceLevelLoad;
 0394                if (currentTime - lastMovementUpdateTime >= config.waypointsUpdateTime)
 395                {
 0396                    lastMovementUpdateTime = currentTime;
 0397                    foreach (int targetBotIndex in targetBots)
 398                    {
 399                        // Thanks to the avatars movement interpolation, we can just update their entity position to the
 400
 0401                        Vector3 position = new Vector3(config.xCoord * ParcelSettings.PARCEL_SIZE, lastConfigUsed.yPos, 
 0402                        Vector3 randomizedAreaPosition = new Vector3(Random.Range(position.x, position.x + config.areaWi
 403                            position.y, Random.Range(position.z, position.z + config.areaDepth));
 404
 405
 0406                        UpdateEntityTransform(globalScene, instantiatedBots[targetBotIndex], randomizedAreaPosition, Qua
 407                    }
 408                }
 409
 0410                yield return null;
 411            }
 412        }
 413
 414        void UpdateEntityTransform(ISceneMessageProcessor scene, string entityId, Vector3 position, Quaternion rotation,
 415        {
 0416            PB_Transform pB_Transform = GetPBTransform(position, rotation, scale);
 0417            scene.EntityComponentCreateOrUpdate(
 418                entityId,
 419                CLASS_ID_COMPONENT.TRANSFORM,
 420                System.Convert.ToBase64String(pB_Transform.ToByteArray())
 421            );
 0422        }
 423
 424        public PB_Transform GetPBTransform(Vector3 position, Quaternion rotation, Vector3 scale)
 425        {
 0426            PB_Transform pbTranf = new PB_Transform();
 0427            pbTranf.Position = new PB_Vector3();
 0428            pbTranf.Position.X = position.x;
 0429            pbTranf.Position.Y = position.y;
 0430            pbTranf.Position.Z = position.z;
 0431            pbTranf.Rotation = new PB_Quaternion();
 0432            pbTranf.Rotation.X = rotation.x;
 0433            pbTranf.Rotation.Y = rotation.y;
 0434            pbTranf.Rotation.Z = rotation.z;
 0435            pbTranf.Rotation.W = rotation.w;
 0436            pbTranf.Scale = new PB_Vector3();
 0437            pbTranf.Scale.X = scale.x;
 0438            pbTranf.Scale.Y = scale.y;
 0439            pbTranf.Scale.Z = scale.z;
 0440            return pbTranf;
 441        }
 442
 443        /// <summary>
 444        /// Logs the tool messages in console regardless of the "Debug.unityLogger.logEnabled" value.
 445        /// </summary>
 446        private void Log(string message)
 447        {
 0448            bool originalLogEnabled = Debug.unityLogger.logEnabled;
 0449            Debug.unityLogger.logEnabled = true;
 450
 0451            Debug.Log("BotsController - " + message);
 452
 0453            Debug.unityLogger.logEnabled = originalLogEnabled;
 0454        }
 455    }
 456}