using System; using System.Collections.Generic; using GeometryTD.CustomComponent; using GeometryTD.CustomEvent; using GameFramework.Event; 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; private CombatSelectUseCaseConfigurator _combatSelectUseCaseConfigurator; private int _currentCoin; private bool _isCoinEventSubscribed; 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(); InitializeCombatSelectUseCaseConfigurator(); 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); } else { _currentCoin = Mathf.Max(0, _mapData.InitialCoin); SubscribeCombatEvents(); } RefreshTiles(); ConfigureCombatSelectUseCase(); HideCombatSelectForm(); } protected override void OnHide(bool isShutdown, object userData) { UnsubscribeCombatEvents(); HideCombatSelectForm(); _towerPlacementService?.HideAndClearAllPlacedTowers(); ClearSelectionState(); ClearTowerTracking(); ClearMapTopology(); base.OnHide(isShutdown, userData); } protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) { base.OnUpdate(elapseSeconds, realElapseSeconds); HandleCombatSelectInput(); } private void RefreshTiles() { ClearMapTopology(); ClearTowerTracking(); ClearSelectionState(); 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 ClearMapTopology() { _mapTopologyService?.Clear(); } private void ClearTowerTracking() { _towerPlacementService?.ClearTracking(); } private void ClearSelectionState() { _towerSelectionPresenter?.ClearSelectedObject(); } private void InitializeCombatSelectUseCase() { if (_combatSelectFormUseCase == null) { _combatSelectFormUseCase = new CombatSelectFormUseCase(); } GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatSelectForm, _combatSelectFormUseCase); } private void InitializeCombatSelectUseCaseConfigurator() { if (_combatSelectUseCaseConfigurator == null) { _combatSelectUseCaseConfigurator = new CombatSelectUseCaseConfigurator(); } } 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() { _combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin); BackpackInventoryData inventorySnapshot = GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetInventorySnapshot() : null; IReadOnlyList participantTowers = GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.GetParticipantTowerSnapshot() : null; _combatSelectUseCaseConfigurator?.Configure( _combatSelectFormUseCase, GetCurrentCoin, buildIndex => () => TryBuildTower(buildIndex), TryUpgradeTower, _upgradeCost, TryDestroyTower, _destroyGain, _buildTowerCosts, GetCurrentBuildTowerCount(), inventorySnapshot, participantTowers); } 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 (_mapData == null || !_mapData.TryGetBuildTowerStats(buildIndex, out TowerStatsData buildTowerStats)) { return false; } BuildTowerVisualInfo buildVisual = _combatSelectUseCaseConfigurator != null ? _combatSelectUseCaseConfigurator.GetBuildVisualInfo(buildIndex) : BuildTowerVisualInfo.Default; if (!_towerPlacementService.TryBuildTower(selectedFoundationCell, IsFoundationCell, buildIndex, _buildTowerCosts, buildTowerStats, buildVisual.MuzzleColor, buildVisual.BearingColor, buildVisual.BaseColor, _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 void SubscribeCombatEvents() { if (_isCoinEventSubscribed) { return; } GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged); _isCoinEventSubscribed = true; } private void UnsubscribeCombatEvents() { if (!_isCoinEventSubscribed) { return; } GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged); _isCoinEventSubscribed = false; } private void OnCombatCoinChanged(object sender, GameEventArgs e) { if (e is not CombatCoinChangedEventArgs args) { return; } _currentCoin = Mathf.Max(0, args.CurrentCoin); } private int GetCurrentBuildTowerCount() { if (_mapData == null) { return 0; } return Mathf.Clamp(_mapData.CurrentBuildTowerCount, 0, 4); } private bool TryConsumeCoin(int cost) { int requiredCoin = Mathf.Max(0, cost); if (requiredCoin <= 0) { return true; } if (_mapData == null) { return false; } return _mapData.TryConsumeCoin(requiredCoin); } private int GetCurrentCoin() { return Mathf.Max(0, _currentCoin); } private void AddCoin(int coin) { int amount = Mathf.Max(0, coin); if (amount <= 0) { return; } _mapData?.AddCoin(amount); } } }