/// <summary> /// Generates all SlippyMap tiles for a given area and zoom level, and saves them to the database. /// </summary> /// <param name="buffered">the GeoArea to generate tiles for</param> /// <param name="zoomLevel">the zoom level to generate tiles at.</param> public static void PregenSlippyMapTilesForArea(GeoArea buffered, int zoomLevel) { //There is a very similar function for this in Standalone.cs, but this one writes back to the main DB. var db = new PraxisContext(); db.ChangeTracker.AutoDetectChangesEnabled = false; var intersectCheck = Converters.GeoAreaToPolygon(buffered); //start drawing maptiles and sorting out data. var swCornerLon = Converters.GetSlippyXFromLon(intersectCheck.EnvelopeInternal.MinX, zoomLevel); var neCornerLon = Converters.GetSlippyXFromLon(intersectCheck.EnvelopeInternal.MaxX, zoomLevel); var swCornerLat = Converters.GetSlippyYFromLat(intersectCheck.EnvelopeInternal.MinY, zoomLevel); var neCornerLat = Converters.GetSlippyYFromLat(intersectCheck.EnvelopeInternal.MaxY, zoomLevel); //declare how many map tiles will be drawn var xTiles = neCornerLon - swCornerLon + 1; var yTiles = swCornerLat - neCornerLat + 1; var totalTiles = xTiles * yTiles; Log.WriteLog("Starting processing " + totalTiles + " maptiles for zoom level " + zoomLevel); long mapTileCounter = 0; System.Diagnostics.Stopwatch progressTimer = new System.Diagnostics.Stopwatch(); progressTimer.Start(); //foreach (var y in yCoords) for (var y = neCornerLat; y <= swCornerLat; y++) { //Make a collision box for just this row of Cell8s, and send the loop below just the list of things that might be relevant. //Add a Cell8 buffer space so all elements are loaded and drawn without needing to loop through the entire area. GeoArea thisRow = new GeoArea(Converters.SlippyYToLat(y + 1, zoomLevel) - ConstantValues.resolutionCell8, Converters.SlippyXToLon(swCornerLon, zoomLevel) - ConstantValues.resolutionCell8, Converters.SlippyYToLat(y, zoomLevel) + ConstantValues.resolutionCell8, Converters.SlippyXToLon(neCornerLon, zoomLevel) + resolutionCell8); var row = Converters.GeoAreaToPolygon(thisRow); var rowList = GetPlaces(thisRow); var tilesToSave = new ConcurrentBag <SlippyMapTile>(); Parallel.For(swCornerLon, neCornerLon + 1, (x) => { //make map tile. var info = new ImageStats(zoomLevel, x, y, IMapTiles.SlippyTileSizeSquare); var acheck = Converters.GeoAreaToPolygon(info.area); //this is faster than using a PreparedPolygon in testing, which was unexpected. var areaList = rowList.Where(a => acheck.Intersects(a.ElementGeometry)).ToList(); //This one is for the maptile var tile = MapTiles.DrawAreaAtSize(info, areaList); tilesToSave.Add(new SlippyMapTile() { TileData = tile, Values = x + "|" + y + "|" + zoomLevel, ExpireOn = DateTime.Now.AddDays(3650), AreaCovered = Converters.GeoAreaToPolygon(info.area), StyleSet = "mapTiles" }); mapTileCounter++; }); db.SlippyMapTiles.AddRange(tilesToSave); db.SaveChanges(); Log.WriteLog(mapTileCounter + " tiles processed, " + Math.Round(((mapTileCounter / (double)totalTiles * 100)), 2) + "% complete"); }//); progressTimer.Stop(); Log.WriteLog("Zoom " + zoomLevel + " map tiles drawn in " + progressTimer.Elapsed.ToString()); }
/// <summary> /// Saves a key/value pair to a given map element. Will reject a pair containing a player's deviceId in the database. /// </summary> /// <param name="elementId">the Guid exposed to clients to identify the map element.</param> /// <param name="key">The key to save to the database for the map element.</param> /// <param name="value">The value to save with the key.</param> /// <param name="expiration">If not null, expire the data in this many seconds from now.</param> /// <returns>true if data was saved, false if data was not.</returns> public static bool SetPlaceData(Guid elementId, string key, string value, double?expiration = null) { var db = new PraxisContext(); if (db.PlayerData.Any(p => p.DeviceID == key || p.DeviceID == value)) { return(false); } var row = db.PlaceGameData.Include(p => p.Place).FirstOrDefault(p => p.Place.PrivacyId == elementId && p.DataKey == key); if (row == null) { var sourceItem = db.Places.First(p => p.PrivacyId == elementId); row = new DbTables.PlaceGameData(); row.DataKey = key; row.Place = sourceItem; db.PlaceGameData.Add(row); } if (expiration.HasValue) { row.Expiration = DateTime.Now.AddSeconds(expiration.Value); } else { row.Expiration = null; } row.IvData = null; row.DataValue = value.ToByteArrayUTF8(); return(db.SaveChanges() == 1); }
/// <summary> /// Saves a key/value pair to a given player's DeviceID with a password. /// </summary> /// <param name="playerId">the unique ID for the player, expected to be their unique device ID</param> /// <param name="key">The key to save to the database. Keys are unique, and you cannot have multiples of the same key.</param> /// <param name="value">The value to save with the key.</param> /// <param name="password"></param> /// <param name="expiration">If not null, expire this data in this many seconds from now.</param> /// <returns>true if data was saved, false if data was not.</returns> public static bool SetSecurePlayerData(string playerId, string key, byte[] value, string password, double?expiration = null) { var encryptedValue = EncryptValue(value, password, out byte[] IVs); var db = new PraxisContext(); var row = db.PlayerData.FirstOrDefault(p => p.DeviceID == playerId && p.DataKey == key); if (row == null) { row = new DbTables.PlayerData(); row.DataKey = key; row.DeviceID = playerId; db.PlayerData.Add(row); } if (expiration.HasValue) { row.Expiration = DateTime.Now.AddSeconds(expiration.Value); } else { row.Expiration = null; } row.IvData = IVs; row.DataValue = encryptedValue; return(db.SaveChanges() == 1); }
/// <summary> /// Saves a key/value pair to a given map element with the given password /// </summary> /// <param name="elementId">the Guid exposed to clients to identify the map element.</param> /// <param name="key">The key to save to the database for the map element.</param> /// <param name="value">The value to save with the key.</param> /// <param name="password">The password to encrypt the value with.</param> /// <param name="expiration">If not null, expire this data in this many seconds from now.</param> /// <returns>true if data was saved, false if data was not.</returns> public static bool SetSecurePlaceData(Guid elementId, string key, byte[] value, string password, double?expiration = null) { byte[] encryptedValue = EncryptValue(value, password, out byte[] IVs); var db = new PraxisContext(); var row = db.PlaceGameData.Include(p => p.Place).FirstOrDefault(p => p.Place.PrivacyId == elementId && p.DataKey == key); if (row == null) { var sourceItem = db.Places.First(p => p.PrivacyId == elementId); row = new DbTables.PlaceGameData(); row.DataKey = key; row.Place = sourceItem; db.PlaceGameData.Add(row); } if (expiration.HasValue) { row.Expiration = DateTime.Now.AddSeconds(expiration.Value); } else { row.Expiration = null; } row.IvData = IVs; row.DataValue = encryptedValue; return(db.SaveChanges() == 1); }
public static bool SetSecureAreaData(string plusCode, string key, byte[] value, string password, double?expiration = null) { var db = new PraxisContext(); byte[] encryptedValue = EncryptValue(value, password, out byte[] IVs); var row = db.AreaGameData.FirstOrDefault(p => p.PlusCode == plusCode && p.DataKey == key); if (row == null) { row = new DbTables.AreaGameData(); row.DataKey = key; row.PlusCode = plusCode; row.GeoAreaIndex = Converters.GeoAreaToPolygon(OpenLocationCode.DecodeValid(plusCode.ToUpper())); db.AreaGameData.Add(row); } if (expiration.HasValue) { row.Expiration = DateTime.Now.AddSeconds(expiration.Value); } else { row.Expiration = null; } row.DataValue = encryptedValue; row.IvData = IVs; return(db.SaveChanges() == 1); }
public static bool SetAreaData(string plusCode, string key, string value, double?expiration = null) { var db = new PraxisContext(); if (db.PlayerData.Any(p => p.DeviceID == key || p.DeviceID == value)) { return(false); } var row = db.AreaGameData.FirstOrDefault(p => p.PlusCode == plusCode && p.DataKey == key); if (row == null) { row = new DbTables.AreaGameData(); row.DataKey = key; row.PlusCode = plusCode; row.GeoAreaIndex = Converters.GeoAreaToPolygon(OpenLocationCode.DecodeValid(plusCode.ToUpper())); db.AreaGameData.Add(row); } if (expiration.HasValue) { row.Expiration = DateTime.Now.AddSeconds(expiration.Value); } else { row.Expiration = null; } row.IvData = null; row.DataValue = value.ToByteArrayUTF8(); return(db.SaveChanges() == 1); }
/// <summary> /// Saves a key/value pair to a given player's DeviceID. Will reject a pair containing a PlusCode or map element Id. /// </summary> /// <param name="playerId"></param> /// <param name="key">The key to save to the database. Keys are unique, and you cannot have multiples of the same key.</param> /// <param name="value">The value to save with the key.</param> /// <param name="expiration">If not null, expire this data in this many seconds from now.</param> /// <returns>true if data was saved, false if data was not.</returns> public static bool SetPlayerData(string playerId, string key, string value, double?expiration = null) { if (DataCheck.IsPlusCode(key) || DataCheck.IsPlusCode(value)) { return(false); //Reject attaching a player to a pluscode. } var db = new PraxisContext(); Guid tempCheck = new Guid(); if ((Guid.TryParse(key, out tempCheck) && db.Places.Any(osm => osm.PrivacyId == tempCheck)) || (Guid.TryParse(value, out tempCheck) && db.Places.Any(osm => osm.PrivacyId == tempCheck))) { return(false); //reject attaching a player to an area } var row = db.PlayerData.FirstOrDefault(p => p.DeviceID == playerId && p.DataKey == key); if (row == null) { row = new DbTables.PlayerData(); row.DataKey = key; row.DeviceID = playerId; db.PlayerData.Add(row); } if (expiration.HasValue) { row.Expiration = DateTime.Now.AddSeconds(expiration.Value); } else { row.Expiration = null; } row.IvData = null; row.DataValue = value.ToByteArrayUTF8(); return(db.SaveChanges() == 1); }
/// <summary> /// Saves a key/value pair to the database that isn't attached to anything specific. Wil reject a pair that contains a player's device ID, PlusCode, or a map element ID. Global entries cannot be set to expire. /// </summary> /// <param name="key">The key to save to the database. Keys are unique, and you cannot have multiples of the same key.</param> /// <param name="value">The value to save with the key.</param> /// <returns>true if data was saved, false if data was not.</returns> public static bool SetGlobalData(string key, string value) { bool trackingPlayer = false; bool trackingLocation = false; var db = new PraxisContext(); if (db.PlayerData.Any(p => p.DeviceID == key || p.DeviceID == value)) { trackingPlayer = true; } if (DataCheck.IsPlusCode(key) || DataCheck.IsPlusCode(value)) { trackingLocation = true; } Guid tempCheck = new Guid(); if ((Guid.TryParse(key, out tempCheck) && db.Places.Any(osm => osm.PrivacyId == tempCheck)) || (Guid.TryParse(value, out tempCheck) && db.Places.Any(osm => osm.PrivacyId == tempCheck))) { trackingLocation = true; } if (trackingLocation && trackingPlayer) //Do not allow players and locations to be attached on the global level as a workaround to being blocked on the individual levels. { return(false); } var row = db.GlobalDataEntries.FirstOrDefault(p => p.DataKey == key); if (row == null) { row = new DbTables.GlobalDataEntries(); row.DataKey = key; db.GlobalDataEntries.Add(row); } row.DataValue = value.ToByteArrayUTF8(); return(db.SaveChanges() == 1); }
/// <summary> /// Creates all gameplay tiles for a given area and saves them to the database (or files, if that option is set) /// </summary> /// <param name="areaToDraw">the GeoArea of the area to create tiles for.</param> /// <param name="saveToFiles">If true, writes to files in the output folder. If false, saves to DB.</param> public static void PregenMapTilesForArea(GeoArea areaToDraw, bool saveToFiles = false) { //There is a very similar function for this in Standalone.cs, but this one writes back to the main DB. var intersectCheck = Converters.GeoAreaToPolygon(areaToDraw); //start drawing maptiles and sorting out data. var swCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MinY, intersectCheck.EnvelopeInternal.MinX); var neCorner = new OpenLocationCode(intersectCheck.EnvelopeInternal.MaxY, intersectCheck.EnvelopeInternal.MaxX); //declare how many map tiles will be drawn var xTiles = areaToDraw.LongitudeWidth / resolutionCell8; var yTiles = areaToDraw.LatitudeHeight / resolutionCell8; var totalTiles = Math.Truncate(xTiles * yTiles); Log.WriteLog("Starting processing maptiles for " + totalTiles + " Cell8 areas."); long mapTileCounter = 0; System.Diagnostics.Stopwatch progressTimer = new System.Diagnostics.Stopwatch(); progressTimer.Start(); //now, for every Cell8 involved, draw and name it. var yCoords = new List <double>((int)(intersectCheck.EnvelopeInternal.Height / resolutionCell8) + 1); var yVal = swCorner.Decode().SouthLatitude; while (yVal <= neCorner.Decode().NorthLatitude) { yCoords.Add(yVal); yVal += resolutionCell8; } var xCoords = new List <double>((int)(intersectCheck.EnvelopeInternal.Width / resolutionCell8) + 1); var xVal = swCorner.Decode().WestLongitude; while (xVal <= neCorner.Decode().EastLongitude) { xCoords.Add(xVal); xVal += resolutionCell8; } var allPlaces = GetPlaces(areaToDraw); object listLock = new object(); DateTime expiration = DateTime.Now.AddYears(10); foreach (var y in yCoords) { System.Diagnostics.Stopwatch thisRowSW = new System.Diagnostics.Stopwatch(); thisRowSW.Start(); var db = new PraxisContext(); db.ChangeTracker.AutoDetectChangesEnabled = false; //Make a collision box for just this row of Cell8s, and send the loop below just the list of things that might be relevant. //Add a Cell8 buffer space so all elements are loaded and drawn without needing to loop through the entire area. GeoArea thisRow = new GeoArea(y - ConstantValues.resolutionCell8, xCoords.First() - ConstantValues.resolutionCell8, y + ConstantValues.resolutionCell8 + ConstantValues.resolutionCell8, xCoords.Last() + resolutionCell8); var rowList = GetPlaces(thisRow, allPlaces); var tilesToSave = new List <MapTile>(xCoords.Count()); Parallel.ForEach(xCoords, x => { //make map tile. var plusCode = new OpenLocationCode(y, x, 10); var plusCode8 = plusCode.CodeDigits.Substring(0, 8); var plusCodeArea = OpenLocationCode.DecodeValid(plusCode8); var paddedArea = GeometrySupport.MakeBufferedGeoArea(plusCodeArea); var acheck = Converters.GeoAreaToPreparedPolygon(paddedArea); //Fastest search option is one preparedPolygon against a list of normal geometry. var areaList = rowList.Where(a => acheck.Intersects(a.ElementGeometry)).ToList(); //Get the list of areas in this maptile. int imgX = 0, imgY = 0; GetPlusCodeImagePixelSize(plusCode8, out imgX, out imgY); var info = new ImageStats(plusCodeArea, imgX, imgY); //new setup. var areaPaintOps = GetPaintOpsForStoredElements(areaList, "mapTiles", info); var tile = DrawPlusCode(plusCode8, areaPaintOps, "mapTiles"); if (saveToFiles) { File.WriteAllBytes("GameTiles\\" + plusCode8 + ".png", tile); } else { var thisTile = new MapTile() { TileData = tile, PlusCode = plusCode8, ExpireOn = expiration, AreaCovered = acheck.Geometry, StyleSet = "mapTiles" }; lock (listLock) tilesToSave.Add(thisTile); } }); mapTileCounter += xCoords.Count(); if (!saveToFiles) { db.MapTiles.AddRange(tilesToSave); db.SaveChanges(); } Log.WriteLog(mapTileCounter + " tiles processed, " + Math.Round((mapTileCounter / totalTiles) * 100, 2) + "% complete, " + Math.Round(xCoords.Count() / thisRowSW.Elapsed.TotalSeconds, 2) + " tiles per second."); }//); progressTimer.Stop(); Log.WriteLog("Area map tiles drawn in " + progressTimer.Elapsed.ToString() + ", averaged " + Math.Round(mapTileCounter / progressTimer.Elapsed.TotalSeconds, 2) + " tiles per second."); }
/// <summary> /// Auto-generate some places to be used as gameplay elements in otherwise sparse areas. Given an 8 digit PlusCode, creates and warps some standard shapes in the Cell. /// </summary> /// <param name="plusCode">The area to generate shape(s) in</param> /// <param name="autoSave">If true, saves the areas to the database immediately.</param> /// <returns>The list of places created for the given area.</returns> public static List <DbTables.Place> CreateInterestingPlaces(string plusCode, bool autoSave = true) { //expected to receive a Cell8 // populate it with some interesting regions for players. Random r = new Random(); CodeArea cell8 = OpenLocationCode.DecodeValid(plusCode); //Reminder: area is .0025 degrees on a Cell8 int shapeCount = 1; // 2; //number of shapes to apply to the Cell8 double shapeWarp = .3; //percentage a shape is allowed to have each vertexs drift by. List <DbTables.Place> areasToAdd = new List <DbTables.Place>(); for (int i = 0; i < shapeCount; i++) { //Pick a shape var masterShape = possibleShapes.OrderBy(s => r.Next()).First(); var shapeToAdd = masterShape.Select(s => new Coordinate(s)).ToList(); var scaleFactor = r.Next(10, 36) * .01; //Math.Clamp(r.Next, .1, .35); //Ensure that we get a value that isn't terribly useless. 2 shapes can still cover 70%+ of an empty area this way. var positionFactorX = r.NextDouble() * resolutionCell8; var positionFactorY = r.NextDouble() * resolutionCell8; foreach (Coordinate c in shapeToAdd) { //scale it to our resolution c.X *= resolutionCell8; c.Y *= resolutionCell8; //multiply this by some factor smaller than 1, so it doesn't take up the entire Cell //If we use NextDouble() here, it scales each coordinate randomly, which would look very unpredictable. Use the results of one call twice to scale proportionally. //but ponder how good/bad it looks for various shapes if each coordinate is scaled differently. c.X *= scaleFactor; c.Y *= scaleFactor; //Rotate the coordinate set some random number of degrees? //TODO: how to rotate these? //Place the shape somewhere randomly by adding the same X/Y value less than the resolution to each point c.X += positionFactorX; c.Y += positionFactorY; //Fuzz each vertex by adding some random distance on each axis less than 30% of the cell's size in either direction. //10% makes the shapes much more recognizable, but not as interesting. Will continue looking into parameters here to help adjust that. c.X += (r.NextDouble() * resolutionCell8 * shapeWarp) * (r.Next() % 2 == 0 ? 1 : -1); c.Y += (r.NextDouble() * resolutionCell8 * shapeWarp) * (r.Next() % 2 == 0 ? 1 : -1); //Let us know if this shape overlaps a neighboring cell. We probably want to make sure we re-draw map tiles if it does. if (c.X > .0025 || c.Y > .0025) { Log.WriteLog("Coordinate for shape " + i + " in Cell8 " + plusCode + " will be out of bounds: " + c.X + " " + c.Y, Log.VerbosityLevels.High); } //And now add the minimum values for the given Cell8 to finish up coordinates. c.X += cell8.Min.Longitude; c.Y += cell8.Min.Latitude; } //ShapeToAdd now has a randomized layout, convert it to a polygon. shapeToAdd.Add(shapeToAdd.First()); //make it a closed shape var polygon = factory.CreatePolygon(shapeToAdd.ToArray()); polygon = CCWCheck(polygon); //Sometimes squares still aren't CCW? or this gets un-done somewhere later? if (!polygon.IsValid || !polygon.Shell.IsCCW) { Log.WriteLog("Invalid geometry generated, retrying", Log.VerbosityLevels.High); i--; continue; } if (!polygon.CoveredBy(Converters.GeoAreaToPolygon(cell8))) { //This should only ever require checking the map tile north/east of the current one, even though the vertex fuzzing can potentially move things negative slightly. Log.WriteLog("This polygon is outside of the Cell8 by " + (cell8.Max.Latitude - shapeToAdd.Max(s => s.Y)) + "/" + (cell8.Max.Longitude - shapeToAdd.Max(s => s.X)), Log.VerbosityLevels.High); } if (polygon != null) { DbTables.Place gmd = new DbTables.Place(); gmd.ElementGeometry = polygon; gmd.GameElementName = "generated"; gmd.Tags.Add(new PlaceTags() { Key = "praxisGenerated", Value = "true" }); areasToAdd.Add(gmd); //this is the line that makes some objects occasionally not be CCW that were CCW before. Maybe its the cast to the generic Geometry item? } else { //Inform me that I did something wrong. Log.WriteLog("failed to convert a randomized shape to a polygon.", Log.VerbosityLevels.Errors); continue; } } //Making this function self-contained if (autoSave) { var db = new PraxisContext(); foreach (var area in areasToAdd) { area.ElementGeometry = CCWCheck((Polygon)area.ElementGeometry); //fixes errors that reappeared above } db.Places.AddRange(areasToAdd); db.SaveChanges(); } return(areasToAdd); }