✨ Building duartion
This commit is contained in:
		| @@ -1,4 +1,19 @@ | ||||
| { | ||||
|   "wall": "res://Scenes/Tiles/WallTile.tscn", | ||||
|   "miner": "res://Scenes/Tiles/MinerTile.tscn" | ||||
|   "wall": { | ||||
| 	"scene": "res://Scenes/Tiles/WallTile.tscn", | ||||
| 	"cost": { | ||||
| 	  "stone": 5 | ||||
| 	}, | ||||
| 	"durability": 100, | ||||
| 	"buildTime": 1.5 | ||||
|   }, | ||||
|   "miner": { | ||||
| 	"scene": "res://Scenes/Tiles/MinerTile.tscn", | ||||
| 	"cost": { | ||||
| 	  "wood": 10, | ||||
| 	  "stone": 3 | ||||
| 	}, | ||||
| 	"durability": 200, | ||||
| 	"buildTime": 3.0 | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -15,3 +15,9 @@ texture = ExtResource("2_mecoy") | ||||
|  | ||||
| [node name="CollisionShape2D" type="CollisionShape2D" parent="."] | ||||
| shape = SubResource("RectangleShape2D_8o613") | ||||
|  | ||||
| [node name="ProgressOverlay" type="ColorRect" parent="."] | ||||
| offset_left = -27.0 | ||||
| offset_top = -27.0 | ||||
| offset_right = 27.0 | ||||
| offset_bottom = 27.0 | ||||
|   | ||||
| @@ -15,3 +15,9 @@ texture = ExtResource("1_8o613") | ||||
|  | ||||
| [node name="CollisionShape2D" type="CollisionShape2D" parent="."] | ||||
| shape = SubResource("RectangleShape2D_8o613") | ||||
|  | ||||
| [node name="ProgressOverlay" type="ColorRect" parent="."] | ||||
| offset_left = -27.0 | ||||
| offset_top = -27.0 | ||||
| offset_right = 27.0 | ||||
| offset_bottom = 27.0 | ||||
|   | ||||
| @@ -5,7 +5,14 @@ namespace AceFieldNewHorizon.Scripts.System; | ||||
|  | ||||
| public partial class BuildingRegistry : Node | ||||
| { | ||||
| 	private Dictionary<string, PackedScene> _registry = new(); | ||||
| 	public record BuildingData( | ||||
| 		PackedScene Scene, | ||||
| 		Dictionary<string, int> Cost, | ||||
| 		int Durability, | ||||
| 		float BuildTime | ||||
| 	); | ||||
|  | ||||
| 	private Dictionary<string, BuildingData> _registry = new(); | ||||
|  | ||||
| 	[Export] public string JsonPath { get; set; } = "res://Data/Buildings.json"; | ||||
|  | ||||
| @@ -38,26 +45,87 @@ public partial class BuildingRegistry : Node | ||||
|  | ||||
| 		foreach (string key in dict.Keys) | ||||
| 		{ | ||||
| 			var scenePath = dict[key].AsString(); | ||||
| 			var scene = GD.Load<PackedScene>(scenePath); | ||||
| 			// Each entry is a Dictionary with keys: "scene", "cost", "durability", "buildTime" | ||||
| 			var buildingDict = dict[key].AsGodotDictionary(); | ||||
|  | ||||
| 			if (scene != null) | ||||
| 			// Parse scene | ||||
| 			var scenePath = buildingDict.ContainsKey("scene") ? buildingDict["scene"].AsString() : null; | ||||
| 			if (string.IsNullOrEmpty(scenePath)) | ||||
| 			{ | ||||
| 				_registry[key] = scene; | ||||
| 				GD.Print($"[BuildingRegistry] Loaded building '{key}' from {scenePath}"); | ||||
| 				GD.PrintErr($"[BuildingRegistry] No scene path for '{key}'"); | ||||
| 				continue; | ||||
| 			} | ||||
| 			else | ||||
|  | ||||
| 			var scene = GD.Load<PackedScene>(scenePath); | ||||
| 			if (scene == null) | ||||
| 			{ | ||||
| 				GD.PrintErr($"[BuildingRegistry] Failed to load scene for '{key}' at {scenePath}"); | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			// Parse cost | ||||
| 			Dictionary<string, int> cost = new(); | ||||
| 			if (buildingDict.TryGetValue("cost", out var value)) | ||||
| 			{ | ||||
| 				var costDict = value.AsGodotDictionary(); | ||||
| 				foreach (string mat in costDict.Keys) | ||||
| 				{ | ||||
| 					int val; | ||||
| 					var obj = costDict[mat]; | ||||
| 					if (obj.VariantType == Variant.Type.PackedInt64Array) | ||||
| 						val = (int)obj.AsInt64(); | ||||
| 					else if (obj.VariantType == Variant.Type.PackedInt32Array) | ||||
| 						val = obj.AsInt32(); | ||||
| 					else | ||||
| 						int.TryParse(obj.ToString(), out val); | ||||
| 					cost[mat] = val; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Parse durability | ||||
| 			var durability = 0; | ||||
| 			if (buildingDict.TryGetValue("durability", out var dObj)) | ||||
| 			{ | ||||
| 				if (dObj.VariantType == Variant.Type.PackedInt64Array) | ||||
| 					durability = (int)dObj.AsInt64(); | ||||
| 				else if (dObj.VariantType == Variant.Type.PackedInt32Array) | ||||
| 					durability = dObj.AsInt32(); | ||||
| 				else | ||||
| 					int.TryParse(dObj.ToString(), out durability); | ||||
| 			} | ||||
|  | ||||
| 			// Parse buildTime | ||||
| 			var buildTime = 0f; | ||||
| 			if (buildingDict.TryGetValue("buildTime", out var bObj)) | ||||
| 			{ | ||||
| 				switch (bObj.VariantType) | ||||
| 				{ | ||||
| 					case Variant.Type.PackedFloat32Array or Variant.Type.PackedFloat64Array: | ||||
| 						buildTime = (float)bObj.AsDouble(); | ||||
| 						break; | ||||
| 					case Variant.Type.PackedInt64Array: | ||||
| 						buildTime = bObj.AsInt64(); | ||||
| 						break; | ||||
| 					case Variant.Type.PackedInt32Array: | ||||
| 						buildTime = bObj.AsInt32(); | ||||
| 						break; | ||||
| 					default: | ||||
| 						float.TryParse(bObj.ToString(), out buildTime); | ||||
| 						break; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			var buildingData = new BuildingData(scene, cost, durability, buildTime); | ||||
| 			_registry[key] = buildingData; | ||||
| 			GD.Print($"[BuildingRegistry] Loaded building '{key}' from {scenePath}"); | ||||
| 		} | ||||
|  | ||||
| 		GD.Print($"[BuildingRegistry] Loaded {_registry.Count} buildings"); | ||||
| 	} | ||||
|  | ||||
| 	public PackedScene GetScene(string id) | ||||
| 	public BuildingData GetBuilding(string id) | ||||
| 	{ | ||||
| 		_registry.TryGetValue(id, out var scene); | ||||
| 		return scene; | ||||
| 		_registry.TryGetValue(id, out var data); | ||||
| 		return data; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ public partial class PlacementManager : Node2D | ||||
|  | ||||
|         if (_ghostBuilding == null) | ||||
|         { | ||||
|             var scene = Registry.GetScene(_currentBuildingId); | ||||
|             var scene = Registry.GetBuilding(_currentBuildingId)?.Scene; | ||||
|             if (scene == null) return; | ||||
|              | ||||
|             _ghostBuilding = (BaseTile)scene.Instantiate(); | ||||
| @@ -61,12 +61,17 @@ public partial class PlacementManager : Node2D | ||||
|         // Left click to place | ||||
|         if (Input.IsActionPressed("build_tile") && canPlace) | ||||
|         { | ||||
|             var scene = Registry.GetScene(_currentBuildingId); | ||||
|             var scene = Registry.GetBuilding(_currentBuildingId)?.Scene; | ||||
|             if (scene == null) return; | ||||
|              | ||||
|             _ghostBuilding.FinalizePlacement(); | ||||
|  | ||||
|             var buildingData = Registry.GetBuilding(_currentBuildingId); | ||||
|             Grid.OccupyCell(_hoveredCell, _ghostBuilding); | ||||
|  | ||||
|             if (buildingData is { BuildTime: > 0f }) | ||||
|                 _ghostBuilding.StartConstruction(buildingData.BuildTime); | ||||
|  | ||||
|             _ghostBuilding = (BaseTile)scene.Instantiate(); | ||||
|             _ghostBuilding.SetGhostMode(true); | ||||
|             AddChild(_ghostBuilding); | ||||
|   | ||||
| @@ -1,45 +1,75 @@ | ||||
| using System.Numerics; | ||||
| using AceFieldNewHorizon.Scripts.System; | ||||
| using Godot; | ||||
| using Vector2 = Godot.Vector2; | ||||
|  | ||||
| namespace AceFieldNewHorizon.Scripts.Tiles; | ||||
|  | ||||
| namespace AceFieldNewHorizon.Scripts.Tiles | ||||
| { | ||||
| public partial class BaseTile : Node2D | ||||
| { | ||||
|     private CollisionShape2D _collisionShape; | ||||
|     private Sprite2D _sprite; | ||||
|     private ColorRect _progressOverlay; | ||||
|  | ||||
|     public override void _Ready() | ||||
|     { | ||||
|         _collisionShape = GetNodeOrNull<CollisionShape2D>("CollisionShape2D"); | ||||
|         _sprite = GetNodeOrNull<Sprite2D>("Sprite2D"); | ||||
|         _progressOverlay = GetNodeOrNull<ColorRect>("ProgressOverlay"); | ||||
|         if (_progressOverlay != null) | ||||
|             _progressOverlay.Visible = false; | ||||
|     } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Switch between ghost and placed mode. | ||||
|         /// </summary> | ||||
|         /// <param name="canPlace">If in ghost mode, true = green, false = red. If placed, always white.</param> | ||||
|     public void SetGhostMode(bool canPlace) | ||||
|     { | ||||
|         if (_collisionShape != null) | ||||
|                 _collisionShape.Disabled = true; // always disabled in ghost mode | ||||
|             _collisionShape.Disabled = true; | ||||
|  | ||||
|         if (_sprite != null) | ||||
|             { | ||||
|                 // Ghost preview coloring | ||||
|                 _sprite.Modulate = canPlace ? new Color(1, 1, 1, 0.2f) : // white semi-transparent | ||||
|                     new Color(1, 0, 0, 0.2f); // red semi-transparent | ||||
|             } | ||||
|             _sprite.Modulate = canPlace | ||||
|                 ? new Color(0, 1, 0, 0.5f) | ||||
|                 : new Color(1, 0, 0, 0.5f); | ||||
|     } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Call when placement is finalized. | ||||
|         /// </summary> | ||||
|     public void FinalizePlacement() | ||||
|     { | ||||
|         if (_collisionShape != null) | ||||
|             _collisionShape.Disabled = false; | ||||
|  | ||||
|         if (_sprite != null) | ||||
|                 _sprite.Modulate = Colors.White; // reset to normal | ||||
|         } | ||||
|             _sprite.Modulate = Colors.White; | ||||
|     } | ||||
|  | ||||
|     // Building progress visualization | ||||
|     public void StartConstruction(float buildTime) | ||||
|     { | ||||
|         if (_progressOverlay == null || _sprite?.Texture == null) return; | ||||
|  | ||||
|         var texSize = new Vector2(GridUtils.TileSize, GridUtils.TileSize); | ||||
|  | ||||
|         _progressOverlay.Visible = true; | ||||
|         _progressOverlay.Color = new Color(0, 0, 1, 0.4f); // semi-transparent blue | ||||
|  | ||||
|         async void RunProgress() | ||||
|         { | ||||
|             var elapsed = 0f; | ||||
|             while (elapsed < buildTime) | ||||
|             { | ||||
|                 await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); | ||||
|                 elapsed += (float)GetProcessDeltaTime(); | ||||
|  | ||||
|                 var ratio = Mathf.Clamp(elapsed / buildTime, 0, 1); | ||||
|  | ||||
|                 // Fill from bottom to top, aligned with sprite center pivot | ||||
|                 _progressOverlay.Size = new Vector2(texSize.X, texSize.Y * ratio); | ||||
|                 _progressOverlay.Position = new Vector2( | ||||
|                     -texSize.X / 2, // align left edge | ||||
|                     texSize.Y / 2 - _progressOverlay.Size.Y // move upward | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             _progressOverlay.Visible = false; | ||||
|         } | ||||
|  | ||||
|         RunProgress(); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user