GridBuildingSystem.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using KairoEngine.Utility;
  5. using KairoEngine.Core;
  6. using KairoEngine.UI;
  7. using KairoEngine.Stockpiles;
  8. using Utils = KairoEngine.Utility.Utilities;
  9. using Sirenix.OdinInspector;
  10. namespace KairoEngine.Grids
  11. {
  12. [HideMonoScript]
  13. public class GridBuildingSystem : MonoBehaviour
  14. {
  15. public static GridBuildingSystem Instance;
  16. public Vector2Int gridSize = new Vector2Int(8,8);
  17. public float cellSize = 10f;
  18. public Vector3 gridPosition = new Vector3();
  19. public bool debug = false;
  20. public string stockpileOwner = "Player";
  21. public bool enableBuildingPlacement = true;
  22. public bool enableBuildingDestruction = false;
  23. [SerializeField] private Grid<GridObject> grid;
  24. //[OnInspectorInit("CreateGrid")]
  25. public List<PlacedObjectType> placeObjectTypeList;
  26. private PlacedObjectType placedObjectType;
  27. private PlacedObjectType.Dir dir = PlacedObjectType.Dir.Down;
  28. public List<PlacedObject> placedObjects = new List<PlacedObject>();
  29. public LayerMask layerMask;
  30. public static bool buildingToolAvailable
  31. {
  32. get;
  33. private set;
  34. }
  35. public static bool buildingDestructionToolAvailable
  36. {
  37. get;
  38. private set;
  39. }
  40. void Awake()
  41. {
  42. CreateGrid();
  43. placedObjectType = placeObjectTypeList[0];
  44. if(Instance == null) Instance = this;
  45. else Destroy(gameObject);
  46. }
  47. void Update()
  48. {
  49. Vector3 worldPos = Utils.GetMouseWorldPosition(layerMask.value);
  50. if(!MouseInputUIBlocker.BlockedByUI && grid.IsPositionInsideGrid(worldPos))
  51. {
  52. if(enableBuildingPlacement)
  53. {
  54. buildingToolAvailable = true;
  55. if(Input.GetKeyDown(KeyCode.R)) RotatePlacebableObject();
  56. if(Input.GetMouseButtonDown(0)) PlaceObjectInGrid(worldPos);
  57. }
  58. else buildingToolAvailable = false;
  59. if(enableBuildingDestruction)
  60. {
  61. buildingDestructionToolAvailable = true;
  62. if(Input.GetMouseButtonDown(0)) RemoveObjectFromGrid(worldPos);
  63. }
  64. else buildingDestructionToolAvailable = false;
  65. }
  66. }
  67. public bool IsGridCreated() => grid != null;
  68. public void CreateGrid()
  69. {
  70. if(grid == null)
  71. {
  72. grid = new Grid<GridObject>(gridSize.x, gridSize.y, cellSize, gridPosition,
  73. (Grid<GridObject> g, int x, int y) => new GridObject(g, x, 0, y), GridPlane.XZ, debug);
  74. }
  75. }
  76. public void ClearGrid() => grid = null;
  77. private void DeselectObjectType()
  78. {
  79. placedObjectType = null;
  80. RefreshSelectedObjectType();
  81. }
  82. private void RefreshSelectedObjectType() => GenericEvents.Trigger("OnSelectedChanged");
  83. public Vector2Int GetGridPosition(Vector3 worldPosition) {
  84. if(grid == null) return new Vector2Int();
  85. grid.GetGridPosition(worldPosition, out int x, out int z);
  86. return new Vector2Int(x, z);
  87. }
  88. public Vector3 GetPositionFromGrid(int x, int y) => grid != null ? grid.GetWorldPosition(x, y) : new Vector3();
  89. public Vector3 GetGridPositionOffset() => gridPosition - transform.position;
  90. public PlacedObjectType.Dir GetDir() => dir;
  91. public Vector3 GetCellSize()
  92. {
  93. if(grid == null) return new Vector3(cellSize, cellSize, cellSize);
  94. switch(grid.GetGridPlane())
  95. {
  96. default:
  97. case GridPlane.XY:
  98. return new Vector3(cellSize, cellSize, 0);
  99. case GridPlane.XZ:
  100. return new Vector3(cellSize, 0, cellSize);
  101. case GridPlane.YZ:
  102. return new Vector3(0, cellSize, cellSize);
  103. }
  104. }
  105. public Vector3 GetMouseWorldSnappedPosition() {
  106. Vector3 mousePosition = Utils.GetMouseWorldPosition(layerMask.value);
  107. if(grid == null) return new Vector3();
  108. grid.GetGridPosition(mousePosition, out int x, out int z);
  109. if (placedObjectType != null) {
  110. Vector2Int rotationOffset = placedObjectType.GetRotationOffset(dir);
  111. Vector3 placedObjectWorldPosition = grid.GetWorldPosition(x, z) + new Vector3(rotationOffset.x, 0, rotationOffset.y) * grid.GetCellSize();
  112. return placedObjectWorldPosition;
  113. } else {
  114. return mousePosition;
  115. }
  116. }
  117. public Quaternion GetPlacedObjectRotation() {
  118. if (placedObjectType != null) {
  119. return Quaternion.Euler(0, placedObjectType.GetRotationAngle(dir), 0);
  120. } else {
  121. return Quaternion.identity;
  122. }
  123. }
  124. public PlacedObjectType GetPlacedObjectTypeSO() => placedObjectType;
  125. public bool CanPlaceObject(int x, int z) => placedObjectType.CanPlaceObject(grid, x, z, dir);
  126. public void PlaceObjectInGrid(Vector3 worldPos)
  127. {
  128. grid.GetGridPosition(Utils.GetMouseWorldPosition(layerMask.value), out int x, out int z);
  129. PlaceObjectInGrid(x, z);
  130. }
  131. public void PlaceObjectInGrid(int x, int z)
  132. {
  133. PlaceObjectInGrid(x, z, placedObjectType, dir);
  134. }
  135. public void PlaceObjectInGrid(int x, int z, PlacedObjectType placedObjectType, PlacedObjectType.Dir dir, bool checkResources = true,
  136. bool checkPlace = true, bool playSound = true, bool destructable = true)
  137. {
  138. Vector3 worldPos = grid.GetWorldPosition(x, z);
  139. if(CanPlaceObject(x, z) || checkPlace == false)
  140. {
  141. if(HasResources() || checkResources == false)
  142. {
  143. Vector2Int rotationOffset = placedObjectType.GetRotationOffset(dir);
  144. Vector3 pos = grid.GetWorldPosition(x + rotationOffset.x, z + rotationOffset.y);
  145. PlacedObject placedObject = PlacedObject.Create(pos, new Vector2Int(x, z), dir, placedObjectType, playSound);
  146. placedObject.destructable = destructable;
  147. placedObjects.Add(placedObject);
  148. List<Vector2Int> gridPositionList = placedObjectType.GetGridPositionList(new Vector2Int(x, z), dir);
  149. foreach (var gridPosition in gridPositionList)
  150. {
  151. grid.GetObject(gridPosition.x, gridPosition.y).SetPlacedObject(placedObject);
  152. }
  153. if(checkResources)
  154. {
  155. GenericEvents.Trigger("BuildingPlaced", placedObjectType.title);
  156. StockpileManager.instance.RemoveResources(stockpileOwner, placedObjectType.cost);
  157. for (int i = 0; i < placedObjectType.cost.Count; i++)
  158. {
  159. var stock = placedObjectType.cost[i];
  160. float delay = 350f * i;
  161. Timer.Execute(delay, () => {
  162. string resource = stock.stockpileType.title.ToLower();
  163. if(resource == "mineral") resource = "minerals";
  164. string message = $"- {stock.ammount} <sprite name={resource}>";
  165. FloatingMessageEvents.Send(message, GetCurrentPlacebleObjectCenter(pos), 5, 1.3f, 20f);
  166. });
  167. }
  168. }
  169. }
  170. else
  171. {
  172. Vector3 finalPos = GetCurrentPlacebleObjectCenter(worldPos);
  173. DrawResourceErrorMessage(finalPos);
  174. GenericEvents.Trigger("CannotBuildError");
  175. }
  176. }
  177. else
  178. {
  179. Vector3 finalPos = new Vector3(worldPos.x, worldPos.y + 10, worldPos.z);
  180. DrawRulesetErrorMessage(finalPos, x, z);
  181. GenericEvents.Trigger("CannotBuildError");
  182. }
  183. }
  184. public void RemoveObjectFromGrid(Vector3 worldPos)
  185. {
  186. GridObject gridObject = grid.GetObject(worldPos);
  187. RemoveObjectFromGrid(gridObject);
  188. }
  189. public void RemoveObjectFromGrid(int x, int z)
  190. {
  191. GridObject gridObject = grid.GetObject(x, z);
  192. RemoveObjectFromGrid(gridObject);
  193. }
  194. public void RemoveObjectFromGrid(GridObject gridObject)
  195. {
  196. if(gridObject == null) return;
  197. PlacedObject placedObject = gridObject.GetPlacedObject();
  198. if(placedObject != null)
  199. {
  200. Vector3 worldPos = grid.GetWorldPosition(gridObject.GetPosition().x, gridObject.GetPosition().z);
  201. Vector3 finalPos = GetCurrentPlacebleObjectCenter(worldPos);
  202. if(!placedObject.placedObjectType.isDestructable)
  203. {
  204. FloatingMessageEvents.Send("Indestructable!", finalPos, 5, 2f, 20f);
  205. return;
  206. }
  207. if(!placedObject.destructable)
  208. {
  209. FloatingMessageEvents.Send("Cannot remove initial building", finalPos, 5, 2f, 20f);
  210. return;
  211. }
  212. GenericEvents.Trigger("BuildingRemoved", placedObject.placedObjectType.title, worldPos);
  213. placedObjects.Remove(placedObject);
  214. placedObject.DestroySelf();
  215. List<Vector2Int> gridPositionList = placedObject.GetGridPositionList();
  216. foreach (var gridPosition in gridPositionList)
  217. {
  218. grid.GetObject(gridPosition.x, gridPosition.y).ClearPlacedObject();
  219. }
  220. }
  221. }
  222. public void RotatePlacebableObject()
  223. {
  224. dir = PlacedObjectType.GetNextDir(dir);
  225. Vector3 pos = Utils.GetMouseWorldPosition(layerMask.value);
  226. Vector3 finalPos = new Vector3(pos.x, pos.y + 10, pos.z);
  227. //Utilities.CreateWorldTextPopup(null, "" + dir, pos, 18, Color.white, finalPos, 1.2f, true);
  228. FloatingMessageEvents.Send("" + dir, GetCurrentPlacebleObjectCenter(pos), 5, 1.2f, 20f);
  229. }
  230. public void ChangeSelectedPlacebleObject(int index)
  231. {
  232. placedObjectType = placeObjectTypeList[index];
  233. RefreshSelectedObjectType();
  234. }
  235. public void ChangeSelectedPlacebleObject(PlacedObjectType newPlacedObjectType)
  236. {
  237. placedObjectType = newPlacedObjectType;
  238. RefreshSelectedObjectType();
  239. }
  240. public bool IsPositionInsideGrid(Vector2Int gridPos)
  241. {
  242. if(grid == null) return false;
  243. return grid.IsPositionInsideGrid(gridPos);
  244. }
  245. public bool IsPositionInsideGrid(Vector3 worldPos)
  246. {
  247. if(grid == null) return false;
  248. return grid.IsPositionInsideGrid(worldPos);
  249. }
  250. public PlacedObject GetPlacedObjectInGrid(Vector2Int gridPosition)
  251. {
  252. if(grid == null) return null;
  253. GridObject gridObject = grid.GetObject(gridPosition.x, gridPosition.y);
  254. if(gridObject == null) return null;
  255. PlacedObject placedObject = gridObject.GetPlacedObject();
  256. return placedObject;
  257. }
  258. public bool HasResources() => StockpileManager.instance.HasResources(stockpileOwner, placedObjectType.cost);
  259. public bool HasResource(KairoEngine.Stockpiles.Stockpile stockpile) => StockpileManager.instance.HasResource(stockpileOwner, stockpile);
  260. public void RemovePlacedObjects()
  261. {
  262. for (int i = 0; i < placedObjects.Count; i++)
  263. {
  264. Destroy(placedObjects[i].gameObject);
  265. }
  266. placedObjects.Clear();
  267. }
  268. public Vector3 GetCurrentPlacebleObjectCenter(Vector3 worldPos)
  269. {
  270. if(dir == PlacedObjectType.Dir.Down || dir == PlacedObjectType.Dir.Up)
  271. {
  272. return new Vector3(
  273. worldPos.x + (placedObjectType.width * cellSize)/2,
  274. worldPos.y + 1,
  275. worldPos.z + (placedObjectType.height * cellSize)/2);
  276. }
  277. else
  278. {
  279. return new Vector3(
  280. worldPos.x + (placedObjectType.height * cellSize)/2,
  281. worldPos.y + 1,
  282. worldPos.z + (placedObjectType.width * cellSize)/2);
  283. }
  284. }
  285. private void DrawResourceErrorMessage(Vector3 finalPos)
  286. {
  287. List<string> sprites = new List<string>();
  288. bool missingResources = false;
  289. for (int i = 0; i < placedObjectType.cost.Count; i++)
  290. {
  291. if(!HasResource(placedObjectType.cost[i]))
  292. {
  293. sprites.Add(placedObjectType.cost[i].stockpileType.spriteText);
  294. missingResources = true;
  295. }
  296. }
  297. if(missingResources == false) return;
  298. switch (sprites.Count)
  299. {
  300. case 1:
  301. FloatingMessageEvents.Send($"Not enought {sprites[0]}", finalPos, 5, 1.5f, 20f);
  302. break;
  303. case 2:
  304. FloatingMessageEvents.Send($"Not enought {sprites[0]} and {sprites[1]}", finalPos, 5, 1.5f, 20f);
  305. break;
  306. case 3:
  307. FloatingMessageEvents.Send($"Not enought {sprites[0]}, {sprites[1]} and {sprites[2]}", finalPos, 5, 1.5f, 20f);
  308. break;
  309. case 4:
  310. FloatingMessageEvents.Send($"Not enought {sprites[0]}, {sprites[1]}, {sprites[2]} and {sprites[3]}", finalPos, 5, 1.5f, 20f);
  311. break;
  312. default:
  313. FloatingMessageEvents.Send("Not enought resources", finalPos, 5, 2f, 20f);
  314. break;
  315. }
  316. }
  317. private void DrawRulesetErrorMessage(Vector3 finalPos, int x, int y)
  318. {
  319. List<PlaceableObjectRule> brokenRules = placedObjectType.GetRulesetError(grid, x, y, dir);
  320. for (int i = 0; i < brokenRules.Count; i++)
  321. {
  322. string message = "";
  323. switch (brokenRules[i].GetRule())
  324. {
  325. case PlaceableObjectRule.Rule.EmptyCell:
  326. message = $"Area must be empty!";
  327. break;
  328. case PlaceableObjectRule.Rule.Connection:
  329. message = $"Invalid connection";
  330. //message += $" of type {brokenRules[i].GetConnector()}!";
  331. break;
  332. case PlaceableObjectRule.Rule.CellOcupied:
  333. message = $"Must be placed on a {brokenRules[i].GetTargetObject().title}";
  334. break;
  335. case PlaceableObjectRule.Rule.AdjacentCellOcupied:
  336. message = $"Adjacent cell need to be of type {brokenRules[i].GetTargetObject().title}";
  337. break;
  338. case PlaceableObjectRule.Rule.AdjacentEmptyCell:
  339. message = $"Adjacent cell must be empty";
  340. break;
  341. case PlaceableObjectRule.Rule.CellInRadius:
  342. message = $"No {brokenRules[i].GetTargetObject().title} in {brokenRules[i].GetRadius()} radius";
  343. break;
  344. case PlaceableObjectRule.Rule.EmptyArea:
  345. message = $"Area must be empty!";
  346. break;
  347. default:
  348. break;
  349. }
  350. Timer.Execute(350f * i, () => {
  351. FloatingMessageEvents.Send(message, finalPos, 5, 2f, 20f);
  352. });
  353. }
  354. Timer.Execute(350f * brokenRules.Count, () => {
  355. DrawResourceErrorMessage(finalPos);
  356. });
  357. }
  358. }
  359. }