コード例 #1
0
ファイル: MapUtil.cs プロジェクト: DrMabuse90/PoGo-UWP
        public static ulong[] GetCellIdsForLatLong(double latitude, double longitude)
        {
            var latLong = S2LatLng.FromDegrees(latitude, longitude);
            var cell    = S2CellId.FromLatLng(latLong);
            var cellId  = cell.ParentForLevel(15);
            var cells   = cellId.GetEdgeNeighbors();
            var cellIds = new List <ulong>
            {
                cellId.Id
            };

            foreach (var cellEdge1 in cells)
            {
                if (!cellIds.Contains(cellEdge1.Id))
                {
                    cellIds.Add(cellEdge1.Id);
                }

                foreach (var cellEdge2 in cellEdge1.GetEdgeNeighbors())
                {
                    if (!cellIds.Contains(cellEdge2.Id))
                    {
                        cellIds.Add(cellEdge2.Id);
                    }
                }
            }

            return(cellIds.ToArray());
        }
コード例 #2
0
        private S2CellId getCellId(double latDegrees, double lngDegrees)
        {
            var id = S2CellId.FromLatLng(S2LatLng.FromDegrees(latDegrees, lngDegrees));

            Trace.WriteLine(Convert.ToString(unchecked ((long)id.Id), 16));
            return(id);
        }
コード例 #3
0
        public static ulong GetPokeCell(ICoordinate poke)
        {
            var latLng = S2LatLng.FromDegrees((double)poke.Latitude, (double)poke.Longitude);
            var hash   = S2CellId.FromLatLng(latLng).ParentForLevel(20).Id;

            return(hash);
        }
コード例 #4
0
        public void AddUser(Guid uid, double lon, double lat)
        {
            var lonLat = S2LatLng.FromDegrees(lat, lon);

            var cellId = S2CellId.FromLatLng(lonLat);

            var cellIdStorageLevel = cellId.ParentForLevel(_level);

            var userList = new UserList {
                s2CellId = cellIdStorageLevel, list = new List <Guid>()
            };

            var item = tree.Search(userList.s2CellId);

            if (item != null)
            {
                userList = new UserList {
                    s2CellId = item.Key, list = item.Pointer
                };

                tree.Delete(userList.s2CellId);
            }

            if (userList.list == null)
            {
                userList.list = new List <Guid>();
            }
            userList.list.Add(uid);

            tree.Insert(userList.s2CellId, userList.list);
        }
コード例 #5
0
 public void testInverses()
 {
     Trace.WriteLine("TestInverses");
     // Check the conversion of random leaf cells to S2LatLngs and back.
     for (var i = 0; i < 200000; ++i)
     {
         var id = getRandomCellId(S2CellId.MaxLevel);
         Assert.True(id.IsLeaf && id.Level == S2CellId.MaxLevel);
         var center = id.ToLatLng();
         JavaAssert.Equal(S2CellId.FromLatLng(center).Id, id.Id);
     }
 }
コード例 #6
0
ファイル: S2Helper.cs プロジェクト: wbonis/PokeBot3
        public static List <ulong> GetNearbyCellIds(double longitude, double latitude)
        {
            var nearbyCellIds = new List <S2CellId>();

            var cellId = S2CellId.FromLatLng(S2LatLng.FromDegrees(latitude, longitude)).ParentForLevel(15);//.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent;

            nearbyCellIds.Add(cellId);
            for (int i = 0; i < 10; i++)
            {
                nearbyCellIds.Add(GetPrevious(cellId, i));
                nearbyCellIds.Add(GetNext(cellId, i));
            }

            return(nearbyCellIds.Select(c => c.Id).OrderBy(c => c).ToList());
        }
コード例 #7
0
        private async Task <ServiceResponse> ConvertPokestopToGym(IPokestop pokestop, string newName)
        {
            var gym = GymRepository.CreateInstance();

            gym.Name       = pokestop.Name;
            gym.ExternalId = pokestop.ExternalId;
            gym.PictureUrl = pokestop.Url;
            gym.Latitude   = pokestop.Latitude;
            gym.Longitude  = pokestop.Longitude;
            // Calculate WeatherCellId
            var latlng  = S2LatLng.FromDegrees(pokestop.Latitude, pokestop.Longitude);
            var cellId  = S2CellId.FromLatLng(latlng);
            var level10 = cellId.ParentForLevel(10);

            gym.WeatherCellId = level10.Id;
            //await GymRepository.AddAsync(gym);
            //await Repository.DeleteAsync(pokestop);
            return(new ServiceResponse(true, "Pokestop converted to Gym. WeatherCellId: " + gym.WeatherCellId));
        }
