|
|
|
@@ -38,7 +38,7 @@ public class LocationStore
|
|
|
|
|
"$jsonSchema", new BsonDocument
|
|
|
|
|
{
|
|
|
|
|
{ "bsonType", "object" },
|
|
|
|
|
{ "required", new BsonArray { "name", "coord", "biomeKey", "createdUtc" } },
|
|
|
|
|
{ "required", new BsonArray { "name", "coord", "biomeKey", "elevation", "createdUtc" } },
|
|
|
|
|
{
|
|
|
|
|
"properties", new BsonDocument
|
|
|
|
|
{
|
|
|
|
@@ -58,6 +58,7 @@ public class LocationStore
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ "biomeKey", new BsonDocument { { "bsonType", "string" } } },
|
|
|
|
|
{ "elevation", new BsonDocument { { "bsonType", "int" } } },
|
|
|
|
|
{
|
|
|
|
|
"resources", new BsonDocument
|
|
|
|
|
{
|
|
|
|
@@ -348,6 +349,7 @@ public class LocationStore
|
|
|
|
|
Name = "Origin",
|
|
|
|
|
Coord = new Coord { X = 0, Y = 0 },
|
|
|
|
|
BiomeKey = originBiomeKey,
|
|
|
|
|
Elevation = 0,
|
|
|
|
|
LocationObject = CreateLocationObjectForBiome(biomeDefinitions, originBiomeKey, 0, 0),
|
|
|
|
|
LocationObjectResolved = true,
|
|
|
|
|
CreatedUtc = DateTime.UtcNow
|
|
|
|
@@ -423,6 +425,7 @@ public class LocationStore
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var biomeKey = await DetermineBiomeKeyAsync(x, y, biomeDefinitions);
|
|
|
|
|
var elevation = await DetermineElevationAsync(x, y, biomeKey);
|
|
|
|
|
var locationObject = CreateLocationObjectForBiome(biomeDefinitions, biomeKey, x, y);
|
|
|
|
|
BsonValue locationObjectValue = locationObject is null ? BsonNull.Value : locationObject.ToBsonDocument();
|
|
|
|
|
var update = Builders<BsonDocument>.Update
|
|
|
|
@@ -430,6 +433,7 @@ public class LocationStore
|
|
|
|
|
.SetOnInsert("name", DefaultLocationName(x, y))
|
|
|
|
|
.SetOnInsert("coord", new BsonDocument { { "x", x }, { "y", y } })
|
|
|
|
|
.SetOnInsert("biomeKey", biomeKey)
|
|
|
|
|
.SetOnInsert("elevation", elevation)
|
|
|
|
|
.SetOnInsert("locationObject", locationObjectValue)
|
|
|
|
|
.SetOnInsert("locationObjectResolved", true)
|
|
|
|
|
.SetOnInsert("createdUtc", DateTime.UtcNow);
|
|
|
|
@@ -447,13 +451,16 @@ public class LocationStore
|
|
|
|
|
|
|
|
|
|
private async Task<Location> EnsureLocationMetadataAsync(Location location)
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(location.BiomeKey) && location.LocationObjectResolved)
|
|
|
|
|
var locationId = location.Id ?? string.Empty;
|
|
|
|
|
var hasElevation = !string.IsNullOrWhiteSpace(locationId) && await HasStoredElevationAsync(locationId);
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(location.BiomeKey) && location.LocationObjectResolved && hasElevation)
|
|
|
|
|
return location;
|
|
|
|
|
|
|
|
|
|
var biomeDefinitions = await LoadBiomeDefinitionsAsync();
|
|
|
|
|
var biomeKey = location.BiomeKey;
|
|
|
|
|
if (string.IsNullOrWhiteSpace(biomeKey))
|
|
|
|
|
biomeKey = await DetermineBiomeKeyAsync(location.Coord.X, location.Coord.Y, biomeDefinitions);
|
|
|
|
|
var elevation = hasElevation ? location.Elevation : await DetermineElevationAsync(location.Coord.X, location.Coord.Y, biomeKey);
|
|
|
|
|
|
|
|
|
|
var migratedObject = TryMigrateLegacyResources(location) ?? CreateLocationObjectForBiome(biomeDefinitions, biomeKey, location.Coord.X, location.Coord.Y);
|
|
|
|
|
var filter = Builders<Location>.Filter.And(
|
|
|
|
@@ -461,10 +468,12 @@ public class LocationStore
|
|
|
|
|
);
|
|
|
|
|
var update = Builders<Location>.Update
|
|
|
|
|
.Set(l => l.BiomeKey, biomeKey)
|
|
|
|
|
.Set(l => l.Elevation, elevation)
|
|
|
|
|
.Set(l => l.LocationObjectResolved, true)
|
|
|
|
|
.Set(l => l.LocationObject, migratedObject);
|
|
|
|
|
await _col.UpdateOneAsync(filter, update);
|
|
|
|
|
location.BiomeKey = biomeKey;
|
|
|
|
|
location.Elevation = elevation;
|
|
|
|
|
location.LocationObject = migratedObject;
|
|
|
|
|
location.LocationObjectResolved = true;
|
|
|
|
|
return location;
|
|
|
|
@@ -478,6 +487,7 @@ public class LocationStore
|
|
|
|
|
Name = location.Name,
|
|
|
|
|
Coord = new Coord { X = location.Coord.X, Y = location.Coord.Y },
|
|
|
|
|
BiomeKey = location.BiomeKey,
|
|
|
|
|
Elevation = location.Elevation,
|
|
|
|
|
LocationObject = MapVisibleLocationObject(location.LocationObject)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
@@ -571,6 +581,23 @@ public class LocationStore
|
|
|
|
|
return bestBiome;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<int> DetermineElevationAsync(int x, int y, string biomeKey)
|
|
|
|
|
{
|
|
|
|
|
if (x == 0 && y == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
var baseElevation = DetermineBaseElevation(x, y, biomeKey);
|
|
|
|
|
var neighbors = await LoadNeighborElevationsAsync(x, y);
|
|
|
|
|
if (neighbors.Count == 0)
|
|
|
|
|
return baseElevation;
|
|
|
|
|
|
|
|
|
|
var averageNeighbor = (int)Math.Round(neighbors.Average());
|
|
|
|
|
var blended = (int)Math.Round((baseElevation + averageNeighbor) / 2.0);
|
|
|
|
|
var minNeighbor = neighbors.Min();
|
|
|
|
|
var maxNeighbor = neighbors.Max();
|
|
|
|
|
return Math.Clamp(blended, minNeighbor - 1, maxNeighbor + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static LocationObject? CreateLocationObjectForBiome(IReadOnlyList<BiomeDefinition> biomeDefinitions, string biomeKey, int x, int y)
|
|
|
|
|
{
|
|
|
|
|
var biome = biomeDefinitions.FirstOrDefault(definition => definition.BiomeKey == biomeKey)
|
|
|
|
@@ -655,6 +682,32 @@ public class LocationStore
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<int>> LoadNeighborElevationsAsync(int x, int y)
|
|
|
|
|
{
|
|
|
|
|
var coords = new[] { (x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1) };
|
|
|
|
|
var filters = coords.Select(coord =>
|
|
|
|
|
Builders<BsonDocument>.Filter.And(
|
|
|
|
|
Builders<BsonDocument>.Filter.Eq("coord.x", coord.Item1),
|
|
|
|
|
Builders<BsonDocument>.Filter.Eq("coord.y", coord.Item2)))
|
|
|
|
|
.ToList();
|
|
|
|
|
var filter = Builders<BsonDocument>.Filter.Or(filters);
|
|
|
|
|
var neighbors = await _rawCol.Find(filter).ToListAsync();
|
|
|
|
|
|
|
|
|
|
return neighbors
|
|
|
|
|
.Where(doc => doc.Contains("elevation") && doc["elevation"].IsInt32)
|
|
|
|
|
.Select(doc => doc["elevation"].AsInt32)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<bool> HasStoredElevationAsync(string locationId)
|
|
|
|
|
{
|
|
|
|
|
var filter = Builders<BsonDocument>.Filter.And(
|
|
|
|
|
Builders<BsonDocument>.Filter.Eq("_id", ObjectId.Parse(locationId)),
|
|
|
|
|
Builders<BsonDocument>.Filter.Exists("elevation", true)
|
|
|
|
|
);
|
|
|
|
|
return await _rawCol.Find(filter).AnyAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<BiomeDefinition> LoadBiomeDefinitions()
|
|
|
|
|
{
|
|
|
|
|
return _biomeDefinitions.Find(Builders<BiomeDefinition>.Filter.Empty)
|
|
|
|
@@ -689,6 +742,22 @@ public class LocationStore
|
|
|
|
|
return "plains";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int DetermineBaseElevation(int x, int y, string biomeKey)
|
|
|
|
|
{
|
|
|
|
|
var macro = StableNoise(x, y, 404);
|
|
|
|
|
var micro = StableNoise(x, y, 505);
|
|
|
|
|
|
|
|
|
|
return biomeKey switch
|
|
|
|
|
{
|
|
|
|
|
"wetlands" => (int)Math.Round((macro * 2.0) - 1.0),
|
|
|
|
|
"plains" => (int)Math.Round((macro * 3.0) - 1.0),
|
|
|
|
|
"forest" => (int)Math.Round((macro * 4.0) - 1.5),
|
|
|
|
|
"desert" => (int)Math.Round((macro * 3.0) - 1.0 + ((micro - 0.5) * 0.75)),
|
|
|
|
|
"rocky" => (int)Math.Round((macro * 5.0) - 1.0 + ((micro - 0.5) * 1.25)),
|
|
|
|
|
_ => (int)Math.Round((macro * 3.0) - 1.0)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static double StableNoise(int x, int y, int salt)
|
|
|
|
|
{
|
|
|
|
|
var value = Math.Sin((x * 12.9898) + (y * 78.233) + ((1729 + salt) * 0.1597)) * 43758.5453;
|
|
|
|
|