geometry-tower-defense/Assets/GameMain/Scripts/Scene/Map/TowerPlacementService.cs

349 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using GeometryTD.Definition;
using GeometryTD.Entity;
using GeometryTD.Entity.EntityData;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace GeometryTD.Map
{
public sealed class TowerPlacementService
{
private const int DefaultTowerTypeId = 401;
private const int MinTowerLevel = 0;
private const int MaxTowerLevel = 4;
private readonly Dictionary<Vector3Int, int> _towerEntityIdByFoundationCell = new();
private readonly Dictionary<int, Vector3Int> _foundationCellByTowerEntityId = new();
private readonly Dictionary<int, TowerStatsData> _towerStatsByEntityId = new();
private readonly Dictionary<int, int> _towerLevelByEntityId = new();
private readonly List<int> _towerEntityIdBuffer = new();
public IReadOnlyDictionary<Vector3Int, int> TowerEntityIdByFoundationCell => _towerEntityIdByFoundationCell;
public IReadOnlyDictionary<int, Vector3Int> FoundationCellByTowerEntityId => _foundationCellByTowerEntityId;
public bool IsTowerAtMaxLevel(int towerEntityId)
{
if (towerEntityId == 0)
{
return false;
}
TowerStatsData towerStats =
_towerStatsByEntityId.TryGetValue(towerEntityId, out TowerStatsData cachedStats)
? cachedStats
: null;
int currentLevel = GetTowerLevel(towerEntityId);
int maxLevel = ResolveMaxTowerLevel(towerStats);
return currentLevel >= maxLevel;
}
public bool TryBuildTower(Vector3Int foundationCell, Func<Vector3Int, bool> isFoundationCell, int buildIndex,
int[] buildTowerCosts, int towerTypeId, Tilemap tilemap, Func<int, bool> tryConsumeCoin,
Action<int> addCoin,
out int towerEntityId)
{
towerEntityId = 0;
if (isFoundationCell == null || !isFoundationCell(foundationCell))
{
return false;
}
if (_towerEntityIdByFoundationCell.ContainsKey(foundationCell))
{
return false;
}
int buildCost = GetBuildTowerCost(buildTowerCosts, buildIndex);
if (tryConsumeCoin != null && !tryConsumeCoin(buildCost))
{
return false;
}
TowerStatsData towerStats = BuildTowerStats(buildIndex);
if (!TryShowTowerEntity(foundationCell, towerStats, towerTypeId, tilemap, out int newTowerEntityId))
{
addCoin?.Invoke(buildCost);
return false;
}
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(towerStats);
_towerLevelByEntityId[newTowerEntityId] = MinTowerLevel;
towerEntityId = newTowerEntityId;
return true;
}
public bool TryUpgradeTower(int towerEntityId, int upgradeCost, int towerTypeId, Tilemap tilemap,
Func<int, bool> tryConsumeCoin, Action<int> addCoin, out int resultTowerEntityId,
out Vector3Int foundationCell)
{
resultTowerEntityId = 0;
foundationCell = default;
if (towerEntityId == 0 || !_foundationCellByTowerEntityId.TryGetValue(towerEntityId, out foundationCell))
{
return false;
}
TowerStatsData towerStats =
_towerStatsByEntityId.TryGetValue(towerEntityId, out TowerStatsData cachedStats)
? CloneTowerStats(cachedStats)
: BuildTowerStats(0);
int currentTowerLevel = GetTowerLevel(towerEntityId);
int maxTowerLevel = ResolveMaxTowerLevel(towerStats);
if (currentTowerLevel >= maxTowerLevel)
{
resultTowerEntityId = towerEntityId;
return false;
}
int requiredUpgradeCost = Mathf.Max(0, upgradeCost);
if (tryConsumeCoin != null && !tryConsumeCoin(requiredUpgradeCost))
{
resultTowerEntityId = towerEntityId;
return false;
}
int nextTowerLevel = Mathf.Clamp(currentTowerLevel + 1, MinTowerLevel, maxTowerLevel);
if (!TryApplyTowerStats(towerEntityId, towerStats, nextTowerLevel))
{
addCoin?.Invoke(requiredUpgradeCost);
resultTowerEntityId = towerEntityId;
return false;
}
_towerStatsByEntityId[towerEntityId] = CloneTowerStats(towerStats);
_towerLevelByEntityId[towerEntityId] = nextTowerLevel;
resultTowerEntityId = towerEntityId;
return true;
}
public bool TryDestroyTower(int towerEntityId, int destroyGain, Action<int> addCoin,
out Vector3Int foundationCell)
{
foundationCell = default;
if (towerEntityId == 0 || !_foundationCellByTowerEntityId.TryGetValue(towerEntityId, out foundationCell))
{
return false;
}
HideTowerEntity(towerEntityId);
_towerEntityIdByFoundationCell.Remove(foundationCell);
_foundationCellByTowerEntityId.Remove(towerEntityId);
_towerStatsByEntityId.Remove(towerEntityId);
_towerLevelByEntityId.Remove(towerEntityId);
addCoin?.Invoke(Mathf.Max(0, destroyGain));
return true;
}
public void HideAndClearAllPlacedTowers()
{
_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();
_towerLevelByEntityId.Clear();
_towerEntityIdBuffer.Clear();
}
public void ClearTracking()
{
_towerEntityIdByFoundationCell.Clear();
_foundationCellByTowerEntityId.Clear();
_towerStatsByEntityId.Clear();
_towerLevelByEntityId.Clear();
_towerEntityIdBuffer.Clear();
}
private static int GetBuildTowerCost(int[] buildTowerCosts, int buildIndex)
{
if (buildTowerCosts == null || buildIndex < 0 || buildIndex >= buildTowerCosts.Length)
{
return 0;
}
return Mathf.Max(0, buildTowerCosts[buildIndex]);
}
private static bool TryShowTowerEntity(Vector3Int foundationCell, TowerStatsData towerStats,
int towerTypeId,
Tilemap tilemap, 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 TowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats,
MinTowerLevel);
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 static TowerStatsData BuildTowerStats(int buildIndex)
{
switch (buildIndex)
{
case 0:
return new TowerStatsData
{
AttackDamage = new[] { 200, 220, 240, 260, 300 },
DamageRandomRate = 0f,
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = Array.Empty<TagType>()
};
case 1:
return new TowerStatsData
{
AttackDamage = new[] { 200, 220, 240, 260, 300 },
DamageRandomRate = 0f,
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Fire,
Tags = Array.Empty<TagType>()
};
case 2:
return new TowerStatsData
{
AttackDamage = new[] { 200, 220, 240, 260, 300 },
DamageRandomRate = 0f,
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Ice,
Tags = Array.Empty<TagType>()
};
case 3:
return new TowerStatsData
{
AttackDamage = new[] { 200, 220, 240, 260, 300 },
DamageRandomRate = 0f,
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Poison,
Tags = Array.Empty<TagType>()
};
default:
return new TowerStatsData
{
AttackDamage = new[] { 200, 220, 240, 260, 300 },
DamageRandomRate = 0f,
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = Array.Empty<TagType>()
};
}
}
private static TowerStatsData CloneTowerStats(TowerStatsData source)
{
if (source == null)
{
return BuildTowerStats(0);
}
TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty<TagType>();
return new TowerStatsData
{
AttackDamage = source.AttackDamage != null ? (int[])source.AttackDamage.Clone() : Array.Empty<int>(),
DamageRandomRate = source.DamageRandomRate,
RotateSpeed = source.RotateSpeed != null ? (float[])source.RotateSpeed.Clone() : Array.Empty<float>(),
AttackRange = source.AttackRange != null ? (float[])source.AttackRange.Clone() : Array.Empty<float>(),
AttackSpeed = source.AttackSpeed != null ? (float[])source.AttackSpeed.Clone() : Array.Empty<float>(),
AttackMethodType = source.AttackMethodType,
AttackPropertyType = source.AttackPropertyType,
Tags = copiedTags
};
}
private int GetTowerLevel(int towerEntityId)
{
if (towerEntityId == 0 || !_towerLevelByEntityId.TryGetValue(towerEntityId, out int towerLevel))
{
return MinTowerLevel;
}
return Mathf.Clamp(towerLevel, MinTowerLevel, MaxTowerLevel);
}
private static int ResolveMaxTowerLevel(TowerStatsData stats)
{
int maxCount = Mathf.Max(
GetLength(stats?.AttackDamage),
GetLength(stats?.RotateSpeed),
GetLength(stats?.AttackRange),
GetLength(stats?.AttackSpeed));
if (maxCount <= 0)
{
return MinTowerLevel;
}
return Mathf.Clamp(maxCount - 1, MinTowerLevel, MaxTowerLevel);
}
private static bool TryApplyTowerStats(int towerEntityId, TowerStatsData towerStats, int towerLevel)
{
if (towerEntityId == 0 || towerStats == null || GameEntry.Entity == null)
{
return false;
}
if (GameEntry.Entity.GetGameEntity(towerEntityId) is not TowerEntity towerEntity)
{
return false;
}
return towerEntity.TryApplyStats(towerStats, towerLevel);
}
private static int GetLength<T>(T[] values)
{
return values != null ? values.Length : 0;
}
}
}