コード例 #8
0
        public static List <ulong> GetNearbyCellIds(double longitude, double latitude)
        {
            var nearbyCellIds = new List <S2CellId>();

            var cellId = S2CellId.FromLatLng(S2LatLng.FromDegrees(latitude, longitude)).ParentForLevel(15);

            nearbyCellIds.Add(cellId);

            var neighbours = new List <S2CellId>();

            cellId.GetAllNeighbors(15, neighbours);

            foreach (var neighbour in neighbours)
            {
                nearbyCellIds.Add(neighbour);
                nearbyCellIds.AddRange(neighbour.GetEdgeNeighbors());
            }

            return(nearbyCellIds.Select(c => c.Id).Distinct().OrderBy(c => c).ToList());
        }
コード例 #9
0
        public void AddUser(Guid uid, double lon, double lat)
        {
            lock (locker)
            {
                var lonLat = S2LatLng.FromDegrees(lat, lon);

                var cellId = S2CellId.FromLatLng(lonLat);

                var cellIdStorageLevel = cellId.ParentForLevel(_level);

                _currentUsersLocations[uid] = cellIdStorageLevel;

                var query_res = rtree.Search(new UserList()
                {
                    s2CellId = cellIdStorageLevel
                });

                var users = new List <Guid>();
                if (query_res.Count > 0)
                {
                    foreach (var item in query_res)
                    {
                        item.Start.list.Add(uid);
                    }
                    return;
                }

                users.Add(uid);

                var toinsert = new UserList()
                {
                    s2CellId = cellIdStorageLevel, list = users
                };

                rtree.Add(new Interval <UserList>()
                {
                    Start = toinsert, End = toinsert
                });
            }
        }
コード例 #10
0
        public void AddUser(Guid uid, double lon, double lat)
        {
            var lonLat = S2LatLng.FromDegrees(lat, lon);

            var cellId = S2CellId.FromLatLng(lonLat);

            var cellIdStorageLevel = cellId.ParentForLevel(_level);

            //var userList = new UserList { s2CellId = cellIdStorageLevel, list = new List<Guid>() };

            var query_res = rtree.Query(cellIdStorageLevel);

            _currentUsersLocations[uid] = cellIdStorageLevel;
            SimpleRangeItem rangeItem = null;

            if (query_res.Count > 0)
            {
                var users = new List <Guid>();
                foreach (var item in query_res)
                {
                    users.AddRange(item.Content);
                }

                rangeItem = new SimpleRangeItem {
                    Range = new Range <S2CellId>(cellIdStorageLevel), Content = users
                };

                rtree.Remove(query_res[0]);
            }

            if (rangeItem == null)
            {
                rangeItem = new SimpleRangeItem {
                    Range = new Range <S2CellId>(cellIdStorageLevel), Content = new List <Guid> ()
                };
            }
            rangeItem.Content.Add(uid);

            rtree.Add(rangeItem);
        }
