440 lines
15 KiB
C#
440 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using GeometryTD.CustomComponent;
|
|
using GeometryTD.Map;
|
|
using GeometryTD.Definition;
|
|
using GeometryTD.Entity.EntityData;
|
|
using GeometryTD.UI;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.InputSystem;
|
|
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<Spawner>();
|
|
|
|
[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 MapCombatRuntimeBridge _combatRuntimeBridge;
|
|
|
|
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
|
|
? _mapTopologyService.PathCells
|
|
: Array.Empty<Vector3Int>();
|
|
public IReadOnlyList<Vector3Int> FoundationCells => _mapTopologyService != null
|
|
? _mapTopologyService.FoundationCells
|
|
: Array.Empty<Vector3Int>();
|
|
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<Vector3Int> pathCells)
|
|
{
|
|
pathCells = null;
|
|
return _mapTopologyService != null && _mapTopologyService.TryGetDefaultPathCells(spawner, out pathCells);
|
|
}
|
|
|
|
public bool TryFindPathCells(Spawner spawner, IReadOnlyCollection<Vector3Int> blockedCells,
|
|
List<Vector3Int> pathResult)
|
|
{
|
|
return _mapTopologyService != null && _mapTopologyService.TryFindPathCells(spawner, blockedCells, pathResult);
|
|
}
|
|
|
|
public bool TryFindPathWorldPoints(Spawner spawner, IReadOnlyCollection<Vector3Int> blockedCells,
|
|
List<Vector3> worldPathResult)
|
|
{
|
|
return _mapTopologyService != null &&
|
|
_mapTopologyService.TryFindPathWorldPoints(Tilemap, spawner, blockedCells, worldPathResult);
|
|
}
|
|
|
|
protected override void OnInit(object userData)
|
|
{
|
|
base.OnInit(userData);
|
|
|
|
_mapDataRefs = GetComponent<MapDataRefs>();
|
|
if (_mapDataRefs == null)
|
|
{
|
|
Log.Error("MapDataRefs is missing on map entity '{0}'.", name);
|
|
}
|
|
|
|
InitializeCombatSelectUseCase();
|
|
InitializeCombatSelectUseCaseConfigurator();
|
|
InitializeCombatSelectInputService();
|
|
InitializeMapTopologyService();
|
|
InitializeTowerPlacementService();
|
|
InitializeTowerSelectionPresenter();
|
|
InitializeCombatRuntimeBridge();
|
|
}
|
|
|
|
protected override void OnShow(object userData)
|
|
{
|
|
MapEntityLoadContext loadContext = ResolveLoadContext(userData);
|
|
_mapData = loadContext?.InitialMapData;
|
|
if (_mapData == null)
|
|
{
|
|
Log.Warning("MapData is invalid for map entity '{0}'.", Id);
|
|
}
|
|
|
|
base.OnShow(_mapData);
|
|
|
|
_combatRuntimeBridge?.Initialize(loadContext, name);
|
|
|
|
RefreshTiles();
|
|
ConfigureCombatSelectUseCase();
|
|
HideCombatSelectForm();
|
|
}
|
|
|
|
protected override void OnHide(bool isShutdown, object userData)
|
|
{
|
|
_combatRuntimeBridge?.Reset();
|
|
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<MapDataRefs>();
|
|
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 InitializeCombatRuntimeBridge()
|
|
{
|
|
if (_combatRuntimeBridge == null)
|
|
{
|
|
_combatRuntimeBridge = new MapCombatRuntimeBridge();
|
|
}
|
|
}
|
|
|
|
private void ConfigureCombatSelectUseCase()
|
|
{
|
|
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
|
|
_combatSelectUseCaseConfigurator?.Configure(
|
|
_combatSelectFormUseCase,
|
|
GetCurrentCoin,
|
|
buildIndex => () => TryBuildTower(buildIndex),
|
|
TryUpgradeTower,
|
|
_upgradeCost,
|
|
TryDestroyTower,
|
|
_destroyGain,
|
|
_buildTowerCosts,
|
|
GetCurrentBuildTowerCount(),
|
|
_mapData != null ? _mapData.InventorySnapshot : null,
|
|
_mapData != null ? _mapData.ParticipantTowerSnapshot : null);
|
|
}
|
|
|
|
private MapEntityLoadContext ResolveLoadContext(object userData)
|
|
{
|
|
if (userData is MapEntityLoadContext loadContext)
|
|
{
|
|
return loadContext;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void HandleCombatSelectInput()
|
|
{
|
|
Mouse mouse = Mouse.current;
|
|
if (!_enableCombatSelectInput || mouse == null || !mouse.leftButton.wasPressedThisFrame)
|
|
{
|
|
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 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 _combatRuntimeBridge != null && _combatRuntimeBridge.TryConsumeCoin(requiredCoin);
|
|
}
|
|
|
|
private int GetCurrentCoin()
|
|
{
|
|
return Mathf.Max(0, _combatRuntimeBridge != null ? _combatRuntimeBridge.CurrentCoin : 0);
|
|
}
|
|
|
|
private void AddCoin(int coin)
|
|
{
|
|
int amount = Mathf.Max(0, coin);
|
|
if (amount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_combatRuntimeBridge?.AddCoin(amount);
|
|
}
|
|
}
|
|
}
|