301 lines
12 KiB
C#
301 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using GeometryTD.Definition;
|
|
using GeometryTD.Entity.EntityData;
|
|
using GeometryTD.Pathfinding;
|
|
using UnityEngine;
|
|
using UnityEngine.Tilemaps;
|
|
using UnityGameFramework.Runtime;
|
|
|
|
namespace GeometryTD.Map
|
|
{
|
|
public sealed class TowerPlacementService
|
|
{
|
|
private const int DefaultTowerTypeId = 401;
|
|
|
|
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();
|
|
|
|
public IReadOnlyDictionary<Vector3Int, int> TowerEntityIdByFoundationCell => _towerEntityIdByFoundationCell;
|
|
public IReadOnlyDictionary<int, Vector3Int> FoundationCellByTowerEntityId => _foundationCellByTowerEntityId;
|
|
|
|
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;
|
|
}
|
|
|
|
DefenseTowerStatsData 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);
|
|
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;
|
|
}
|
|
|
|
int requiredUpgradeCost = Mathf.Max(0, upgradeCost);
|
|
if (tryConsumeCoin != null && !tryConsumeCoin(requiredUpgradeCost))
|
|
{
|
|
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, towerTypeId, tilemap, out int newTowerEntityId))
|
|
{
|
|
if (TryShowTowerEntity(foundationCell, oldStats, towerTypeId, tilemap, out int fallbackTowerEntityId))
|
|
{
|
|
_towerEntityIdByFoundationCell[foundationCell] = fallbackTowerEntityId;
|
|
_foundationCellByTowerEntityId[fallbackTowerEntityId] = foundationCell;
|
|
_towerStatsByEntityId[fallbackTowerEntityId] = CloneTowerStats(oldStats);
|
|
resultTowerEntityId = fallbackTowerEntityId;
|
|
}
|
|
|
|
addCoin?.Invoke(requiredUpgradeCost);
|
|
return false;
|
|
}
|
|
|
|
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
|
|
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
|
|
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(upgradedStats);
|
|
resultTowerEntityId = newTowerEntityId;
|
|
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);
|
|
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();
|
|
_towerEntityIdBuffer.Clear();
|
|
}
|
|
|
|
public void ClearTracking()
|
|
{
|
|
_towerEntityIdByFoundationCell.Clear();
|
|
_foundationCellByTowerEntityId.Clear();
|
|
_towerStatsByEntityId.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, DefenseTowerStatsData 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 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 static DefenseTowerStatsData BuildTowerStats(int buildIndex)
|
|
{
|
|
switch (buildIndex)
|
|
{
|
|
case 0:
|
|
return new DefenseTowerStatsData
|
|
{
|
|
AttackDamage = 100,
|
|
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 = 100,
|
|
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);
|
|
}
|
|
}
|
|
}
|