コード例 #11
0
        // Handle MAD data

        /*
         * [
         *  HttpPost("/raw"),
         *  Produces("application/json"),
         * ]
         * public async Task<ProtoResponse> PostAsync(List<ProtoData> payloads)
         * {
         *  Response.Headers["Accept"] = "application/json";
         *  Response.Headers["Content-Type"] = "application/json";
         *  var response await HandleProtoRequest(new ProtoPayload
         *  {
         *      Username = "******",
         *      Uuid = Request.Headers["Origin"],
         *      Contents = payloads,
         *  });
         *  return response;
         * }
         */

        #endregion

        #region Handlers

        private async Task <ProtoResponse> HandleProtoRequest(ProtoPayload payload)
        {
            if (payload == null)
            {
                _logger.LogError("Invalid proto payload received");
                return(null);
            }

            var stopwatch = new Stopwatch();

            stopwatch.Start();
            var device = await _deviceRepository.GetByIdAsync(payload.Uuid).ConfigureAwait(false);

            if (device != null)
            {
                device.LastLatitude  = payload.LatitudeTarget;
                device.LastLongitude = payload.LongitudeTarget;
                device.LastSeen      = DateTime.UtcNow.ToTotalSeconds();
                await _deviceRepository.UpdateAsync(device).ConfigureAwait(false);
            }

            if (!string.IsNullOrEmpty(payload.Username) && payload.Level > 0)
            {
                if (!_levelCache.ContainsKey(payload.Username))
                {
                    _levelCache.Add(payload.Username, payload.Level);
                }
                else
                {
                    var oldLevel = _levelCache[payload.Username];
                    if (oldLevel != payload.Level)
                    {
                        var account = await _accountRepository.GetByIdAsync(payload.Username).ConfigureAwait(false);

                        if (account != null)
                        {
                            account.Level = payload.Level;
                            await _accountRepository.UpdateAsync(account).ConfigureAwait(false);
                        }
                        _levelCache[payload.Username] = payload.Level;
                    }
                }
            }
            if (payload.Contents?.Count == 0)
            {
                _logger.LogWarning($"[Proto] [{payload.Uuid}] Invalid GMO");
                return(null);
            }
            var wildPokemon   = 0;
            var nearbyPokemon = 0;
            var clientWeather = 0;
            var forts         = 0;
            var fortDetails   = new List <FortDetailsOutProto>();
            var quests        = 0;
            var fortSearch    = 0;
            var encounters    = 0;
            var cells         = new List <ulong>();
            var inventory     = new List <InventoryDeltaProto>();
            var playerData    = 0;
            //var spawnpoints = new List<Spawnpoint>();

            var isEmptyGmo   = true;
            var isInvalidGmo = true;
            var containsGmo  = false;

            if (payload.Contents == null)
            {
                _logger.LogWarning($"[Proto] [{payload.Uuid}] Empty data");
                return(null);
            }

            Coordinate targetCoord = null;
            var        inArea      = false;

            if (payload.LatitudeTarget != 0 && payload.LongitudeTarget != 0)
            {
                targetCoord = new Coordinate(payload.LatitudeTarget, payload.LongitudeTarget);
            }
            var      targetKnown  = false;
            S2CellId targetCellId = default;

            if (targetCoord != null)
            {
                // Check target is within cell id instead of checking geofences
                targetKnown  = true;
                targetCellId = S2CellId.FromLatLng(S2LatLng.FromDegrees(targetCoord.Latitude, targetCoord.Longitude));
                //_logger.LogDebug($"[Proto] [{payload.Uuid}] Data received within target area {targetCoord} and target distance {payload.TargetMaxDistance}");
            }
            //_logger.LogWarning($"[{device.Uuid}] InArea={inArea}");

            foreach (var rawData in payload.Contents)
            {
                if (string.IsNullOrEmpty(rawData.Data))
                {
                    _logger.LogWarning($"[Proto] [{payload.Uuid}] Unhandled proto {rawData.Method}: {rawData.Data}");
                    continue;
                }
                var data   = rawData.Data;
                var method = (Method)rawData.Method;
                switch (method)
                {
                case Method.GetPlayer:
                    try
                    {
                        var gpr = GetPlayerOutProto.Parser.ParseFrom(Convert.FromBase64String(data));
                        if (gpr?.Success == true)
                        {
                            await PushData(RedisChannels.ProtoAccount, new
                            {
                                gpr,
                                username = payload.Username,
                            });

                            playerData++;
                        }
                        else
                        {
                            _logger.LogError($"[Proto] [{payload.Uuid}] Malformed GetPlayerOutProto");
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"[Proto] [{payload.Uuid}] Unable to decode GetPlayerOutProto: {ex}");
                    }
                    break;

                case Method.GetHoloholoInventory:
                    try
                    {
                        var ghi = GetHoloholoInventoryOutProto.Parser.ParseFrom(Convert.FromBase64String(data));
                        if (ghi?.Success == true)
                        {
                            if (ghi.InventoryDelta.InventoryItem?.Count > 0)
                            {
                                // TODO: Publish with redis
                                inventory.Add(ghi.InventoryDelta);
                            }
                        }
                        else
                        {
                            _logger.LogError($"[Proto] [{payload.Uuid}] Malformed GetHoloholoInventoryOutProto");
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"[Proto] [{payload.Uuid}] Unable to decode GetHoloholoInventoryOutProto: {ex}");
                    }
                    break;

                case Method.FortSearch:
                    try
                    {
                        var fsr = FortSearchOutProto.Parser.ParseFrom(Convert.FromBase64String(data));
                        if (fsr != null)
                        {
                            if (fsr.ChallengeQuest?.Quest != null)
                            {
                                await PushData(RedisChannels.ProtoQuest, new
                                {
                                    raw = data,
                                });

                                quests++;
                            }
                            //fortSearch.Add(fsr);
                            fortSearch++;
                        }
                        else
                        {
                            _logger.LogError($"[Proto] [{payload.Uuid}] Malformed FortSearchOutProto");
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"[Proto] [{payload.Uuid}] Unable to decode FortSearchOutProto: {ex}");
                    }
                    break;

                case Method.Encounter:
                    isEmptyGmo   = false;
                    isInvalidGmo = false;
                    try
                    {
                        if (payload.Level >= 30)
                        {
                            var er = EncounterOutProto.Parser.ParseFrom(Convert.FromBase64String(data));
                            if (er?.Status == EncounterOutProto.Types.Status.EncounterSuccess)
                            {
                                await PushData(RedisChannels.ProtoEncounter, new
                                {
                                    data     = er,
                                    username = payload.Username,
                                });

                                encounters++;
                            }
                            else if (er == null)
                            {
                                _logger.LogError($"[Proto] [{payload.Uuid}] Malformed EncounterOutProto");
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"[Proto] [{payload.Uuid}] Unable to decode EncounterOutProto: {ex}");
                    }
                    break;

                case Method.FortDetails:
                    try
                    {
                        var fdr = FortDetailsOutProto.Parser.ParseFrom(Convert.FromBase64String(data));
                        if (fdr != null)
                        {
                            fortDetails.Add(fdr);
                            //fortDetails++;
                            // TODO: Publish with redis
                            //await PublishData(RedisChannels.Fort, fdr);
                        }
                        else
                        {
                            _logger.LogError($"[Proto] [{payload.Uuid}] Malformed FortDetailsOutProto");
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"[Proto] [{payload.Uuid}] Unable to decode FortDetailsOutProto: {ex}");
                    }
                    break;

                case Method.GetMapObjects:
                    containsGmo = true;
                    try
                    {
                        var gmo = GetMapObjectsOutProto.Parser.ParseFrom(Convert.FromBase64String(data));
                        if (gmo != null)
                        {
                            isInvalidGmo = false;
                            var mapCellsNew = gmo.MapCell;

                            if (mapCellsNew.Count == 0)
                            {
                                //_logger.LogDebug($"[Proto] [{payload.Uuid}] Map cells are empty");
                                //return null;
                            }

                            // Check if we're within the same cell, if so then we are within the target distance
                            if (!inArea && targetKnown && mapCellsNew.Select(x => x.S2CellId).Contains(targetCellId.Id))
                            {
                                inArea = true;
                            }

                            foreach (var mapCell in mapCellsNew)
                            {
                                cells.Add(mapCell.S2CellId);
                                await PushData(RedisChannels.ProtoCell, mapCell.S2CellId);

                                var tsMs = mapCell.AsOfTimeMs;
                                foreach (var wild in mapCell.WildPokemon)
                                {
                                    await PushData(RedisChannels.ProtoWildPokemon, new
                                    {
                                        cell         = mapCell.S2CellId,
                                        data         = wild,
                                        timestamp_ms = tsMs,
                                        username     = payload.Username,
                                    });

                                    wildPokemon++;
                                }
                                foreach (var nearby in mapCell.NearbyPokemon)
                                {
                                    await PushData(RedisChannels.ProtoNearbyPokemon, new
                                    {
                                        cell         = mapCell.S2CellId,
                                        data         = nearby,
                                        timestamp_ms = tsMs,
                                        username     = payload.Username,
                                    });

                                    nearbyPokemon++;
                                }
                                foreach (var fort in mapCell.Fort)
                                {
                                    await PushData(RedisChannels.ProtoFort, new
                                    {
                                        cell = mapCell.S2CellId,
                                        data = fort,
                                    });

                                    forts++;
                                }
                            }
                            foreach (var weather in gmo.ClientWeather)
                            {
                                await PushData(RedisChannels.ProtoWeather, new Weather(weather));

                                clientWeather++;
                            }
                            if (wildPokemon == 0 && nearbyPokemon == 0 && forts == 0 && quests == 0)
                            {
                                foreach (var cellId in cells)
                                {
                                    if (!_emptyCells.ContainsKey(cellId))
                                    {
                                        _emptyCells.Add(cellId, 1);
                                    }
                                    else
                                    {
                                        _emptyCells[cellId]++;
                                    }
                                    if (_emptyCells[cellId] == 3)
                                    {
                                        _logger.LogWarning($"[Proto] [{payload.Uuid}] Cell {cellId} was empty 3 times in a row, assuming empty...");
                                        await PushData(RedisChannels.ProtoCell, cellId);
                                    }
                                }
                                _logger.LogDebug($"[Proto] [{payload.Uuid}] GMO is empty");
                                isEmptyGmo = true;
                            }
                            else
                            {
                                cells.ForEach(cellId => _emptyCells[cellId] = 0);
                                isEmptyGmo = false;
                            }
                        }
                        else
                        {
                            _logger.LogError($"[Proto] [{payload.Uuid}] Malformed GetMapObjectsOutProto");
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"[Proto] [{payload.Uuid}] Unable to decode GetMapObjectsOutProto: {ex}");
                    }
                    break;

                case Method.GymGetInfo:
                    try
                    {
                        var ggi = GymGetInfoOutProto.Parser.ParseFrom(Convert.FromBase64String(data));
                        if (ggi != null)
                        {
                            if (ggi.GymStatusAndDefenders == null)
                            {
                                ConsoleExt.WriteWarn($"[DataConsumer] Invalid GymStatusAndDefenders provided, skipping...\n: {ggi}");
                                continue;
                            }
                            var fortId       = ggi.GymStatusAndDefenders.PokemonFortProto.FortId;
                            var gymDefenders = ggi.GymStatusAndDefenders.GymDefender;
                            if (gymDefenders == null)
                            {
                                continue;
                            }

                            foreach (var gymDefender in gymDefenders)
                            {
                                if (gymDefender.TrainerPublicProfile != null)
                                {
                                    await PushData(RedisChannels.ProtoGymTrainer, new Trainer(gymDefender));
                                }
                                if (gymDefender.MotivatedPokemon != null)
                                {
                                    await PushData(RedisChannels.ProtoGymDefender, new GymDefender(fortId, gymDefender));
                                }
                            }
                        }
                        else
                        {
                            _logger.LogError($"[Proto] [{payload.Uuid}] Malformed GymGetInfoOutProto");
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError($"[Proto] [{payload.Uuid}] Unable to decode GymGetInfoOutProto: {ex}");
                    }
                    break;

                //case Method.Unset:
                default:
                    _logger.LogDebug($"[Proto] [{payload.Uuid}] Invalid method or data provided. {method}:{data}");
                    break;
                }
            }

            Coordinate pokemonCoords = null;

            /*
             * if (targetCoord != null)
             * {
             *  foreach (var fort in forts)
             *  {
             *      if (!inArea)
             *      {
             *          var coord = new Coordinate(fort.data.Latitude, fort.data.Longitude);
             *          if (coord.DistanceTo(targetCoord) <= payload.TargetMaxDistance)
             *          {
             *              inArea = true;
             *          }
             *      }
             *  }
             * }
             * if (targetCoord != null || payload.PokemonEncounterId != null)
             * {
             *  foreach (var pokemon in wildPokemons)
             *  {
             *      WildPokemonProto wild = (WildPokemonProto)pokemon.data;
             *      if (targetCoord != null)
             *      {
             *          if (pokemonCoords != null && inArea)
             *          {
             *              break;
             *          }
             *
             *          if (!inArea)
             *          {
             *              var coord = new Coordinate(wild.Latitude, wild.Longitude);
             *              if (coord.DistanceTo(targetCoord) <= payload.TargetMaxDistance)
             *              {
             *                  inArea = true;
             *              }
             *          }
             *      }
             *      if (!string.IsNullOrEmpty(payload.PokemonEncounterId))
             *      {
             *          if (pokemonCoords != null && inArea)
             *          {
             *              break;
             *          }
             *
             *          if (pokemonCoords == null)
             *          {
             *              if (string.Compare(wild.EncounterId.ToString(), payload.PokemonEncounterId, true) == 0)
             *              {
             *                  pokemonCoords = new Coordinate(wild.Latitude, wild.Longitude);
             *              }
             *          }
             *      }
             *  }
             * }
             * if (targetCoord != null && !inArea)
             * {
             *  foreach (var cell in cells)
             *  {
             *      if (inArea)
             *      {
             *          break;
             *      }
             *
             *      var s2cell = new S2Cell(new S2CellId(cell));
             *      var latlng = new S2LatLng(s2cell.Center);
             *      var coord = new Coordinate(latlng.LatDegrees, latlng.LngDegrees);
             *      if (coord.DistanceTo(targetCoord) <= Math.Max(payload.TargetMaxDistance ?? 250, 100))
             *      {
             *          inArea = true;
             *      }
             *  }
             * }
             */

            stopwatch.Stop();

            var response = new ProtoResponse
            {
                Status = "ok",
                Data   = new ProtoDataDetails
                {
                    Nearby             = nearbyPokemon,
                    Wild               = wildPokemon,
                    Forts              = forts,
                    Quests             = quests,
                    FortSearch         = fortSearch,
                    Encounters         = encounters,
                    Level              = payload.Level,
                    OnlyEmptyGmos      = containsGmo && isEmptyGmo,
                    OnlyInvalidGmos    = containsGmo && isInvalidGmo,
                    ContainsGmos       = containsGmo,
                    InArea             = inArea,
                    LatitudeTarget     = targetCoord?.Latitude,
                    LongitudeTarget    = targetCoord?.Longitude,
                    PokemonLatitude    = pokemonCoords?.Latitude,
                    PokemonLongitude   = pokemonCoords?.Longitude,
                    PokemonEncounterId = payload.PokemonEncounterId,
                },
            };

            _logger.LogInformation($"[{payload.Uuid}] {response.ToJson()} parsed in {stopwatch.Elapsed.TotalSeconds}s");
            return(response);
        }
コード例 #12
0
        public bool UpdateUser(Guid uid, double lon, double lat)
        {
            lock (locker)
            {
                var lonLat = S2LatLng.FromDegrees(lat, lon);

                var cellId = S2CellId.FromLatLng(lonLat);

                var cellIdStorageLevel = cellId.ParentForLevel(_level);

                if (!_currentUsersLocations.ContainsKey(uid))
                {
                    return(false);
                }

                var oldCell = _currentUsersLocations[uid];

                if (oldCell == cellIdStorageLevel)
                {
                    return(true);
                }
                _currentUsersLocations[uid] = cellIdStorageLevel;

                var query_res = rtree.Search(new UserList()
                {
                    s2CellId = oldCell
                });

                var users = new List <Guid>();
                if (query_res.Count > 0)
                {
                    //remove from old cell
                    foreach (var item in query_res)
                    {
                        item.Start.list.Remove(uid);
                        if (item.Start.list.Count == 0)
                        {
                            rtree.Remove(item);
                        }
                    }
                }

                query_res = rtree.Search(new UserList()
                {
                    s2CellId = cellIdStorageLevel
                });

                if (query_res.Count > 0)
                {
                    foreach (var item in query_res)
                    {
                        item.Start.list.Add(uid);
                    }
                    return(true);
                }
                users.Add(uid);

                var toinsert = new UserList()
                {
                    s2CellId = cellIdStorageLevel, list = users
                };

                rtree.Add(new Interval <UserList>()
                {
                    Start = toinsert, End = toinsert
                });
                return(true);
            }
        }