|
@@ -0,0 +1,402 @@
|
|
|
|
+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<GridObject> grid;
|
|
|
|
+
|
|
|
|
+ //[OnInspectorInit("CreateGrid")]
|
|
|
|
+ public List<PlacedObjectType> placeObjectTypeList;
|
|
|
|
+ private PlacedObjectType placedObjectType;
|
|
|
|
+ private PlacedObjectType.Dir dir = PlacedObjectType.Dir.Down;
|
|
|
|
+ public List<PlacedObject> placedObjects = new List<PlacedObject>();
|
|
|
|
+
|
|
|
|
+ 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<GridObject>(gridSize.x, gridSize.y, cellSize, gridPosition,
|
|
|
|
+ (Grid<GridObject> 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<Vector2Int> 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} <sprite name={resource}>";
|
|
|
|
+ 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<Vector2Int> 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<string> sprites = new List<string>();
|
|
|
|
+ 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<PlaceableObjectRule> 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);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+}
|