443 lines
14 KiB
C#
443 lines
14 KiB
C#
using GameFramework.DataTable;
|
|
using GeometryTD.DataTable;
|
|
using GeometryTD.Definition;
|
|
using UnityEngine;
|
|
|
|
namespace GeometryTD.CustomUtility
|
|
{
|
|
public static class IconColorGenerator
|
|
{
|
|
private const int StatCount = 5;
|
|
private const float MinSaturation = 0.4f;
|
|
private const float MinValue = 0.6f;
|
|
private const float TinyValue = 0.0001f;
|
|
|
|
private static readonly float[] HueMap = { 0f, 30f, 200f, 120f, 270f };
|
|
|
|
private static StatRange _attackDamageRange = new StatRange(1f, 300f);
|
|
private static StatRange _damageRandomRateRange = new StatRange(0f, 0.3f);
|
|
private static StatRange _rotateSpeedRange = new StatRange(10f, 240f);
|
|
private static StatRange _attackRangeRange = new StatRange(2f, 8f);
|
|
private static StatRange _attackSpeedRange = new StatRange(0.5f, 4f);
|
|
|
|
private static bool s_HasMuzzleRange;
|
|
private static bool s_HasBearingRange;
|
|
private static bool s_HasBaseRange;
|
|
|
|
public static Color GenerateForComponent(TowerCompItemData item)
|
|
{
|
|
if (item == null)
|
|
{
|
|
return Color.white;
|
|
}
|
|
|
|
float attackDamage = 0f;
|
|
float damageRandomRate = 0f;
|
|
float rotateSpeed = 0f;
|
|
float attackRange = 0f;
|
|
float attackSpeed = 0f;
|
|
|
|
if (item is MuzzleCompItemData muzzle)
|
|
{
|
|
attackDamage = ResolveArrayValue(muzzle.AttackDamage);
|
|
damageRandomRate = Mathf.Max(0f, muzzle.DamageRandomRate);
|
|
}
|
|
else if (item is BearingCompItemData bearing)
|
|
{
|
|
rotateSpeed = ResolveArrayValue(bearing.RotateSpeed);
|
|
attackRange = ResolveArrayValue(bearing.AttackRange);
|
|
}
|
|
else if (item is BaseCompItemData baseComp)
|
|
{
|
|
attackSpeed = ResolveArrayValue(baseComp.AttackSpeed);
|
|
}
|
|
|
|
return GenerateColor(
|
|
attackDamage,
|
|
damageRandomRate,
|
|
rotateSpeed,
|
|
attackRange,
|
|
attackSpeed,
|
|
item.SlotType);
|
|
}
|
|
|
|
public static Color GenerateForTower(TowerItemData tower)
|
|
{
|
|
if (tower?.Stats == null)
|
|
{
|
|
return Color.white;
|
|
}
|
|
|
|
TowerStatsData stats = tower.Stats;
|
|
return GenerateColor(
|
|
ResolveArrayValue(stats.AttackDamage),
|
|
stats.DamageRandomRate,
|
|
ResolveArrayValue(stats.RotateSpeed),
|
|
ResolveArrayValue(stats.AttackRange),
|
|
ResolveArrayValue(stats.AttackSpeed),
|
|
TowerCompSlotType.None);
|
|
}
|
|
|
|
private static Color GenerateColor(
|
|
float attackDamage,
|
|
float damageRandomRate,
|
|
float rotateSpeed,
|
|
float attackRange,
|
|
float attackSpeed,
|
|
TowerCompSlotType slotType)
|
|
{
|
|
TryRefreshRangesFromDataTables();
|
|
|
|
float[] normalizedStats = new float[StatCount];
|
|
normalizedStats[0] = Normalize(attackDamage, _attackDamageRange);
|
|
normalizedStats[1] = Normalize(damageRandomRate, _damageRandomRateRange);
|
|
normalizedStats[2] = Normalize(rotateSpeed, _rotateSpeedRange);
|
|
normalizedStats[3] = Normalize(attackRange, _attackRangeRange);
|
|
normalizedStats[4] = Normalize(attackSpeed, _attackSpeedRange);
|
|
|
|
FindTopTwoIndexes(normalizedStats, out int primaryIndex, out int secondaryIndex);
|
|
|
|
float denominator = normalizedStats[primaryIndex] + normalizedStats[secondaryIndex] + TinyValue;
|
|
float blendWeight = normalizedStats[secondaryIndex] / denominator;
|
|
float hue = LerpHue(HueMap[primaryIndex], HueMap[secondaryIndex], blendWeight);
|
|
hue = NormalizeHue(hue + ResolveSlotHueOffset(slotType));
|
|
|
|
float purity = normalizedStats[primaryIndex] - normalizedStats[secondaryIndex];
|
|
float saturation = Mathf.Lerp(0.45f, 0.9f, Mathf.Clamp01(purity * 1.5f));
|
|
saturation = Mathf.Max(saturation, MinSaturation);
|
|
|
|
float intensity = 0f;
|
|
for (int i = 0; i < normalizedStats.Length; i++)
|
|
{
|
|
intensity += normalizedStats[i];
|
|
}
|
|
|
|
float value = Mathf.Lerp(0.55f, 0.95f, intensity / StatCount);
|
|
value = Mathf.Max(value, MinValue);
|
|
|
|
Color color = Color.HSVToRGB(hue / 360f, saturation, value);
|
|
color.a = 1f;
|
|
return color;
|
|
}
|
|
|
|
private static void TryRefreshRangesFromDataTables()
|
|
{
|
|
if (GameEntry.DataTable == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!s_HasMuzzleRange &&
|
|
TryResolveMuzzleRange(out StatRange attackDamageRange, out StatRange damageRandomRateRange))
|
|
{
|
|
_attackDamageRange = attackDamageRange;
|
|
_damageRandomRateRange = damageRandomRateRange;
|
|
s_HasMuzzleRange = true;
|
|
}
|
|
|
|
if (!s_HasBearingRange &&
|
|
TryResolveBearingRange(out StatRange rotateSpeedRange, out StatRange attackRangeRange))
|
|
{
|
|
_rotateSpeedRange = rotateSpeedRange;
|
|
_attackRangeRange = attackRangeRange;
|
|
s_HasBearingRange = true;
|
|
}
|
|
|
|
if (!s_HasBaseRange && TryResolveBaseRange(out StatRange attackSpeedRange))
|
|
{
|
|
_attackSpeedRange = attackSpeedRange;
|
|
s_HasBaseRange = true;
|
|
}
|
|
}
|
|
|
|
private static bool TryResolveMuzzleRange(out StatRange attackDamageRange, out StatRange damageRandomRateRange)
|
|
{
|
|
attackDamageRange = _attackDamageRange;
|
|
damageRandomRateRange = _damageRandomRateRange;
|
|
|
|
IDataTable<DRMuzzleComp> dataTable = GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
|
|
if (dataTable == null || dataTable.Count <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DRMuzzleComp[] rows = dataTable.GetAllDataRows();
|
|
if (rows == null || rows.Length <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool hasAttackDamage = false;
|
|
bool hasDamageRandomRate = false;
|
|
float minAttackDamage = float.MaxValue;
|
|
float maxAttackDamage = float.MinValue;
|
|
float minDamageRandomRate = float.MaxValue;
|
|
float maxDamageRandomRate = float.MinValue;
|
|
|
|
for (int i = 0; i < rows.Length; i++)
|
|
{
|
|
DRMuzzleComp row = rows[i];
|
|
if (row == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IncludeValues(row.AttackDamage, ref minAttackDamage, ref maxAttackDamage, ref hasAttackDamage);
|
|
IncludeValue(Mathf.Max(0f, row.DamageRandomRate), ref minDamageRandomRate, ref maxDamageRandomRate,
|
|
ref hasDamageRandomRate);
|
|
}
|
|
|
|
if (!hasAttackDamage || !hasDamageRandomRate)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
attackDamageRange = BuildRange(minAttackDamage, maxAttackDamage, _attackDamageRange);
|
|
damageRandomRateRange = BuildRange(minDamageRandomRate, maxDamageRandomRate, _damageRandomRateRange);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryResolveBearingRange(out StatRange rotateSpeedRange, out StatRange attackRangeRange)
|
|
{
|
|
rotateSpeedRange = _rotateSpeedRange;
|
|
attackRangeRange = _attackRangeRange;
|
|
|
|
IDataTable<DRBearingComp> dataTable = GameEntry.DataTable.GetDataTable<DRBearingComp>();
|
|
if (dataTable == null || dataTable.Count <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DRBearingComp[] rows = dataTable.GetAllDataRows();
|
|
if (rows == null || rows.Length <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool hasRotateSpeed = false;
|
|
bool hasAttackRange = false;
|
|
float minRotateSpeed = float.MaxValue;
|
|
float maxRotateSpeed = float.MinValue;
|
|
float minAttackRange = float.MaxValue;
|
|
float maxAttackRange = float.MinValue;
|
|
|
|
for (int i = 0; i < rows.Length; i++)
|
|
{
|
|
DRBearingComp row = rows[i];
|
|
if (row == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IncludeValues(row.RotateSpeed, ref minRotateSpeed, ref maxRotateSpeed, ref hasRotateSpeed);
|
|
IncludeValues(row.AttackRange, ref minAttackRange, ref maxAttackRange, ref hasAttackRange);
|
|
}
|
|
|
|
if (!hasRotateSpeed || !hasAttackRange)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
rotateSpeedRange = BuildRange(minRotateSpeed, maxRotateSpeed, _rotateSpeedRange);
|
|
attackRangeRange = BuildRange(minAttackRange, maxAttackRange, _attackRangeRange);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryResolveBaseRange(out StatRange attackSpeedRange)
|
|
{
|
|
attackSpeedRange = _attackSpeedRange;
|
|
|
|
IDataTable<DRBaseComp> dataTable = GameEntry.DataTable.GetDataTable<DRBaseComp>();
|
|
if (dataTable == null || dataTable.Count <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DRBaseComp[] rows = dataTable.GetAllDataRows();
|
|
if (rows == null || rows.Length <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool hasAttackSpeed = false;
|
|
float minAttackSpeed = float.MaxValue;
|
|
float maxAttackSpeed = float.MinValue;
|
|
|
|
for (int i = 0; i < rows.Length; i++)
|
|
{
|
|
DRBaseComp row = rows[i];
|
|
if (row == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IncludeValues(row.AttackSpeed, ref minAttackSpeed, ref maxAttackSpeed, ref hasAttackSpeed);
|
|
}
|
|
|
|
if (!hasAttackSpeed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
attackSpeedRange = BuildRange(minAttackSpeed, maxAttackSpeed, _attackSpeedRange);
|
|
return true;
|
|
}
|
|
|
|
private static void FindTopTwoIndexes(float[] values, out int primaryIndex, out int secondaryIndex)
|
|
{
|
|
primaryIndex = 0;
|
|
secondaryIndex = 1;
|
|
|
|
if (values[secondaryIndex] > values[primaryIndex])
|
|
{
|
|
(primaryIndex, secondaryIndex) = (secondaryIndex, primaryIndex);
|
|
}
|
|
|
|
for (int i = 2; i < values.Length; i++)
|
|
{
|
|
float value = values[i];
|
|
if (value > values[primaryIndex])
|
|
{
|
|
secondaryIndex = primaryIndex;
|
|
primaryIndex = i;
|
|
}
|
|
else if (value > values[secondaryIndex])
|
|
{
|
|
secondaryIndex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static float ResolveSlotHueOffset(TowerCompSlotType slotType)
|
|
{
|
|
return slotType switch
|
|
{
|
|
TowerCompSlotType.Bearing => 15f,
|
|
TowerCompSlotType.Base => -15f,
|
|
_ => 0f
|
|
};
|
|
}
|
|
|
|
private static float ResolveArrayValue(int[] values)
|
|
{
|
|
if (values == null || values.Length <= 0)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
return Mathf.Max(0f, values[0]);
|
|
}
|
|
|
|
private static float ResolveArrayValue(float[] values)
|
|
{
|
|
if (values == null || values.Length <= 0)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
return Mathf.Max(0f, values[0]);
|
|
}
|
|
|
|
private static void IncludeValues(int[] values, ref float min, ref float max, ref bool hasValue)
|
|
{
|
|
if (values == null || values.Length <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
IncludeValue(Mathf.Max(0f, values[i]), ref min, ref max, ref hasValue);
|
|
}
|
|
}
|
|
|
|
private static void IncludeValues(float[] values, ref float min, ref float max, ref bool hasValue)
|
|
{
|
|
if (values == null || values.Length <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
IncludeValue(Mathf.Max(0f, values[i]), ref min, ref max, ref hasValue);
|
|
}
|
|
}
|
|
|
|
private static void IncludeValue(float value, ref float min, ref float max, ref bool hasValue)
|
|
{
|
|
if (float.IsNaN(value) || float.IsInfinity(value))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!hasValue)
|
|
{
|
|
min = value;
|
|
max = value;
|
|
hasValue = true;
|
|
return;
|
|
}
|
|
|
|
if (value < min)
|
|
{
|
|
min = value;
|
|
}
|
|
|
|
if (value > max)
|
|
{
|
|
max = value;
|
|
}
|
|
}
|
|
|
|
private static float Normalize(float value, StatRange range)
|
|
{
|
|
float denominator = Mathf.Max(TinyValue, range.Max - range.Min);
|
|
return Mathf.Clamp01((value - range.Min) / denominator);
|
|
}
|
|
|
|
private static StatRange BuildRange(float min, float max, StatRange fallback)
|
|
{
|
|
if (float.IsNaN(min) || float.IsInfinity(min) || float.IsNaN(max) || float.IsInfinity(max))
|
|
{
|
|
return fallback;
|
|
}
|
|
|
|
if (max - min < TinyValue)
|
|
{
|
|
return fallback;
|
|
}
|
|
|
|
return new StatRange(min, max);
|
|
}
|
|
|
|
private static float LerpHue(float fromDegree, float toDegree, float t)
|
|
{
|
|
float delta = Mathf.DeltaAngle(fromDegree, toDegree);
|
|
return fromDegree + delta * Mathf.Clamp01(t);
|
|
}
|
|
|
|
private static float NormalizeHue(float hueDegree)
|
|
{
|
|
float normalized = hueDegree % 360f;
|
|
if (normalized < 0f)
|
|
{
|
|
normalized += 360f;
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
private readonly struct StatRange
|
|
{
|
|
public StatRange(float min, float max)
|
|
{
|
|
Min = min;
|
|
Max = max;
|
|
}
|
|
|
|
public float Min { get; }
|
|
public float Max { get; }
|
|
}
|
|
}
|
|
} |