using System; using System.Collections.Generic; using AceFieldNewHorizon.Scripts.Tiles; using Godot; using System.Linq; namespace AceFieldNewHorizon.Scripts.System; public partial class PlacementManager : Node2D { [Export] public GridManager Grid { get; set; } [Export] public BuildingRegistry Registry { get; set; } private static readonly List BuildableTiles = ["wall", "miner"]; private string _currentBuildingId = "wall"; private Vector2I _hoveredCell; private BaseTile _ghostBuilding; private float _currentRotation; private Vector2I _currentBuildingSize = Vector2I.One; public void SetCurrentBuilding(string buildingId) { _currentBuildingId = buildingId; var buildingData = Registry.GetBuilding(buildingId); if (buildingData != null) { _currentBuildingSize = buildingData.Size; // Reset rotation to nearest allowed rotation if (!buildingData.IsRotationAllowed(_currentRotation)) { _currentRotation = (int)buildingData.AllowedRotations[0] * 90f; } } // Replace ghost immediately if (_ghostBuilding != null) { _ghostBuilding.QueueFree(); _ghostBuilding = null; } } private void RotateGhost(bool reverse = false) { if (_ghostBuilding == null) return; var buildingData = Registry.GetBuilding(_currentBuildingId); if (buildingData == null) return; // Calculate next rotation var currentDirection = (RotationDirection)((Mathf.RoundToInt(_currentRotation / 90f) % 4 + 4) % 4); var currentIndex = Array.IndexOf(buildingData.AllowedRotations.ToArray(), currentDirection); if (reverse) currentIndex = (currentIndex - 1 + buildingData.AllowedRotations.Length) % buildingData.AllowedRotations.Length; else currentIndex = (currentIndex + 1) % buildingData.AllowedRotations.Length; _currentRotation = (int)buildingData.AllowedRotations[currentIndex] * 90f; _ghostBuilding.RotationDegrees = _currentRotation; // Update ghost position to keep the same cell under cursor UpdateGhostPosition(); } private void UpdateGhostPosition() { if (_ghostBuilding == null) return; var buildingData = Registry.GetBuilding(_currentBuildingId); if (buildingData == null) return; var rotatedSize = buildingData.GetRotatedSize(_currentRotation); var offset = GridUtils.GetCenterOffset(rotatedSize, _currentRotation); _ghostBuilding.Position = GridUtils.GridToWorld(_hoveredCell) + offset; // Update occupied cells GridUtils.GetOccupiedCells(_hoveredCell, rotatedSize, _currentRotation); } public override void _Process(double delta) { // Snap mouse to grid var mousePos = GetGlobalMousePosition(); var newHoveredCell = GridUtils.WorldToGrid(mousePos); // Only update if cell changed if (newHoveredCell != _hoveredCell) { _hoveredCell = newHoveredCell; UpdateGhostPosition(); } if (Input.IsActionJustPressed("rotate_tile_reverse")) RotateGhost(reverse: true); else if (Input.IsActionJustPressed("rotate_tile")) RotateGhost(); if (_ghostBuilding == null) { var building = Registry.GetBuilding(_currentBuildingId); if (building == null) return; var scene = building.Scene; _ghostBuilding = (BaseTile)scene.Instantiate(); _ghostBuilding.SetGhostMode(true); _ghostBuilding.RotationDegrees = _currentRotation; _ghostBuilding.ZAsRelative = false; _ghostBuilding.ZIndex = (int)building.Layer; UpdateGhostPosition(); AddChild(_ghostBuilding); } var canPlace = CanPlaceBuilding(); _ghostBuilding.SetGhostMode(canPlace); // Left click to place if (Input.IsActionPressed("build_tile") && canPlace) { var building = Registry.GetBuilding(_currentBuildingId); if (building == null) return; var scene = building.Scene; var buildingInstance = (BaseTile)scene.Instantiate(); buildingInstance.RotationDegrees = _currentRotation; buildingInstance.ZIndex = (int)building.Layer; buildingInstance.Position = _ghostBuilding.Position; AddChild(buildingInstance); Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer); if (building.BuildTime > 0f) buildingInstance.StartConstruction(building.BuildTime); } if (Input.IsActionPressed("destroy_tile") && !Grid.IsAreaFree(_hoveredCell, Vector2I.One, 0f)) { // Right click to destroy from current layer var building = Grid.GetBuildingAtCell(_hoveredCell); if (building == null) return; // Find all cells occupied by this building var buildingInfo = Grid.GetBuildingInfoAtCell(_hoveredCell, GridLayer.Building); if (buildingInfo == null) return; building.QueueFree(); Grid.FreeArea(buildingInfo.Value.Position, buildingInfo.Value.Size, buildingInfo.Value.Rotation); } if (Input.IsActionJustPressed("switch_tile")) { var currentIdx = BuildableTiles.IndexOf(_currentBuildingId); var nextIdx = (currentIdx + 1) % BuildableTiles.Count; SetCurrentBuilding(BuildableTiles[nextIdx]); } } private bool CanPlaceBuilding() { var buildingData = Registry.GetBuilding(_currentBuildingId); if (buildingData == null) return false; // Check if rotation is allowed if (!buildingData.IsRotationAllowed(_currentRotation)) return false; // Check if area is free var rotatedSize = buildingData.GetRotatedSize(_currentRotation); return !Grid.IsAreaOccupied(_hoveredCell, rotatedSize, _currentRotation, GetBlockingLayers(buildingData.Layer)); } private GridLayer[] GetBlockingLayers(GridLayer layer) { return layer switch { GridLayer.Ground => [GridLayer.Ground], GridLayer.Building => [GridLayer.Building], GridLayer.Decoration => [GridLayer.Decoration], _ => [] }; } } public static class GridManagerExtensions { public static (Vector2I Position, Vector2I Size, float Rotation)? GetBuildingInfoAtCell(this GridManager grid, Vector2I cell, GridLayer layer) { if (grid.GetBuildingAtCell(cell, layer) is { } building) { // Find the top-left position of the building for (int x = 0; x < 100; x++) // Arbitrary max size { for (int y = 0; y < 100; y++) { var checkCell = new Vector2I(cell.X - x, cell.Y - y); if (grid.GetBuildingAtCell(checkCell, layer) == building) { // Found the top-left corner, now find the size var size = Vector2I.One; // Search right while (grid.GetBuildingAtCell(new Vector2I(checkCell.X + size.X, checkCell.Y), layer) == building) size.X++; // Search down while (grid.GetBuildingAtCell(new Vector2I(checkCell.X, checkCell.Y + size.Y), layer) == building) size.Y++; // Get rotation from the first cell var rotation = 0f; // You'll need to store rotation in GridManager to make this work return (checkCell, size, rotation); } } } } return null; } }