805 lines
28 KiB
C#
805 lines
28 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using GeometryTD;
|
|
using GeometryTD.Definition;
|
|
using GeometryTD.Entity.EntityData;
|
|
using GeometryTD.Pathfinding;
|
|
using GeometryTD.UI;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.Tilemaps;
|
|
using UnityGameFramework.Runtime;
|
|
|
|
namespace GeometryTD.Entity
|
|
{
|
|
public class MapEntity : EntityBase
|
|
{
|
|
private const string PathTileName = "Path";
|
|
private const string FoundationTileName = "Foundation";
|
|
private const int DefaultTowerTypeId = 401;
|
|
private static readonly Spawner[] EmptySpawners = Array.Empty<Spawner>();
|
|
|
|
private readonly List<Vector3Int> _pathCells = new();
|
|
private readonly List<Vector3Int> _foundationCells = new();
|
|
private readonly HashSet<Vector3Int> _pathCellSet = new();
|
|
private readonly HashSet<Vector3Int> _foundationCellSet = new();
|
|
private readonly IMapPathfinder _mapPathfinder = new GridMapPathfinder();
|
|
private readonly List<Vector3Int> _pathCellBuffer = new();
|
|
private readonly Dictionary<Spawner, Vector3Int> _spawnerPathStartByRef = new();
|
|
private readonly Dictionary<Spawner, List<Vector3Int>> _defaultPathCellsBySpawner = new();
|
|
private readonly Dictionary<Vector3Int, int> _towerEntityIdByFoundationCell = new();
|
|
private readonly Dictionary<int, Vector3Int> _foundationCellByTowerEntityId = new();
|
|
private readonly Dictionary<int, DefenseTowerStatsData> _towerStatsByEntityId = new();
|
|
private readonly List<int> _towerEntityIdBuffer = new();
|
|
|
|
[SerializeField] private bool _enableCombatSelectInput = true;
|
|
[SerializeField] private int _towerTypeId = DefaultTowerTypeId;
|
|
[SerializeField] private int[] _buildTowerCosts = { 80, 120, 160, 220 };
|
|
[SerializeField] private int _upgradeCost = 80;
|
|
[SerializeField] private int _destroyGain = 40;
|
|
|
|
private MapDataRefs _mapDataRefs;
|
|
private MapData _mapData;
|
|
private bool _hasHousePathCell;
|
|
private Vector3Int _housePathCell;
|
|
private CombatSelectFormUseCase _combatSelectFormUseCase;
|
|
private bool _hasSelectedFoundationCell;
|
|
private Vector3Int _selectedFoundationCell;
|
|
private int _selectedTowerEntityId;
|
|
|
|
public IReadOnlyList<Vector3Int> PathCells => _pathCells;
|
|
public IReadOnlyList<Vector3Int> FoundationCells => _foundationCells;
|
|
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 _pathCellSet.Contains(cellPosition);
|
|
}
|
|
|
|
public bool IsFoundationCell(Vector3Int cellPosition)
|
|
{
|
|
return _foundationCellSet.Contains(cellPosition);
|
|
}
|
|
|
|
public bool TryGetNearestPathCell(Vector3 worldPosition, out Vector3Int pathCell)
|
|
{
|
|
pathCell = default;
|
|
if (_pathCells.Count <= 0 || Tilemap == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Vector3Int directCell = Tilemap.WorldToCell(worldPosition);
|
|
if (_pathCellSet.Contains(directCell))
|
|
{
|
|
pathCell = directCell;
|
|
return true;
|
|
}
|
|
|
|
float minDistance = float.MaxValue;
|
|
for (int i = 0; i < _pathCells.Count; i++)
|
|
{
|
|
Vector3Int candidate = _pathCells[i];
|
|
float distance = (Tilemap.GetCellCenterWorld(candidate) - worldPosition).sqrMagnitude;
|
|
if (distance >= minDistance)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
minDistance = distance;
|
|
pathCell = candidate;
|
|
}
|
|
|
|
return minDistance < float.MaxValue;
|
|
}
|
|
|
|
public Vector3 GetPathCellCenterWorld(Vector3Int pathCell)
|
|
{
|
|
return Tilemap != null ? Tilemap.GetCellCenterWorld(pathCell) : Vector3.zero;
|
|
}
|
|
|
|
public bool TryGetDefaultPathCells(Spawner spawner, out IReadOnlyList<Vector3Int> pathCells)
|
|
{
|
|
pathCells = null;
|
|
if (spawner == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_defaultPathCellsBySpawner.TryGetValue(spawner, out List<Vector3Int> cachedPathCells))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pathCells = cachedPathCells;
|
|
return true;
|
|
}
|
|
|
|
public bool TryFindPathCells(Spawner spawner, IReadOnlyCollection<Vector3Int> blockedCells,
|
|
List<Vector3Int> pathResult)
|
|
{
|
|
if (pathResult == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pathResult.Clear();
|
|
if (spawner == null || !_hasHousePathCell)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_spawnerPathStartByRef.TryGetValue(spawner, out Vector3Int startCell))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return _mapPathfinder.TryFindPath(_pathCells, startCell, _housePathCell, blockedCells, pathResult);
|
|
}
|
|
|
|
public bool TryFindPathWorldPoints(Spawner spawner, IReadOnlyCollection<Vector3Int> blockedCells,
|
|
List<Vector3> worldPathResult)
|
|
{
|
|
if (worldPathResult == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
worldPathResult.Clear();
|
|
if (Tilemap == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!TryFindPathCells(spawner, blockedCells, _pathCellBuffer))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var pos in _pathCellBuffer)
|
|
{
|
|
worldPathResult.Add(GetPathCellCenterWorld(pos));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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();
|
|
ClearPlacedTowers();
|
|
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<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;
|
|
}
|
|
|
|
BoundsInt bounds = tilemap.cellBounds;
|
|
foreach (Vector3Int cellPosition in bounds.allPositionsWithin)
|
|
{
|
|
TileBase tile = tilemap.GetTile(cellPosition);
|
|
if (tile == null || string.IsNullOrEmpty(tile.name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (string.Equals(tile.name, PathTileName, StringComparison.Ordinal))
|
|
{
|
|
_pathCells.Add(cellPosition);
|
|
_pathCellSet.Add(cellPosition);
|
|
continue;
|
|
}
|
|
|
|
if (string.Equals(tile.name, FoundationTileName, StringComparison.Ordinal))
|
|
{
|
|
_foundationCells.Add(cellPosition);
|
|
_foundationCellSet.Add(cellPosition);
|
|
}
|
|
}
|
|
|
|
RefreshPathCache();
|
|
Log.Info(
|
|
"Map '{0}' initialized. LevelId={1}, PathCells={2}, FoundationCells={3}, Spawners={4}, House={5}, Routes={6}.",
|
|
name,
|
|
_mapData != null ? _mapData.LevelId : 0,
|
|
_pathCells.Count,
|
|
_foundationCells.Count,
|
|
Spawners.Length,
|
|
House != null ? House.name : "None",
|
|
_defaultPathCellsBySpawner.Count);
|
|
}
|
|
|
|
private void RefreshPathCache()
|
|
{
|
|
_hasHousePathCell = false;
|
|
_housePathCell = default;
|
|
_spawnerPathStartByRef.Clear();
|
|
_defaultPathCellsBySpawner.Clear();
|
|
|
|
if (House == null)
|
|
{
|
|
Log.Warning("Map '{0}' has no house reference, path cache skipped.", name);
|
|
return;
|
|
}
|
|
|
|
if (!TryGetNearestPathCell(House.Position, out _housePathCell))
|
|
{
|
|
Log.Warning("Map '{0}' house position can not map to a valid path cell.", name);
|
|
return;
|
|
}
|
|
|
|
_hasHousePathCell = true;
|
|
Spawner[] spawners = Spawners;
|
|
foreach (var spawner in spawners)
|
|
{
|
|
if (spawner == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!TryGetNearestPathCell(spawner.Position, out Vector3Int startCell))
|
|
{
|
|
Log.Warning("Map '{0}' spawner '{1}' can not map to a valid path cell.", name, spawner.name);
|
|
continue;
|
|
}
|
|
|
|
_spawnerPathStartByRef[spawner] = startCell;
|
|
|
|
List<Vector3Int> defaultPathCells = new List<Vector3Int>();
|
|
if (!_mapPathfinder.TryFindPath(_pathCells, startCell, _housePathCell, null, defaultPathCells))
|
|
{
|
|
Log.Warning(
|
|
"Map '{0}' spawner '{1}' has no path to house cell {2}.",
|
|
name,
|
|
spawner.name,
|
|
_housePathCell);
|
|
continue;
|
|
}
|
|
|
|
_defaultPathCellsBySpawner[spawner] = defaultPathCells;
|
|
}
|
|
}
|
|
|
|
private void ClearRuntimeData()
|
|
{
|
|
_pathCells.Clear();
|
|
_foundationCells.Clear();
|
|
_pathCellSet.Clear();
|
|
_foundationCellSet.Clear();
|
|
_pathCellBuffer.Clear();
|
|
_hasHousePathCell = false;
|
|
_housePathCell = default;
|
|
_spawnerPathStartByRef.Clear();
|
|
_defaultPathCellsBySpawner.Clear();
|
|
_towerEntityIdByFoundationCell.Clear();
|
|
_foundationCellByTowerEntityId.Clear();
|
|
_towerStatsByEntityId.Clear();
|
|
_towerEntityIdBuffer.Clear();
|
|
ClearSelectedObject();
|
|
}
|
|
|
|
private void InitializeCombatSelectUseCase()
|
|
{
|
|
if (_combatSelectFormUseCase == null)
|
|
{
|
|
_combatSelectFormUseCase = new CombatSelectFormUseCase();
|
|
}
|
|
|
|
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatSelectForm, _combatSelectFormUseCase);
|
|
}
|
|
|
|
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 (!TryBuildCombatSelectUserData(out CombatSelectFormUserData userData))
|
|
{
|
|
userData = new CombatSelectFormUserData
|
|
{
|
|
ClickObjectType = CombatSelectClickObjectType.None
|
|
};
|
|
}
|
|
|
|
ApplySelectedObject(userData);
|
|
GameEntry.UIRouter.OpenUI(UIFormType.CombatSelectForm, userData);
|
|
}
|
|
|
|
private bool TryBuildCombatSelectUserData(out CombatSelectFormUserData userData)
|
|
{
|
|
userData = null;
|
|
if (Tilemap == null || !TryGetPointerWorldPosition(out Vector3 worldPosition, out Vector2 contentPosition))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Vector3Int clickedCell = Tilemap.WorldToCell(worldPosition);
|
|
CombatSelectClickObjectType clickObjectType = CombatSelectClickObjectType.None;
|
|
int towerEntityId = 0;
|
|
|
|
if (_towerEntityIdByFoundationCell.TryGetValue(clickedCell, out int occupiedTowerEntityId))
|
|
{
|
|
clickObjectType = CombatSelectClickObjectType.Tower;
|
|
towerEntityId = occupiedTowerEntityId;
|
|
}
|
|
else if (IsFoundationCell(clickedCell))
|
|
{
|
|
clickObjectType = CombatSelectClickObjectType.Foundation;
|
|
}
|
|
|
|
userData = new CombatSelectFormUserData
|
|
{
|
|
ClickObjectType = clickObjectType,
|
|
ContentPosition = contentPosition,
|
|
WorldPosition = worldPosition,
|
|
CellPosition = clickedCell,
|
|
TowerEntityId = towerEntityId,
|
|
UpgradeCost = Mathf.Max(0, _upgradeCost),
|
|
DestroyGain = Mathf.Max(0, _destroyGain)
|
|
};
|
|
return true;
|
|
}
|
|
|
|
private bool TryGetPointerWorldPosition(out Vector3 worldPosition, out Vector2 contentPosition)
|
|
{
|
|
worldPosition = Vector3.zero;
|
|
contentPosition = Vector2.zero;
|
|
|
|
Camera mainCamera = GameEntry.Scene != null ? GameEntry.Scene.MainCamera : Camera.main;
|
|
if (mainCamera == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
|
|
float mapPlaneZ = Tilemap != null ? Tilemap.transform.position.z : CachedTransform.position.z;
|
|
Vector3 planeNormal = mainCamera.transform.forward.sqrMagnitude > Mathf.Epsilon
|
|
? -mainCamera.transform.forward
|
|
: Vector3.forward;
|
|
Plane mapPlane = new Plane(planeNormal, new Vector3(0f, 0f, mapPlaneZ));
|
|
if (!mapPlane.Raycast(ray, out float enterDistance))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
worldPosition = ray.GetPoint(enterDistance);
|
|
contentPosition = BuildContentPosition(Input.mousePosition);
|
|
return true;
|
|
}
|
|
|
|
private static Vector2 BuildContentPosition(Vector3 pointerScreenPosition)
|
|
{
|
|
return new Vector2(
|
|
pointerScreenPosition.x - Screen.width * 0.5f,
|
|
pointerScreenPosition.y - Screen.height * 0.5f);
|
|
}
|
|
|
|
private void ApplySelectedObject(CombatSelectFormUserData userData)
|
|
{
|
|
if (userData == null)
|
|
{
|
|
ClearSelectedObject();
|
|
return;
|
|
}
|
|
|
|
switch (userData.ClickObjectType)
|
|
{
|
|
case CombatSelectClickObjectType.Foundation:
|
|
_hasSelectedFoundationCell = true;
|
|
_selectedFoundationCell = userData.CellPosition;
|
|
_selectedTowerEntityId = 0;
|
|
break;
|
|
case CombatSelectClickObjectType.Tower:
|
|
_hasSelectedFoundationCell = true;
|
|
_selectedFoundationCell = userData.CellPosition;
|
|
_selectedTowerEntityId = userData.TowerEntityId;
|
|
break;
|
|
default:
|
|
ClearSelectedObject();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private bool TryBuildTower(int buildIndex)
|
|
{
|
|
if (!_hasSelectedFoundationCell || !IsFoundationCell(_selectedFoundationCell))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_towerEntityIdByFoundationCell.ContainsKey(_selectedFoundationCell))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int buildCost = GetBuildTowerCost(buildIndex);
|
|
if (!TryConsumeCoin(buildCost))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DefenseTowerStatsData towerStats = BuildTowerStats(buildIndex);
|
|
if (!TryShowTowerEntity(_selectedFoundationCell, towerStats, out int towerEntityId))
|
|
{
|
|
GameEntry.CombatNode?.AddCoin(buildCost);
|
|
return false;
|
|
}
|
|
|
|
_towerEntityIdByFoundationCell[_selectedFoundationCell] = towerEntityId;
|
|
_foundationCellByTowerEntityId[towerEntityId] = _selectedFoundationCell;
|
|
_towerStatsByEntityId[towerEntityId] = CloneTowerStats(towerStats);
|
|
_selectedTowerEntityId = towerEntityId;
|
|
return true;
|
|
}
|
|
|
|
private bool TryUpgradeTower()
|
|
{
|
|
if (!TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int upgradeCost = Mathf.Max(0, _upgradeCost);
|
|
if (!TryConsumeCoin(upgradeCost))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DefenseTowerStatsData oldStats = _towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats)
|
|
? CloneTowerStats(cachedStats)
|
|
: BuildTowerStats(0);
|
|
DefenseTowerStatsData upgradedStats = CloneTowerStats(oldStats);
|
|
ApplyUpgradeToStats(upgradedStats);
|
|
|
|
HideTowerEntity(towerEntityId);
|
|
_towerEntityIdByFoundationCell.Remove(foundationCell);
|
|
_foundationCellByTowerEntityId.Remove(towerEntityId);
|
|
_towerStatsByEntityId.Remove(towerEntityId);
|
|
|
|
if (!TryShowTowerEntity(foundationCell, upgradedStats, out int newTowerEntityId))
|
|
{
|
|
if (TryShowTowerEntity(foundationCell, oldStats, out int fallbackTowerEntityId))
|
|
{
|
|
_towerEntityIdByFoundationCell[foundationCell] = fallbackTowerEntityId;
|
|
_foundationCellByTowerEntityId[fallbackTowerEntityId] = foundationCell;
|
|
_towerStatsByEntityId[fallbackTowerEntityId] = CloneTowerStats(oldStats);
|
|
_selectedTowerEntityId = fallbackTowerEntityId;
|
|
}
|
|
|
|
GameEntry.CombatNode?.AddCoin(upgradeCost);
|
|
return false;
|
|
}
|
|
|
|
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
|
|
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
|
|
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(upgradedStats);
|
|
_hasSelectedFoundationCell = true;
|
|
_selectedFoundationCell = foundationCell;
|
|
_selectedTowerEntityId = newTowerEntityId;
|
|
return true;
|
|
}
|
|
|
|
private bool TryDestroyTower()
|
|
{
|
|
if (!TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
HideTowerEntity(towerEntityId);
|
|
_towerEntityIdByFoundationCell.Remove(foundationCell);
|
|
_foundationCellByTowerEntityId.Remove(towerEntityId);
|
|
_towerStatsByEntityId.Remove(towerEntityId);
|
|
GameEntry.CombatNode?.AddCoin(Mathf.Max(0, _destroyGain));
|
|
|
|
ClearSelectedObject();
|
|
return true;
|
|
}
|
|
|
|
private bool TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell)
|
|
{
|
|
towerEntityId = 0;
|
|
foundationCell = default;
|
|
|
|
if (_selectedTowerEntityId == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!_foundationCellByTowerEntityId.TryGetValue(_selectedTowerEntityId, out foundationCell))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
towerEntityId = _selectedTowerEntityId;
|
|
return true;
|
|
}
|
|
|
|
private bool TryShowTowerEntity(Vector3Int foundationCell, DefenseTowerStatsData towerStats, out int towerEntityId)
|
|
{
|
|
towerEntityId = 0;
|
|
if (GameEntry.Entity == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int entityId = GameEntry.Entity.GenerateSerialId();
|
|
int typeId = _towerTypeId > 0 ? _towerTypeId : DefaultTowerTypeId;
|
|
Vector3 towerPosition = Tilemap != null ? Tilemap.GetCellCenterWorld(foundationCell) : foundationCell;
|
|
towerPosition.z = 0f;
|
|
var towerData = new DefenseTowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats);
|
|
GameEntry.Entity.ShowDefenseTower(towerData);
|
|
|
|
towerEntityId = entityId;
|
|
return true;
|
|
}
|
|
|
|
private static void HideTowerEntity(int towerEntityId)
|
|
{
|
|
if (towerEntityId == 0 || GameEntry.Entity == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UnityGameFramework.Runtime.Entity towerEntity = GameEntry.Entity.GetEntity(towerEntityId);
|
|
if (towerEntity != null)
|
|
{
|
|
GameEntry.Entity.HideEntity(towerEntity);
|
|
}
|
|
}
|
|
|
|
private void ClearPlacedTowers()
|
|
{
|
|
_towerEntityIdBuffer.Clear();
|
|
foreach (KeyValuePair<int, Vector3Int> pair in _foundationCellByTowerEntityId)
|
|
{
|
|
_towerEntityIdBuffer.Add(pair.Key);
|
|
}
|
|
|
|
for (int i = 0; i < _towerEntityIdBuffer.Count; i++)
|
|
{
|
|
HideTowerEntity(_towerEntityIdBuffer[i]);
|
|
}
|
|
|
|
_towerEntityIdByFoundationCell.Clear();
|
|
_foundationCellByTowerEntityId.Clear();
|
|
_towerStatsByEntityId.Clear();
|
|
_towerEntityIdBuffer.Clear();
|
|
ClearSelectedObject();
|
|
}
|
|
|
|
private void HideCombatSelectForm()
|
|
{
|
|
_combatSelectFormUseCase?.Hide();
|
|
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
|
|
}
|
|
|
|
private void ClearSelectedObject()
|
|
{
|
|
_hasSelectedFoundationCell = false;
|
|
_selectedFoundationCell = default;
|
|
_selectedTowerEntityId = 0;
|
|
}
|
|
|
|
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 DefenseTowerStatsData BuildTowerStats(int buildIndex)
|
|
{
|
|
switch (buildIndex)
|
|
{
|
|
case 0:
|
|
return new DefenseTowerStatsData
|
|
{
|
|
AttackDamage = 10,
|
|
DamageRandomRate = 0f,
|
|
RotateSpeed = 200f,
|
|
AttackRange = 4.5f,
|
|
AttackSpeed = 1.5f,
|
|
AttackMethodType = AttackMethodType.NormalBullet,
|
|
AttackPropertyType = AttackPropertyType.Physics,
|
|
Tags = Array.Empty<TagType>()
|
|
};
|
|
case 1:
|
|
return new DefenseTowerStatsData
|
|
{
|
|
AttackDamage = 13,
|
|
DamageRandomRate = 0f,
|
|
RotateSpeed = 160f,
|
|
AttackRange = 5f,
|
|
AttackSpeed = 1.2f,
|
|
AttackMethodType = AttackMethodType.NormalBullet,
|
|
AttackPropertyType = AttackPropertyType.Fire,
|
|
Tags = Array.Empty<TagType>()
|
|
};
|
|
case 2:
|
|
return new DefenseTowerStatsData
|
|
{
|
|
AttackDamage = 17,
|
|
DamageRandomRate = 0f,
|
|
RotateSpeed = 140f,
|
|
AttackRange = 5.5f,
|
|
AttackSpeed = 0.95f,
|
|
AttackMethodType = AttackMethodType.NormalBullet,
|
|
AttackPropertyType = AttackPropertyType.Ice,
|
|
Tags = Array.Empty<TagType>()
|
|
};
|
|
case 3:
|
|
return new DefenseTowerStatsData
|
|
{
|
|
AttackDamage = 22,
|
|
DamageRandomRate = 0f,
|
|
RotateSpeed = 120f,
|
|
AttackRange = 6f,
|
|
AttackSpeed = 0.75f,
|
|
AttackMethodType = AttackMethodType.NormalBullet,
|
|
AttackPropertyType = AttackPropertyType.Poison,
|
|
Tags = Array.Empty<TagType>()
|
|
};
|
|
default:
|
|
return new DefenseTowerStatsData
|
|
{
|
|
AttackDamage = 10,
|
|
DamageRandomRate = 0f,
|
|
RotateSpeed = 180f,
|
|
AttackRange = 5f,
|
|
AttackSpeed = 1f,
|
|
AttackMethodType = AttackMethodType.NormalBullet,
|
|
AttackPropertyType = AttackPropertyType.Physics,
|
|
Tags = Array.Empty<TagType>()
|
|
};
|
|
}
|
|
}
|
|
|
|
private static DefenseTowerStatsData CloneTowerStats(DefenseTowerStatsData source)
|
|
{
|
|
if (source == null)
|
|
{
|
|
return BuildTowerStats(0);
|
|
}
|
|
|
|
TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty<TagType>();
|
|
return new DefenseTowerStatsData
|
|
{
|
|
AttackDamage = source.AttackDamage,
|
|
DamageRandomRate = source.DamageRandomRate,
|
|
RotateSpeed = source.RotateSpeed,
|
|
AttackRange = source.AttackRange,
|
|
AttackSpeed = source.AttackSpeed,
|
|
AttackMethodType = source.AttackMethodType,
|
|
AttackPropertyType = source.AttackPropertyType,
|
|
Tags = copiedTags
|
|
};
|
|
}
|
|
|
|
private static void ApplyUpgradeToStats(DefenseTowerStatsData stats)
|
|
{
|
|
if (stats == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
stats.AttackDamage = Mathf.Max(1, stats.AttackDamage + 3);
|
|
stats.AttackSpeed = Mathf.Max(0.01f, stats.AttackSpeed + 0.2f);
|
|
stats.AttackRange = Mathf.Max(0.1f, stats.AttackRange + 0.4f);
|
|
stats.RotateSpeed = Mathf.Max(1f, stats.RotateSpeed + 10f);
|
|
}
|
|
}
|
|
}
|