355 lines
14 KiB
C#
355 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, TowerStatsData buildTowerStats, Color muzzleColor, Color bearingColor, Color baseColor, 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;
|
|
}
|
|
|
|
TowerStatsData towerStats = CloneTowerStats(buildTowerStats);
|
|
if (towerStats == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int buildCost = GetBuildTowerCost(buildTowerCosts, buildIndex);
|
|
if (tryConsumeCoin != null && !tryConsumeCoin(buildCost))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!TryShowTowerEntity(foundationCell, towerStats, muzzleColor, bearingColor, baseColor, 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, Color muzzleColor, Color bearingColor, Color baseColor,
|
|
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, muzzleColor, bearingColor, baseColor);
|
|
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;
|
|
}
|
|
}
|
|
}
|