From f90de8a98ba04e61c96210675eb67b9192200063 Mon Sep 17 00:00:00 2001 From: Zeeshaun Date: Sat, 21 Mar 2026 13:09:41 -0500 Subject: [PATCH] Adding elevation --- game/scenes/Levels/location_level.gd | 41 ++++++++--- .../CharacterApi/Models/VisibleLocation.cs | 3 + microservices/LocationsApi/Models/Location.cs | 3 + .../Models/VisibleLocationResponse.cs | 2 + .../LocationsApi/Services/LocationStore.cs | 73 ++++++++++++++++++- 5 files changed, 108 insertions(+), 14 deletions(-) diff --git a/game/scenes/Levels/location_level.gd b/game/scenes/Levels/location_level.gd index ea56e22..ef90550 100644 --- a/game/scenes/Levels/location_level.gd +++ b/game/scenes/Levels/location_level.gd @@ -8,8 +8,9 @@ const SETTINGS_SCENE := "res://scenes/UI/Settings.tscn" const CHARACTER_SLOT_COUNT := 6 @export var tile_size := 8.0 -@export var block_height := 1.0 -@export_range(1, 8, 1) var tile_radius := 3 +@export var block_height := 1.0 +@export var elevation_step_height := 0.5 +@export_range(1, 8, 1) var tile_radius := 3 @export var tracked_node_path: NodePath @export var player_spawn_height := 2.0 @export var border_color: Color = Color(0.05, 0.05, 0.05, 1.0) @@ -129,14 +130,14 @@ func _world_to_coord(world_pos: Vector3) -> Vector2i: ) -func _coord_to_world(coord: Vector2i) -> Vector3: - return Vector3(coord.x * tile_size, block_height * 0.5, coord.y * tile_size) - - +func _coord_to_world(coord: Vector2i) -> Vector3: + return Vector3(coord.x * tile_size, _get_tile_center_y(coord), coord.y * tile_size) + + func _move_player_to_coord(coord: Vector2i) -> void: if _player == null: return - _player.global_position = Vector3(coord.x * tile_size, player_spawn_height, coord.y * tile_size) + _player.global_position = Vector3(coord.x * tile_size, _get_tile_surface_y(coord) + player_spawn_height, coord.y * tile_size) _player.linear_velocity = Vector3.ZERO _player.angular_velocity = Vector3.ZERO @@ -302,11 +303,12 @@ func _spawn_tile(coord: Vector2i, location_data: Dictionary) -> void: _tile_nodes[coord] = tile_root -func _update_tile(coord: Vector2i, location_data: Dictionary) -> void: - var tile_root := _tile_nodes.get(coord) as Node3D - if tile_root == null: - return - +func _update_tile(coord: Vector2i, location_data: Dictionary) -> void: + var tile_root := _tile_nodes.get(coord) as Node3D + if tile_root == null: + return + tile_root.position = _coord_to_world(coord) + if show_tile_labels: var label := tile_root.get_node_or_null("LocationNameLabel") as Label3D if label: @@ -480,6 +482,19 @@ func _build_floor_inventory_label(floor_items: Array) -> String: return label +func _get_tile_elevation(coord: Vector2i) -> int: + var location_data := _get_location_data(coord) + return int(location_data.get("elevation", 0)) + + +func _get_tile_center_y(coord: Vector2i) -> float: + return (_get_tile_elevation(coord) * elevation_step_height) + (block_height * 0.5) + + +func _get_tile_surface_y(coord: Vector2i) -> float: + return (_get_tile_elevation(coord) * elevation_step_height) + block_height + + func _update_inventory_location_label() -> void: if _inventory_location_label == null: return @@ -812,6 +827,7 @@ func _ensure_selected_location_exists(coord: Vector2i) -> void: "id": "", "name": _selected_location_name(coord), "biomeKey": "plains", + "elevation": 0, "locationObject": {}, "floorItems": [] } @@ -889,6 +905,7 @@ func _load_existing_locations() -> void: "id": String(location.get("id", "")).strip_edges(), "name": location_name, "biomeKey": String(location.get("biomeKey", "plains")).strip_edges(), + "elevation": int(location.get("elevation", 0)), "locationObject": _parse_location_object(location.get("locationObject", {})), "floorItems": _parse_floor_inventory_items(location.get("floorItems", [])) } diff --git a/microservices/CharacterApi/Models/VisibleLocation.cs b/microservices/CharacterApi/Models/VisibleLocation.cs index c7f250b..d4ee262 100644 --- a/microservices/CharacterApi/Models/VisibleLocation.cs +++ b/microservices/CharacterApi/Models/VisibleLocation.cs @@ -19,6 +19,9 @@ public class VisibleLocation [BsonElement("biomeKey")] public string BiomeKey { get; set; } = "plains"; + [BsonElement("elevation")] + public int Elevation { get; set; } + [BsonElement("locationObject")] public VisibleLocationObject? LocationObject { get; set; } diff --git a/microservices/LocationsApi/Models/Location.cs b/microservices/LocationsApi/Models/Location.cs index dc4778e..ecd1067 100644 --- a/microservices/LocationsApi/Models/Location.cs +++ b/microservices/LocationsApi/Models/Location.cs @@ -18,6 +18,9 @@ public class Location [BsonElement("biomeKey")] public string BiomeKey { get; set; } = "plains"; + [BsonElement("elevation")] + public int Elevation { get; set; } + [BsonElement("resources")] public List Resources { get; set; } = []; diff --git a/microservices/LocationsApi/Models/VisibleLocationResponse.cs b/microservices/LocationsApi/Models/VisibleLocationResponse.cs index 0a0fbde..9a41d20 100644 --- a/microservices/LocationsApi/Models/VisibleLocationResponse.cs +++ b/microservices/LocationsApi/Models/VisibleLocationResponse.cs @@ -10,6 +10,8 @@ public class VisibleLocationResponse public string BiomeKey { get; set; } = "plains"; + public int Elevation { get; set; } + public VisibleLocationObjectResponse? LocationObject { get; set; } public List FloorItems { get; set; } = []; diff --git a/microservices/LocationsApi/Services/LocationStore.cs b/microservices/LocationsApi/Services/LocationStore.cs index 04f5449..ffd0362 100644 --- a/microservices/LocationsApi/Services/LocationStore.cs +++ b/microservices/LocationsApi/Services/LocationStore.cs @@ -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.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 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.Filter.And( @@ -461,10 +468,12 @@ public class LocationStore ); var update = Builders.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 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 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> 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.Filter.And( + Builders.Filter.Eq("coord.x", coord.Item1), + Builders.Filter.Eq("coord.y", coord.Item2))) + .ToList(); + var filter = Builders.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 HasStoredElevationAsync(string locationId) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("_id", ObjectId.Parse(locationId)), + Builders.Filter.Exists("elevation", true) + ); + return await _rawCol.Find(filter).AnyAsync(); + } + private List LoadBiomeDefinitions() { return _biomeDefinitions.Find(Builders.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;