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

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
BotsController()0%2100%
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 IParcelScene globalScene;
 022        private List<string> randomizedCollections = new List<string>();
 023        private List<long> instantiatedBots = new List<long>();
 024        private List<string> eyesWearableIds = new List<string>();
 025        private List<string> eyebrowsWearableIds = new List<string>();
 026        private List<string> mouthWearableIds = new List<string>();
 027        private List<string> hairWearableIds = new List<string>();
 028        private List<string> facialWearableIds = new List<string>();
 029        private List<string> upperBodyWearableIds = new List<string>();
 030        private List<string> lowerBodyWearableIds = new List<string>();
 031        private List<string> feetWearableIds = new List<string>();
 032        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]];
 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 => DataStore.i.player.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 - CHARACTER_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            long entityId = instantiatedBots.Count;
 213
 0214            AvatarModel avatarModel = new AvatarModel()
 215            {
 216                id = entityId.ToString(),
 217                name = entityId.ToString(),
 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.componentsManagerLegacy.EntityComponentCreateOrUpdate(entityId, CLASS_ID_COMPONENT.AVATAR_SHAPE,
 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(long 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 const float CHARACTER_HEIGHT = 1.6f;
 341        private void PatchCoordsRandomMovementConfig(CoordsRandomMovementConfig config)
 342        {
 0343            config.populationNormalizedPercentage = Mathf.Clamp(config.populationNormalizedPercentage, 0f, 1f);
 344
 345            // TODO(Brian): Use nullable types here, this may fail.
 0346            if (config.waypointsUpdateTime == EnvironmentSettings.UNINITIALIZED_FLOAT)
 347            {
 0348                config.waypointsUpdateTime = WAYPOINTS_UPDATE_DEFAULT_TIME;
 0349                Log($"waypointsUpdateTime value wasn't provided... using default time: {config.waypointsUpdateTime}");
 350            }
 351
 0352            if (config.xCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 353            {
 0354                config.xCoord = Mathf.Floor(lastConfigUsed.xPos / ParcelSettings.PARCEL_SIZE);
 0355                Log($"X Coordinate value wasn't provided... using last bots spawning X coordinate: {config.xCoord}");
 356            }
 357
 0358            if (config.yCoord == EnvironmentSettings.UNINITIALIZED_FLOAT)
 359            {
 0360                config.yCoord = Mathf.Floor(lastConfigUsed.zPos / ParcelSettings.PARCEL_SIZE);
 0361                Log($"Y Coordinate value wasn't provided... using last bots spawning Y coordinate: {config.yCoord}");
 362            }
 363
 0364            if (config.areaWidth == 0)
 365            {
 0366                config.areaWidth = lastConfigUsed.areaWidth;
 0367                Log($"Area width provided is 0... using last bots spawning area config width: {config.areaWidth}");
 368            }
 369
 0370            if (config.areaDepth == 0)
 371            {
 0372                config.areaWidth = lastConfigUsed.areaDepth;
 0373                Log($"Area depth provided is 0... using last bots spawning area config depth: {config.areaWidth}");
 374            }
 0375        }
 376
 377        public void StopRandomMovement()
 378        {
 0379            if (movementRoutine == null)
 0380                return;
 381
 0382            CoroutineStarter.Stop(movementRoutine);
 0383            Log("Stopped bots movement.");
 0384        }
 385
 386        private float lastMovementUpdateTime;
 387        IEnumerator RandomMovementRoutine(List<int> targetBots, CoordsRandomMovementConfig config)
 388        {
 0389            lastMovementUpdateTime = Time.timeSinceLevelLoad;
 0390            while (true)
 391            {
 0392                float currentTime = Time.timeSinceLevelLoad;
 0393                if (currentTime - lastMovementUpdateTime >= config.waypointsUpdateTime)
 394                {
 0395                    lastMovementUpdateTime = currentTime;
 0396                    foreach (int targetBotIndex in targetBots)
 397                    {
 398                        // Thanks to the avatars movement interpolation, we can just update their entity position to the
 399
 0400                        Vector3 position = new Vector3(config.xCoord * ParcelSettings.PARCEL_SIZE, lastConfigUsed.yPos, 
 0401                        Vector3 randomizedAreaPosition = new Vector3(Random.Range(position.x, position.x + config.areaWi
 402                            position.y, Random.Range(position.z, position.z + config.areaDepth));
 403
 404
 0405                        UpdateEntityTransform(globalScene, instantiatedBots[targetBotIndex], randomizedAreaPosition, Qua
 406                    }
 407                }
 408
 0409                yield return null;
 410            }
 411        }
 412
 413        void UpdateEntityTransform(IParcelScene scene, long entityId, Vector3 position, Quaternion rotation, Vector3 sca
 414        {
 0415            PB_Transform pB_Transform = GetPBTransform(position, rotation, scale);
 0416            scene.componentsManagerLegacy.EntityComponentCreateOrUpdate(
 417                entityId,
 418                CLASS_ID_COMPONENT.TRANSFORM,
 419                System.Convert.ToBase64String(pB_Transform.ToByteArray())
 420            );
 0421        }
 422
 423        public PB_Transform GetPBTransform(Vector3 position, Quaternion rotation, Vector3 scale)
 424        {
 0425            PB_Transform pbTranf = new PB_Transform();
 0426            pbTranf.Position = new PB_Vector3();
 0427            pbTranf.Position.X = position.x;
 0428            pbTranf.Position.Y = position.y;
 0429            pbTranf.Position.Z = position.z;
 0430            pbTranf.Rotation = new PB_Quaternion();
 0431            pbTranf.Rotation.X = rotation.x;
 0432            pbTranf.Rotation.Y = rotation.y;
 0433            pbTranf.Rotation.Z = rotation.z;
 0434            pbTranf.Rotation.W = rotation.w;
 0435            pbTranf.Scale = new PB_Vector3();
 0436            pbTranf.Scale.X = scale.x;
 0437            pbTranf.Scale.Y = scale.y;
 0438            pbTranf.Scale.Z = scale.z;
 0439            return pbTranf;
 440        }
 441
 442        /// <summary>
 443        /// Logs the tool messages in console regardless of the "Debug.unityLogger.logEnabled" value.
 444        /// </summary>
 445        private void Log(string message)
 446        {
 0447            bool originalLogEnabled = Debug.unityLogger.logEnabled;
 0448            Debug.unityLogger.logEnabled = true;
 449
 0450            Debug.Log("BotsController - " + message);
 451
 0452            Debug.unityLogger.logEnabled = originalLogEnabled;
 0453        }
 454    }
 455}