using System.Collections; using System.Collections.Generic; using UnityEngine; using KairoEngine.Utility; using KairoEngine.Core; using KairoEngine.UI; using KairoEngine.Stockpiles; using Utils = KairoEngine.Utility.Utilities; using Sirenix.OdinInspector; namespace KairoEngine.Grids { [HideMonoScript] public class GridBuildingSystem : MonoBehaviour { public static GridBuildingSystem Instance; public Vector2Int gridSize = new Vector2Int(8,8); public float cellSize = 10f; public Vector3 gridPosition = new Vector3(); public bool debug = false; public string stockpileOwner = "Player"; public bool enableBuildingPlacement = true; public bool enableBuildingDestruction = false; [SerializeField] private Grid grid; //[OnInspectorInit("CreateGrid")] public List placeObjectTypeList; private PlacedObjectType placedObjectType; private PlacedObjectType.Dir dir = PlacedObjectType.Dir.Down; public List placedObjects = new List(); public LayerMask layerMask; public static bool buildingToolAvailable { get; private set; } public static bool buildingDestructionToolAvailable { get; private set; } void Awake() { CreateGrid(); placedObjectType = placeObjectTypeList[0]; if(Instance == null) Instance = this; else Destroy(gameObject); } void Update() { Vector3 worldPos = Utils.GetMouseWorldPosition(layerMask.value); if(!MouseInputUIBlocker.BlockedByUI && grid.IsPositionInsideGrid(worldPos)) { if(enableBuildingPlacement) { buildingToolAvailable = true; if(Input.GetKeyDown(KeyCode.R)) RotatePlacebableObject(); if(Input.GetMouseButtonDown(0)) PlaceObjectInGrid(worldPos); } else buildingToolAvailable = false; if(enableBuildingDestruction) { buildingDestructionToolAvailable = true; if(Input.GetMouseButtonDown(0)) RemoveObjectFromGrid(worldPos); } else buildingDestructionToolAvailable = false; } } public bool IsGridCreated() => grid != null; public void CreateGrid() { if(grid == null) { grid = new Grid(gridSize.x, gridSize.y, cellSize, gridPosition, (Grid g, int x, int y) => new GridObject(g, x, 0, y), GridPlane.XZ, debug); } } public void ClearGrid() => grid = null; private void DeselectObjectType() { placedObjectType = null; RefreshSelectedObjectType(); } private void RefreshSelectedObjectType() => GenericEvents.Trigger("OnSelectedChanged"); public Vector2Int GetGridPosition(Vector3 worldPosition) { if(grid == null) return new Vector2Int(); grid.GetGridPosition(worldPosition, out int x, out int z); return new Vector2Int(x, z); } public Vector3 GetPositionFromGrid(int x, int y) => grid != null ? grid.GetWorldPosition(x, y) : new Vector3(); public Vector3 GetGridPositionOffset() => gridPosition - transform.position; public PlacedObjectType.Dir GetDir() => dir; public Vector3 GetCellSize() { if(grid == null) return new Vector3(cellSize, cellSize, cellSize); switch(grid.GetGridPlane()) { default: case GridPlane.XY: return new Vector3(cellSize, cellSize, 0); case GridPlane.XZ: return new Vector3(cellSize, 0, cellSize); case GridPlane.YZ: return new Vector3(0, cellSize, cellSize); } } public Vector3 GetMouseWorldSnappedPosition() { Vector3 mousePosition = Utils.GetMouseWorldPosition(layerMask.value); if(grid == null) return new Vector3(); grid.GetGridPosition(mousePosition, out int x, out int z); if (placedObjectType != null) { Vector2Int rotationOffset = placedObjectType.GetRotationOffset(dir); Vector3 placedObjectWorldPosition = grid.GetWorldPosition(x, z) + new Vector3(rotationOffset.x, 0, rotationOffset.y) * grid.GetCellSize(); return placedObjectWorldPosition; } else { return mousePosition; } } public Quaternion GetPlacedObjectRotation() { if (placedObjectType != null) { return Quaternion.Euler(0, placedObjectType.GetRotationAngle(dir), 0); } else { return Quaternion.identity; } } public PlacedObjectType GetPlacedObjectTypeSO() => placedObjectType; public bool CanPlaceObject(int x, int z) => placedObjectType.CanPlaceObject(grid, x, z, dir); public void PlaceObjectInGrid(Vector3 worldPos) { grid.GetGridPosition(Utils.GetMouseWorldPosition(layerMask.value), out int x, out int z); PlaceObjectInGrid(x, z); } public void PlaceObjectInGrid(int x, int z) { PlaceObjectInGrid(x, z, placedObjectType, dir); } public void PlaceObjectInGrid(int x, int z, PlacedObjectType placedObjectType, PlacedObjectType.Dir dir, bool checkResources = true, bool checkPlace = true, bool playSound = true, bool destructable = true) { Vector3 worldPos = grid.GetWorldPosition(x, z); if(CanPlaceObject(x, z) || checkPlace == false) { if(HasResources() || checkResources == false) { Vector2Int rotationOffset = placedObjectType.GetRotationOffset(dir); Vector3 pos = grid.GetWorldPosition(x + rotationOffset.x, z + rotationOffset.y); PlacedObject placedObject = PlacedObject.Create(pos, new Vector2Int(x, z), dir, placedObjectType, playSound); placedObject.destructable = destructable; placedObjects.Add(placedObject); List gridPositionList = placedObjectType.GetGridPositionList(new Vector2Int(x, z), dir); foreach (var gridPosition in gridPositionList) { grid.GetObject(gridPosition.x, gridPosition.y).SetPlacedObject(placedObject); } if(checkResources) { GenericEvents.Trigger("BuildingPlaced", placedObjectType.title); StockpileManager.instance.RemoveResources(stockpileOwner, placedObjectType.cost); for (int i = 0; i < placedObjectType.cost.Count; i++) { var stock = placedObjectType.cost[i]; float delay = 350f * i; Timer.Execute(delay, () => { string resource = stock.stockpileType.title.ToLower(); if(resource == "mineral") resource = "minerals"; string message = $"- {stock.ammount} "; FloatingMessageEvents.Send(message, GetCurrentPlacebleObjectCenter(pos), 5, 1.3f, 20f); }); } } } else { Vector3 finalPos = GetCurrentPlacebleObjectCenter(worldPos); DrawResourceErrorMessage(finalPos); GenericEvents.Trigger("CannotBuildError"); } } else { Vector3 finalPos = new Vector3(worldPos.x, worldPos.y + 10, worldPos.z); DrawRulesetErrorMessage(finalPos, x, z); GenericEvents.Trigger("CannotBuildError"); } } public void RemoveObjectFromGrid(Vector3 worldPos) { GridObject gridObject = grid.GetObject(worldPos); RemoveObjectFromGrid(gridObject); } public void RemoveObjectFromGrid(int x, int z) { GridObject gridObject = grid.GetObject(x, z); RemoveObjectFromGrid(gridObject); } public void RemoveObjectFromGrid(GridObject gridObject) { if(gridObject == null) return; PlacedObject placedObject = gridObject.GetPlacedObject(); if(placedObject != null) { Vector3 worldPos = grid.GetWorldPosition(gridObject.GetPosition().x, gridObject.GetPosition().z); Vector3 finalPos = GetCurrentPlacebleObjectCenter(worldPos); if(!placedObject.placedObjectType.isDestructable) { FloatingMessageEvents.Send("Indestructable!", finalPos, 5, 2f, 20f); return; } if(!placedObject.destructable) { FloatingMessageEvents.Send("Cannot remove initial building", finalPos, 5, 2f, 20f); return; } GenericEvents.Trigger("BuildingRemoved", placedObject.placedObjectType.title, worldPos); placedObjects.Remove(placedObject); placedObject.DestroySelf(); List gridPositionList = placedObject.GetGridPositionList(); foreach (var gridPosition in gridPositionList) { grid.GetObject(gridPosition.x, gridPosition.y).ClearPlacedObject(); } } } public void RotatePlacebableObject() { dir = PlacedObjectType.GetNextDir(dir); Vector3 pos = Utils.GetMouseWorldPosition(layerMask.value); Vector3 finalPos = new Vector3(pos.x, pos.y + 10, pos.z); //Utilities.CreateWorldTextPopup(null, "" + dir, pos, 18, Color.white, finalPos, 1.2f, true); FloatingMessageEvents.Send("" + dir, GetCurrentPlacebleObjectCenter(pos), 5, 1.2f, 20f); } public void ChangeSelectedPlacebleObject(int index) { placedObjectType = placeObjectTypeList[index]; RefreshSelectedObjectType(); } public void ChangeSelectedPlacebleObject(PlacedObjectType newPlacedObjectType) { placedObjectType = newPlacedObjectType; RefreshSelectedObjectType(); } public bool IsPositionInsideGrid(Vector2Int gridPos) { if(grid == null) return false; return grid.IsPositionInsideGrid(gridPos); } public bool IsPositionInsideGrid(Vector3 worldPos) { if(grid == null) return false; return grid.IsPositionInsideGrid(worldPos); } public PlacedObject GetPlacedObjectInGrid(Vector2Int gridPosition) { if(grid == null) return null; GridObject gridObject = grid.GetObject(gridPosition.x, gridPosition.y); if(gridObject == null) return null; PlacedObject placedObject = gridObject.GetPlacedObject(); return placedObject; } public bool HasResources() => StockpileManager.instance.HasResources(stockpileOwner, placedObjectType.cost); public bool HasResource(KairoEngine.Stockpiles.Stockpile stockpile) => StockpileManager.instance.HasResource(stockpileOwner, stockpile); public void RemovePlacedObjects() { for (int i = 0; i < placedObjects.Count; i++) { Destroy(placedObjects[i].gameObject); } placedObjects.Clear(); } public Vector3 GetCurrentPlacebleObjectCenter(Vector3 worldPos) { if(dir == PlacedObjectType.Dir.Down || dir == PlacedObjectType.Dir.Up) { return new Vector3( worldPos.x + (placedObjectType.width * cellSize)/2, worldPos.y + 1, worldPos.z + (placedObjectType.height * cellSize)/2); } else { return new Vector3( worldPos.x + (placedObjectType.height * cellSize)/2, worldPos.y + 1, worldPos.z + (placedObjectType.width * cellSize)/2); } } private void DrawResourceErrorMessage(Vector3 finalPos) { List sprites = new List(); bool missingResources = false; for (int i = 0; i < placedObjectType.cost.Count; i++) { if(!HasResource(placedObjectType.cost[i])) { sprites.Add(placedObjectType.cost[i].stockpileType.spriteText); missingResources = true; } } if(missingResources == false) return; switch (sprites.Count) { case 1: FloatingMessageEvents.Send($"Not enought {sprites[0]}", finalPos, 5, 1.5f, 20f); break; case 2: FloatingMessageEvents.Send($"Not enought {sprites[0]} and {sprites[1]}", finalPos, 5, 1.5f, 20f); break; case 3: FloatingMessageEvents.Send($"Not enought {sprites[0]}, {sprites[1]} and {sprites[2]}", finalPos, 5, 1.5f, 20f); break; case 4: FloatingMessageEvents.Send($"Not enought {sprites[0]}, {sprites[1]}, {sprites[2]} and {sprites[3]}", finalPos, 5, 1.5f, 20f); break; default: FloatingMessageEvents.Send("Not enought resources", finalPos, 5, 2f, 20f); break; } } private void DrawRulesetErrorMessage(Vector3 finalPos, int x, int y) { List brokenRules = placedObjectType.GetRulesetError(grid, x, y, dir); for (int i = 0; i < brokenRules.Count; i++) { string message = ""; switch (brokenRules[i].GetRule()) { case PlaceableObjectRule.Rule.EmptyCell: message = $"Area must be empty!"; break; case PlaceableObjectRule.Rule.Connection: message = $"Invalid connection"; //message += $" of type {brokenRules[i].GetConnector()}!"; break; case PlaceableObjectRule.Rule.CellOcupied: message = $"Must be placed on a {brokenRules[i].GetTargetObject().title}"; break; case PlaceableObjectRule.Rule.AdjacentCellOcupied: message = $"Adjacent cell need to be of type {brokenRules[i].GetTargetObject().title}"; break; case PlaceableObjectRule.Rule.AdjacentEmptyCell: message = $"Adjacent cell must be empty"; break; case PlaceableObjectRule.Rule.CellInRadius: message = $"No {brokenRules[i].GetTargetObject().title} in {brokenRules[i].GetRadius()} radius"; break; case PlaceableObjectRule.Rule.EmptyArea: message = $"Area must be empty!"; break; default: break; } Timer.Execute(350f * i, () => { FloatingMessageEvents.Send(message, finalPos, 5, 2f, 20f); }); } Timer.Execute(350f * brokenRules.Count, () => { DrawResourceErrorMessage(finalPos); }); } } }