using System; using System.Collections.Generic; using GeometryTD.Map; using GeometryTD.Definition; using GeometryTD.Entity.EntityData; using GeometryTD.UI; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Tilemaps; using UnityGameFramework.Runtime; namespace GeometryTD.Entity { public class MapEntity : EntityBase { private const int DefaultTowerTypeId = 401; private static readonly Spawner[] EmptySpawners = Array.Empty(); [SerializeField] private bool _enableCombatSelectInput = true; [SerializeField] private int _towerTypeId = DefaultTowerTypeId; [SerializeField] private int[] _buildTowerCosts = { 40, 60, 60, 80 }; [SerializeField] private int _upgradeCost = 50; [SerializeField] private int _destroyGain = 40; private MapDataRefs _mapDataRefs; private MapData _mapData; private MapTopologyService _mapTopologyService; private CombatSelectFormUseCase _combatSelectFormUseCase; private CombatSelectInputService _combatSelectInputService; private TowerPlacementService _towerPlacementService; private TowerSelectionPresenter _towerSelectionPresenter; public IReadOnlyList PathCells => _mapTopologyService != null ? _mapTopologyService.PathCells : Array.Empty(); public IReadOnlyList FoundationCells => _mapTopologyService != null ? _mapTopologyService.FoundationCells : Array.Empty(); public Tilemap Tilemap => _mapDataRefs != null ? _mapDataRefs.Tilemap : null; public Spawner[] Spawners => _mapDataRefs?.Spawners ?? EmptySpawners; public House House => _mapDataRefs?.House; public bool IsPathCell(Vector3Int cellPosition) { return _mapTopologyService != null && _mapTopologyService.IsPathCell(cellPosition); } public bool IsFoundationCell(Vector3Int cellPosition) { return _mapTopologyService != null && _mapTopologyService.IsFoundationCell(cellPosition); } public bool TryGetNearestPathCell(Vector3 worldPosition, out Vector3Int pathCell) { pathCell = default; return _mapTopologyService != null && _mapTopologyService.TryGetNearestPathCell(Tilemap, worldPosition, out pathCell); } public Vector3 GetPathCellCenterWorld(Vector3Int pathCell) { return _mapTopologyService != null ? _mapTopologyService.GetPathCellCenterWorld(Tilemap, pathCell) : Vector3.zero; } public bool TryGetDefaultPathCells(Spawner spawner, out IReadOnlyList pathCells) { pathCells = null; return _mapTopologyService != null && _mapTopologyService.TryGetDefaultPathCells(spawner, out pathCells); } public bool TryFindPathCells(Spawner spawner, IReadOnlyCollection blockedCells, List pathResult) { return _mapTopologyService != null && _mapTopologyService.TryFindPathCells(spawner, blockedCells, pathResult); } public bool TryFindPathWorldPoints(Spawner spawner, IReadOnlyCollection blockedCells, List worldPathResult) { return _mapTopologyService != null && _mapTopologyService.TryFindPathWorldPoints(Tilemap, spawner, blockedCells, worldPathResult); } protected override void OnInit(object userData) { base.OnInit(userData); _mapDataRefs = GetComponent(); if (_mapDataRefs == null) { Log.Error("MapDataRefs is missing on map entity '{0}'.", name); } InitializeCombatSelectUseCase(); InitializeCombatSelectInputService(); InitializeMapTopologyService(); InitializeTowerPlacementService(); InitializeTowerSelectionPresenter(); } protected override void OnShow(object userData) { base.OnShow(userData); _mapData = userData as MapData; if (_mapData == null) { Log.Warning("MapData is invalid for map entity '{0}'.", Id); } RefreshTiles(); ConfigureCombatSelectUseCase(); HideCombatSelectForm(); } protected override void OnHide(bool isShutdown, object userData) { HideCombatSelectForm(); _towerPlacementService?.HideAndClearAllPlacedTowers(); ClearRuntimeData(); base.OnHide(isShutdown, userData); } protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) { base.OnUpdate(elapseSeconds, realElapseSeconds); HandleCombatSelectInput(); } private void RefreshTiles() { ClearRuntimeData(); if (_mapDataRefs == null) { _mapDataRefs = GetComponent(); if (_mapDataRefs == null) { Log.Error("MapDataRefs is missing on map entity '{0}'.", name); return; } } Tilemap tilemap = _mapDataRefs.Tilemap; if (tilemap == null) { Log.Error("Tilemap reference is missing in MapDataRefs on '{0}'.", name); return; } _mapTopologyService?.Refresh(tilemap, Spawners, House, name, _mapData != null ? _mapData.LevelId : 0); } private void ClearRuntimeData() { _mapTopologyService?.Clear(); _towerPlacementService?.ClearTracking(); _towerSelectionPresenter?.ClearSelectedObject(); } private void InitializeCombatSelectUseCase() { if (_combatSelectFormUseCase == null) { _combatSelectFormUseCase = new CombatSelectFormUseCase(); } GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatSelectForm, _combatSelectFormUseCase); } private void InitializeTowerSelectionPresenter() { if (_towerSelectionPresenter == null) { _towerSelectionPresenter = new TowerSelectionPresenter(); } } private void InitializeTowerPlacementService() { if (_towerPlacementService == null) { _towerPlacementService = new TowerPlacementService(); } } private void InitializeCombatSelectInputService() { if (_combatSelectInputService == null) { _combatSelectInputService = new CombatSelectInputService(); } } private void InitializeMapTopologyService() { if (_mapTopologyService == null) { _mapTopologyService = new MapTopologyService(); } } private void ConfigureCombatSelectUseCase() { if (_combatSelectFormUseCase == null) { return; } _combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin); for (int i = 0; i < 4; i++) { int buildIndex = i; _combatSelectFormUseCase.SetBuildAction( buildIndex, () => TryBuildTower(buildIndex), GetBuildTowerCost(buildIndex)); } _combatSelectFormUseCase.SetUpgradeAction( TryUpgradeTower, Mathf.Max(0, _upgradeCost)); _combatSelectFormUseCase.SetDestroyAction( TryDestroyTower, Mathf.Max(0, _destroyGain)); } private void HandleCombatSelectInput() { if (!_enableCombatSelectInput || !Input.GetMouseButtonDown(0)) { return; } if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject()) { return; } if (_combatSelectInputService == null || !_combatSelectInputService.TryBuildUserData(Tilemap, CachedTransform, _towerPlacementService != null ? _towerPlacementService.TowerEntityIdByFoundationCell : null, IsFoundationCell, IsTowerAtMaxLevel, _upgradeCost, _destroyGain, out CombatSelectFormUserData userData)) { userData = new CombatSelectFormUserData { ClickObjectType = CombatSelectClickObjectType.None }; } ApplySelectedObject(userData); GameEntry.UIRouter.OpenUI(UIFormType.CombatSelectForm, userData); } private bool IsTowerAtMaxLevel(int towerEntityId) { return _towerPlacementService != null && _towerPlacementService.IsTowerAtMaxLevel(towerEntityId); } private void ApplySelectedObject(CombatSelectFormUserData userData) { _towerSelectionPresenter?.ApplySelectedObject(userData); } private bool TryBuildTower(int buildIndex) { if (_towerSelectionPresenter == null || _towerPlacementService == null || !_towerSelectionPresenter.TryGetSelectedFoundationCell(out Vector3Int selectedFoundationCell) || !IsFoundationCell(selectedFoundationCell)) { return false; } if (!_towerPlacementService.TryBuildTower(selectedFoundationCell, IsFoundationCell, buildIndex, _buildTowerCosts, _towerTypeId, Tilemap, TryConsumeCoin, AddCoin, out int towerEntityId)) { return false; } _towerSelectionPresenter.SelectTower(selectedFoundationCell, towerEntityId); return true; } private bool TryUpgradeTower() { if (_towerSelectionPresenter == null || _towerPlacementService == null || !_towerSelectionPresenter.TryGetSelectedTower(_towerPlacementService.FoundationCellByTowerEntityId, out int towerEntityId, out Vector3Int foundationCell)) { return false; } if (!_towerPlacementService.TryUpgradeTower(towerEntityId, _upgradeCost, _towerTypeId, Tilemap, TryConsumeCoin, AddCoin, out int resultTowerEntityId, out foundationCell)) { if (resultTowerEntityId != 0) { _towerSelectionPresenter.SelectTower(foundationCell, resultTowerEntityId); } else { _towerSelectionPresenter.ClearSelectedObject(); } return false; } _towerSelectionPresenter.SelectTower(foundationCell, resultTowerEntityId); return true; } private bool TryDestroyTower() { if (_towerSelectionPresenter == null || _towerPlacementService == null || !_towerSelectionPresenter.TryGetSelectedTower(_towerPlacementService.FoundationCellByTowerEntityId, out int towerEntityId, out Vector3Int foundationCell)) { return false; } if (!_towerPlacementService.TryDestroyTower(towerEntityId, _destroyGain, AddCoin, out foundationCell)) { return false; } _towerSelectionPresenter.ClearSelectedObject(); return true; } private void HideCombatSelectForm() { _combatSelectFormUseCase?.Hide(); GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm); } private int GetBuildTowerCost(int buildIndex) { if (_buildTowerCosts == null || buildIndex < 0 || buildIndex >= _buildTowerCosts.Length) { return 0; } return Mathf.Max(0, _buildTowerCosts[buildIndex]); } private static bool TryConsumeCoin(int cost) { int requiredCoin = Mathf.Max(0, cost); if (requiredCoin <= 0) { return true; } if (GameEntry.CombatNode == null) { return false; } return GameEntry.CombatNode.TryConsumeCoin(requiredCoin); } private static int GetCurrentCoin() { return GameEntry.CombatNode != null ? Mathf.Max(0, GameEntry.CombatNode.CurrentCoin) : 0; } private static void AddCoin(int coin) { int amount = Mathf.Max(0, coin); if (amount <= 0) { return; } GameEntry.CombatNode?.AddCoin(amount); } } }