- S4-03 | 先固化 Tag 系统设计与首发范围

- S4-04 | 实现组件实例 Tag 的统一生成入口
- S4-05 | 实现组塔后的 Tag 汇总与展示入口
This commit is contained in:
SepComet
2026-03-09 21:06:43 +08:00
parent 73b7adedb8
commit 34ef001ef3
31 changed files with 1280 additions and 178 deletions
@@ -17,17 +17,20 @@ namespace GeometryTD.CustomComponent
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
private readonly Dictionary<RarityType, float> _rarityRollWeightBuffer = new();
private readonly Dictionary<TagType, RarityType> _tagMinRarityByTag = new();
private IDataTable<DROutGameDropPool> _drOutGameDropPool;
private IDataTable<DRMuzzleComp> _drMuzzleComp;
private IDataTable<DRBearingComp> _drBearingComp;
private IDataTable<DRBaseComp> _drBaseComp;
private IDataTable<DRTag> _drTag;
private long _nextDropItemInstanceId = 1;
public void Reset()
{
_eligibleDropPoolBuffer.Clear();
_rarityRollWeightBuffer.Clear();
_tagMinRarityByTag.Clear();
_nextDropItemInstanceId = 1;
}
@@ -304,6 +307,35 @@ namespace GeometryTD.CustomComponent
return _drOutGameDropPool;
}
private bool EnsureTagMinRarityLookup()
{
if (_tagMinRarityByTag.Count > 0)
{
return true;
}
_drTag ??= GameEntry.DataTable.GetDataTable<DRTag>();
if (_drTag == null)
{
return false;
}
DRTag[] rows = _drTag.GetAllDataRows();
for (int i = 0; i < rows.Length; i++)
{
DRTag row = rows[i];
if (row == null || row.Id <= 0)
{
continue;
}
TagType tagType = (TagType)row.Id;
_tagMinRarityByTag[tagType] = row.MinRarity;
}
return _tagMinRarityByTag.Count > 0;
}
private bool TryBuildDropItem(DROutGameDropPool row, out TowerCompItemData droppedItem)
{
droppedItem = null;
@@ -340,21 +372,34 @@ namespace GeometryTD.CustomComponent
return false;
}
if (!EnsureTagMinRarityLookup())
{
return false;
}
DRMuzzleComp config = _drMuzzleComp.GetDataRow(row.ItemId);
if (config == null)
{
return false;
}
long instanceId = _nextDropItemInstanceId++;
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity);
droppedItem = new MuzzleCompItemData
{
InstanceId = _nextDropItemInstanceId++,
InstanceId = instanceId,
ConfigId = config.Id,
Name = config.Name,
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity),
Rarity = rarity,
Endurance = 100f,
Constraint = config.Constraint,
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
Tags = InventoryTagRuleService.ResolveComponentTags(
config.PossibleTag,
rarity,
InventoryTagSourceType.Drop,
instanceId,
config.Id,
_tagMinRarityByTag),
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
DamageRandomRate = config.DamageRandomRate,
AttackMethodType = config.AttackMethodType
@@ -371,21 +416,34 @@ namespace GeometryTD.CustomComponent
return false;
}
if (!EnsureTagMinRarityLookup())
{
return false;
}
DRBearingComp config = _drBearingComp.GetDataRow(row.ItemId);
if (config == null)
{
return false;
}
long instanceId = _nextDropItemInstanceId++;
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity);
droppedItem = new BearingCompItemData
{
InstanceId = _nextDropItemInstanceId++,
InstanceId = instanceId,
ConfigId = config.Id,
Name = config.Name,
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity),
Rarity = rarity,
Endurance = 100f,
Constraint = config.Constraint,
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
Tags = InventoryTagRuleService.ResolveComponentTags(
config.PossibleTag,
rarity,
InventoryTagSourceType.Drop,
instanceId,
config.Id,
_tagMinRarityByTag),
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
};
@@ -401,21 +459,34 @@ namespace GeometryTD.CustomComponent
return false;
}
if (!EnsureTagMinRarityLookup())
{
return false;
}
DRBaseComp config = _drBaseComp.GetDataRow(row.ItemId);
if (config == null)
{
return false;
}
long instanceId = _nextDropItemInstanceId++;
RarityType rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity);
droppedItem = new BaseCompItemData
{
InstanceId = _nextDropItemInstanceId++,
InstanceId = instanceId,
ConfigId = config.Id,
Name = config.Name,
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(row.Rarity),
Rarity = rarity,
Endurance = 100f,
Constraint = config.Constraint,
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
Tags = InventoryTagRuleService.ResolveComponentTags(
config.PossibleTag,
rarity,
InventoryTagSourceType.Drop,
instanceId,
config.Id,
_tagMinRarityByTag),
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
AttackPropertyType = config.AttackPropertyType
};
@@ -107,8 +107,12 @@ namespace GeometryTD.CustomComponent
AttackSpeed = BuildLevelFloatArray(baseComp.AttackSpeed, baseComp.Rarity, baseConfig.AttackSpeedPerLevel),
AttackMethodType = muzzleComp.AttackMethodType,
AttackPropertyType = baseComp.AttackPropertyType,
Tags = MergeTags(muzzleComp.Tags, bearingComp.Tags, baseComp.Tags)
TagRuntimes = TowerTagAggregationService.AggregateTowerTags(
muzzleComp.Tags,
bearingComp.Tags,
baseComp.Tags)
};
stats.Tags = TowerTagAggregationService.FlattenUniqueTags(stats.TagRuntimes);
return true;
}
@@ -159,31 +163,6 @@ namespace GeometryTD.CustomComponent
return rarityBaseArray[rarityIndex];
}
private static TagType[] MergeTags(params TagType[][] sources)
{
HashSet<TagType> uniqueTags = new HashSet<TagType>();
if (sources != null)
{
for (int i = 0; i < sources.Length; i++)
{
TagType[] tags = sources[i];
if (tags == null)
{
continue;
}
for (int j = 0; j < tags.Length; j++)
{
uniqueTags.Add(tags[j]);
}
}
}
TagType[] mergedTags = new TagType[uniqueTags.Count];
uniqueTags.CopyTo(mergedTags);
return mergedTags;
}
private IDataTable<DRMuzzleComp> EnsureMuzzleTable()
{
_drMuzzleComp ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
@@ -0,0 +1,11 @@
using System;
namespace GeometryTD.Definition
{
[Serializable]
public sealed class TagRuntimeData
{
public TagType TagType { get; set; }
public int TotalStack { get; set; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 71784829e4844ae3acbd58fb4f88a1ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -18,6 +18,7 @@ namespace GeometryTD.Definition
public float[] AttackSpeed { get; set; }
public AttackMethodType AttackMethodType { get; set; }
public AttackPropertyType AttackPropertyType { get; set; }
public TagRuntimeData[] TagRuntimes { get; set; }
public TagType[] Tags { get; set; }
}
@@ -68,4 +69,4 @@ namespace GeometryTD.Definition
[JsonIgnore] public string ComposedIconKey { get; set; }
}
}
}
@@ -0,0 +1,10 @@
namespace GeometryTD.Definition
{
public enum InventoryTagSourceType : byte
{
Seed = 1,
Shop = 2,
Drop = 3,
Reward = 4,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a31a31d8d96e4d659fb5be2a8f76836d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
namespace GeometryTD.Definition
{
public static class InventoryTagRuleService
{
private static readonly IReadOnlyDictionary<TagType, RarityType> DefaultMinRarityByTag =
new Dictionary<TagType, RarityType>
{
{ TagType.Fire, RarityType.White },
{ TagType.Ice, RarityType.White },
{ TagType.Crit, RarityType.White },
{ TagType.Shatter, RarityType.Green },
{ TagType.Inferno, RarityType.Purple },
{ TagType.AbsoluteZero, RarityType.Purple },
{ TagType.Execution, RarityType.Purple },
};
private static readonly HashSet<TagType> SupportedLaunchTags = new HashSet<TagType>(DefaultMinRarityByTag.Keys);
public static TagType[] ResolveComponentTags(
IReadOnlyList<TagType> possibleTags,
RarityType rarity,
InventoryTagSourceType sourceType,
long itemInstanceId,
int configId,
IReadOnlyDictionary<TagType, RarityType> minRarityByTag = null)
{
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, minRarityByTag);
if (eligibleTags.Length <= 0)
{
return Array.Empty<TagType>();
}
Random random = new Random(BuildStableSeed(rarity, sourceType, itemInstanceId, configId));
int tagBudget = ResolveTagBudget(rarity, random);
if (tagBudget <= 0)
{
return Array.Empty<TagType>();
}
int finalCount = Math.Min(tagBudget, eligibleTags.Length);
List<TagType> pool = new List<TagType>(eligibleTags);
TagType[] result = new TagType[finalCount];
for (int i = 0; i < finalCount; i++)
{
int index = random.Next(0, pool.Count);
result[i] = pool[index];
pool.RemoveAt(index);
}
return result;
}
public static TagType[] GetEligibleTags(
IReadOnlyList<TagType> possibleTags,
RarityType rarity,
IReadOnlyDictionary<TagType, RarityType> minRarityByTag = null)
{
if (possibleTags == null || possibleTags.Count <= 0)
{
return Array.Empty<TagType>();
}
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
IReadOnlyDictionary<TagType, RarityType> rarityLookup = minRarityByTag ?? DefaultMinRarityByTag;
HashSet<TagType> uniqueTags = new HashSet<TagType>();
for (int i = 0; i < possibleTags.Count; i++)
{
TagType tagType = possibleTags[i];
if (!IsSupportedLaunchTag(tagType) || tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
{
continue;
}
if (!TryGetMinRarity(tagType, rarityLookup, out RarityType minRarity))
{
continue;
}
if (normalizedRarity < InventoryRarityRuleService.NormalizeComponentRarity(minRarity))
{
continue;
}
uniqueTags.Add(tagType);
}
if (uniqueTags.Count <= 0)
{
return Array.Empty<TagType>();
}
TagType[] result = new TagType[uniqueTags.Count];
uniqueTags.CopyTo(result);
Array.Sort(result);
return result;
}
public static int ResolveTagBudget(RarityType rarity, Random random)
{
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
random ??= new Random(0);
switch (normalizedRarity)
{
case RarityType.White:
return random.Next(0, 2);
case RarityType.Green:
return 1;
case RarityType.Blue:
return random.Next(1, 3);
case RarityType.Purple:
return 2;
case RarityType.Red:
return random.Next(2, 4);
default:
return 0;
}
}
private static bool IsSupportedLaunchTag(TagType tagType)
{
return SupportedLaunchTags.Contains(tagType);
}
private static bool TryGetMinRarity(
TagType tagType,
IReadOnlyDictionary<TagType, RarityType> rarityLookup,
out RarityType minRarity)
{
if (rarityLookup != null && rarityLookup.TryGetValue(tagType, out minRarity))
{
return true;
}
if (DefaultMinRarityByTag.TryGetValue(tagType, out minRarity))
{
return true;
}
minRarity = RarityType.White;
return false;
}
private static int BuildStableSeed(
RarityType rarity,
InventoryTagSourceType sourceType,
long itemInstanceId,
int configId)
{
unchecked
{
int seed = 17;
seed = seed * 31 + (int)InventoryRarityRuleService.NormalizeComponentRarity(rarity);
seed = seed * 31 + (int)sourceType;
seed = seed * 31 + configId;
seed = seed * 31 + (int)itemInstanceId;
seed = seed * 31 + (int)(itemInstanceId >> 32);
return seed;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56c9c8fd48d246a882bfae5f8c8c02e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
namespace GeometryTD.Definition
{
public static class TowerTagAggregationService
{
public static TagRuntimeData[] AggregateTowerTags(params TagType[][] componentTags)
{
Dictionary<TagType, int> stackByTag = new Dictionary<TagType, int>();
if (componentTags != null)
{
for (int i = 0; i < componentTags.Length; i++)
{
TagType[] tags = componentTags[i];
if (tags == null)
{
continue;
}
for (int j = 0; j < tags.Length; j++)
{
TagType tagType = tags[j];
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
{
continue;
}
if (stackByTag.TryGetValue(tagType, out int stack))
{
stackByTag[tagType] = stack + 1;
}
else
{
stackByTag[tagType] = 1;
}
}
}
}
if (stackByTag.Count <= 0)
{
return Array.Empty<TagRuntimeData>();
}
List<TagRuntimeData> runtimes = new List<TagRuntimeData>(stackByTag.Count);
foreach (KeyValuePair<TagType, int> pair in stackByTag)
{
runtimes.Add(new TagRuntimeData
{
TagType = pair.Key,
TotalStack = Math.Max(1, pair.Value)
});
}
runtimes.Sort((left, right) => left.TagType.CompareTo(right.TagType));
return runtimes.ToArray();
}
public static TagRuntimeData[] BuildRuntimeTagsFromUniqueTags(IReadOnlyList<TagType> tags)
{
if (tags == null || tags.Count <= 0)
{
return Array.Empty<TagRuntimeData>();
}
HashSet<TagType> uniqueTags = new HashSet<TagType>();
for (int i = 0; i < tags.Count; i++)
{
TagType tagType = tags[i];
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
{
continue;
}
uniqueTags.Add(tagType);
}
if (uniqueTags.Count <= 0)
{
return Array.Empty<TagRuntimeData>();
}
TagType[] orderedTags = new TagType[uniqueTags.Count];
uniqueTags.CopyTo(orderedTags);
Array.Sort(orderedTags);
TagRuntimeData[] runtimes = new TagRuntimeData[orderedTags.Length];
for (int i = 0; i < orderedTags.Length; i++)
{
runtimes[i] = new TagRuntimeData
{
TagType = orderedTags[i],
TotalStack = 1
};
}
return runtimes;
}
public static TagType[] FlattenUniqueTags(IReadOnlyList<TagRuntimeData> tagRuntimes)
{
if (tagRuntimes == null || tagRuntimes.Count <= 0)
{
return Array.Empty<TagType>();
}
List<TagType> tags = new List<TagType>(tagRuntimes.Count);
for (int i = 0; i < tagRuntimes.Count; i++)
{
TagRuntimeData runtime = tagRuntimes[i];
if (runtime == null || runtime.TagType == TagType.None || !Enum.IsDefined(typeof(TagType), runtime.TagType))
{
continue;
}
if (runtime.TotalStack <= 0)
{
continue;
}
tags.Add(runtime.TagType);
}
if (tags.Count <= 0)
{
return Array.Empty<TagType>();
}
tags.Sort();
return tags.ToArray();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a2b16218c9c4f90a90c12b975d9a5c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -18,7 +18,7 @@ namespace GeometryTD.UI
public string Title;
public string TypeText;
public string Description;
public TagType[] Tags;
public string[] TagTexts;
}
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
@@ -138,7 +138,7 @@ namespace GeometryTD.UI
tower.Name,
"Tower",
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
tower.Stats != null ? tower.Stats.Tags : null);
TagDisplayUtility.BuildTowerTagTexts(tower.Stats));
}
}
@@ -166,7 +166,7 @@ namespace GeometryTD.UI
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
item.Tags);
TagDisplayUtility.BuildTagTexts(item.Tags));
}
}
@@ -194,7 +194,7 @@ namespace GeometryTD.UI
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
item.Tags);
TagDisplayUtility.BuildTagTexts(item.Tags));
}
}
@@ -222,7 +222,7 @@ namespace GeometryTD.UI
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
item.Tags);
TagDisplayUtility.BuildTagTexts(item.Tags));
}
}
@@ -261,7 +261,7 @@ namespace GeometryTD.UI
return Color.white;
}
private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags)
private void AddItemDescSeed(long itemId, string title, string typeText, string description, string[] tagTexts)
{
if (itemId <= 0)
{
@@ -273,13 +273,13 @@ namespace GeometryTD.UI
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
TypeText = typeText ?? string.Empty,
Description = description ?? string.Empty,
Tags = CloneTags(tags)
TagTexts = CloneTagTexts(tagTexts)
};
}
private static TagType[] CloneTags(TagType[] tags)
private static string[] CloneTagTexts(string[] tagTexts)
{
return tags != null ? (TagType[])tags.Clone() : System.Array.Empty<TagType>();
return tagTexts != null ? (string[])tagTexts.Clone() : System.Array.Empty<string>();
}
private static string BuildComponentTypeText(TowerCompSlotType slotType)
@@ -374,7 +374,7 @@ namespace GeometryTD.UI
Description = seed.Description ?? string.Empty,
Price = 0,
TargetPos = args.TargetPos,
Tags = CloneTags(seed.Tags)
TagTexts = CloneTagTexts(seed.TagTexts)
});
}
@@ -405,4 +405,3 @@ namespace GeometryTD.UI
@@ -43,7 +43,7 @@ namespace GeometryTD.UI
tower.Name,
"Tower",
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
tower.Stats != null ? tower.Stats.Tags : null);
TagDisplayUtility.BuildTowerTagTexts(tower.Stats));
}
}
@@ -71,7 +71,7 @@ namespace GeometryTD.UI
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
item.Tags);
TagDisplayUtility.BuildTagTexts(item.Tags));
}
}
@@ -99,7 +99,7 @@ namespace GeometryTD.UI
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
item.Tags);
TagDisplayUtility.BuildTagTexts(item.Tags));
}
}
@@ -127,7 +127,7 @@ namespace GeometryTD.UI
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
item.Tags);
TagDisplayUtility.BuildTagTexts(item.Tags));
}
}
@@ -169,7 +169,7 @@ namespace GeometryTD.UI
_compAreaTowerIds.Add(itemContext.InstanceId);
}
private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags)
private void AddItemDescSeed(long itemId, string title, string typeText, string description, string[] tagTexts)
{
if (itemId <= 0)
{
@@ -181,15 +181,10 @@ namespace GeometryTD.UI
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
TypeText = typeText ?? string.Empty,
Description = description ?? string.Empty,
Tags = CloneTags(tags)
TagTexts = tagTexts != null ? (string[])tagTexts.Clone() : Array.Empty<string>()
};
}
private static TagType[] CloneTags(TagType[] tags)
{
return tags != null ? (TagType[])tags.Clone() : Array.Empty<TagType>();
}
private static string BuildComponentTypeText(TowerCompSlotType slotType)
{
return slotType switch
@@ -391,4 +386,3 @@ namespace GeometryTD.UI
}
}
}
@@ -22,7 +22,7 @@ namespace GeometryTD.UI
public string Title;
public string TypeText;
public string Description;
public TagType[] Tags;
public string[] TagTexts;
}
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
@@ -151,7 +151,7 @@ namespace GeometryTD.UI
Description = seed.Description ?? string.Empty,
Price = 0,
TargetPos = args.TargetPos,
Tags = CloneTags(seed.Tags)
TagTexts = CloneTagTexts(seed.TagTexts)
});
}
@@ -301,5 +301,9 @@ namespace GeometryTD.UI
}
#endregion
private static string[] CloneTagTexts(string[] tagTexts)
{
return tagTexts != null ? (string[])tagTexts.Clone() : System.Array.Empty<string>();
}
}
}
@@ -1,5 +1,3 @@
using System.Collections.Generic;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI
@@ -11,6 +9,6 @@ namespace GeometryTD.UI
public string Description;
public int Price;
public Vector3 TargetPos;
public List<TagType> Tags;
public TagItemContext[] Tags;
}
}
}
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using GameFramework.Event;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
@@ -35,18 +35,33 @@ namespace GeometryTD.UI
Description = rawData.Description,
Price = rawData.Price,
TargetPos = rawData.TargetPos,
Tags = BuildTags(rawData.Tags)
Tags = BuildTags(rawData)
};
}
private static List<TagType> BuildTags(TagType[] tags)
private static TagItemContext[] BuildTags(ItemDescFormRawData rawData)
{
if (tags == null || tags.Length <= 0)
string[] tagTexts = rawData?.TagTexts;
if ((tagTexts == null || tagTexts.Length <= 0) && rawData?.Tags != null)
{
return new List<TagType>();
tagTexts = TagDisplayUtility.BuildTagTexts(rawData.Tags);
}
return new List<TagType>(tags);
if (tagTexts == null || tagTexts.Length <= 0)
{
return System.Array.Empty<TagItemContext>();
}
TagItemContext[] contexts = new TagItemContext[tagTexts.Length];
for (int i = 0; i < tagTexts.Length; i++)
{
contexts[i] = new TagItemContext
{
TagName = tagTexts[i] ?? string.Empty
};
}
return contexts;
}
public int? OpenUI(ItemDescFormRawData rawData)
@@ -179,44 +179,24 @@ namespace GeometryTD.UI
private static TagItemContext[] BuildTagContexts(TagType[] tags)
{
if (tags == null || tags.Length <= 0)
string[] tagTexts = TagDisplayUtility.BuildTagTexts(tags);
if (tagTexts == null || tagTexts.Length <= 0)
{
return System.Array.Empty<TagItemContext>();
}
TagItemContext[] contexts = new TagItemContext[tags.Length];
for (int i = 0; i < tags.Length; i++)
TagItemContext[] contexts = new TagItemContext[tagTexts.Length];
for (int i = 0; i < tagTexts.Length; i++)
{
TagType tagType = tags[i];
contexts[i] = new TagItemContext
{
TagName = ResolveTagName(tagType)
TagName = tagTexts[i]
};
}
return contexts;
}
private static string ResolveTagName(TagType tagType)
{
if (tagType == TagType.None)
{
return string.Empty;
}
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
if (tagTable != null)
{
DRTag tagRow = tagTable.GetDataRow((int)tagType);
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
{
return tagRow.Name;
}
}
return tagType.ToString();
}
private void OnRewardSelected(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RewardSelectItemSelectedEventArgs args))
@@ -11,5 +11,6 @@ namespace GeometryTD.UI
public int Price;
public Vector3 TargetPos;
public TagType[] Tags;
public string[] TagTexts;
}
}
@@ -1,6 +1,4 @@
using System.Collections.Generic;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -249,11 +247,11 @@ namespace GeometryTD.UI
return value >= min && value <= max;
}
private void RefreshTags(List<TagType> tags)
private void RefreshTags(TagItemContext[] tags)
{
ClearTags();
if (_tagAreaParent == null || _tagItemPrefab == null || tags == null || tags.Count <= 0)
if (_tagAreaParent == null || _tagItemPrefab == null || tags == null || tags.Length <= 0)
{
return;
}
@@ -263,10 +261,10 @@ namespace GeometryTD.UI
_tagItemPrefab.SetActive(false);
}
for (int i = 0; i < tags.Count; i++)
for (int i = 0; i < tags.Length; i++)
{
string tagName = ResolveTagName(tags[i]);
if (string.IsNullOrWhiteSpace(tagName))
TagItemContext tagContext = tags[i];
if (tagContext == null || string.IsNullOrWhiteSpace(tagContext.TagName))
{
continue;
}
@@ -276,17 +274,14 @@ namespace GeometryTD.UI
if (tagGo.TryGetComponent(out TagItem tagItem))
{
tagItem.OnInit(new TagItemContext
{
TagName = tagName
});
tagItem.OnInit(tagContext);
}
else
{
TMP_Text tagText = tagGo.GetComponentInChildren<TMP_Text>(true);
if (tagText != null)
{
tagText.text = tagName;
tagText.text = tagContext.TagName;
}
}
@@ -308,26 +303,6 @@ namespace GeometryTD.UI
_runtimeTagItems.Clear();
}
private static string ResolveTagName(TagType tagType)
{
if (tagType == TagType.None)
{
return string.Empty;
}
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
if (tagTable != null)
{
DRTag tagRow = tagTable.GetDataRow((int)tagType);
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
{
return tagRow.Name;
}
}
return tagType.ToString();
}
private void CloseSelf()
{
GameEntry.UI.CloseUIForm(this);
@@ -1,6 +1,7 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GameFramework.Event;
using GeometryTD.CustomUtility;
using UnityEngine;
using UnityGameFramework.Runtime;
@@ -165,18 +166,7 @@ namespace GeometryTD.UI
private static string[] BuildTagTexts(TagType[] tags)
{
if (tags == null || tags.Length <= 0)
{
return System.Array.Empty<string>();
}
string[] results = new string[tags.Length];
for (int i = 0; i < tags.Length; i++)
{
results[i] = tags[i].ToString();
}
return results;
return TagDisplayUtility.BuildTagTexts(tags);
}
}
}
@@ -16,11 +16,13 @@ namespace GeometryTD.UI
private readonly List<GoodsItemRawData> _currentGoods = new List<GoodsItemRawData>(GoodsCount);
private readonly List<DRShopPrice> _shopPriceRows = new List<DRShopPrice>();
private readonly Dictionary<TagType, RarityType> _tagMinRarityByTag = new Dictionary<TagType, RarityType>();
private IDataTable<DRShopPrice> _shopPriceTable;
private IDataTable<DRMuzzleComp> _muzzleCompTable;
private IDataTable<DRBearingComp> _bearingCompTable;
private IDataTable<DRBaseComp> _baseCompTable;
private IDataTable<DRTag> _tagTable;
public bool PrepareForOpen()
{
@@ -99,8 +101,9 @@ namespace GeometryTD.UI
_muzzleCompTable ??= GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
_bearingCompTable ??= GameEntry.DataTable.GetDataTable<DRBearingComp>();
_baseCompTable ??= GameEntry.DataTable.GetDataTable<DRBaseComp>();
_tagTable ??= GameEntry.DataTable.GetDataTable<DRTag>();
if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null)
if (_shopPriceTable == null || _muzzleCompTable == null || _bearingCompTable == null || _baseCompTable == null || _tagTable == null)
{
Log.Warning("ShopFormUseCase.EnsureTables() failed. Missing required data tables.");
return false;
@@ -124,10 +127,13 @@ namespace GeometryTD.UI
}
}
EnsureTagMinRarityLookup();
return _shopPriceRows.Count > 0 &&
_muzzleCompTable.Count > 0 &&
_bearingCompTable.Count > 0 &&
_baseCompTable.Count > 0;
_baseCompTable.Count > 0 &&
_tagMinRarityByTag.Count > 0;
}
private bool TryBuildRandomGoodsItem(int goodsIndex, out GoodsItemRawData goodsItem)
@@ -176,15 +182,23 @@ namespace GeometryTD.UI
{
DRMuzzleComp[] rows = _muzzleCompTable.GetAllDataRows();
DRMuzzleComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
long instanceId = _nextTempInstanceId++;
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
return new MuzzleCompItemData
{
InstanceId = _nextTempInstanceId++,
InstanceId = instanceId,
ConfigId = config.Id,
Name = config.Name,
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity),
Rarity = normalizedRarity,
Endurance = 100f,
Constraint = config.Constraint,
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
Tags = InventoryTagRuleService.ResolveComponentTags(
config.PossibleTag,
normalizedRarity,
InventoryTagSourceType.Shop,
instanceId,
config.Id,
_tagMinRarityByTag),
AttackDamage = config.AttackDamage != null ? (int[])config.AttackDamage.Clone() : Array.Empty<int>(),
DamageRandomRate = config.DamageRandomRate,
AttackMethodType = config.AttackMethodType
@@ -195,15 +209,23 @@ namespace GeometryTD.UI
{
DRBearingComp[] rows = _bearingCompTable.GetAllDataRows();
DRBearingComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
long instanceId = _nextTempInstanceId++;
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
return new BearingCompItemData
{
InstanceId = _nextTempInstanceId++,
InstanceId = instanceId,
ConfigId = config.Id,
Name = config.Name,
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity),
Rarity = normalizedRarity,
Endurance = 100f,
Constraint = config.Constraint,
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
Tags = InventoryTagRuleService.ResolveComponentTags(
config.PossibleTag,
normalizedRarity,
InventoryTagSourceType.Shop,
instanceId,
config.Id,
_tagMinRarityByTag),
RotateSpeed = config.RotateSpeed != null ? (float[])config.RotateSpeed.Clone() : Array.Empty<float>(),
AttackRange = config.AttackRange != null ? (float[])config.AttackRange.Clone() : Array.Empty<float>()
};
@@ -213,20 +235,49 @@ namespace GeometryTD.UI
{
DRBaseComp[] rows = _baseCompTable.GetAllDataRows();
DRBaseComp config = rows[UnityEngine.Random.Range(0, rows.Length)];
long instanceId = _nextTempInstanceId++;
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
return new BaseCompItemData
{
InstanceId = _nextTempInstanceId++,
InstanceId = instanceId,
ConfigId = config.Id,
Name = config.Name,
Rarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity),
Rarity = normalizedRarity,
Endurance = 100f,
Constraint = config.Constraint,
Tags = config.PossibleTag != null ? (TagType[])config.PossibleTag.Clone() : Array.Empty<TagType>(),
Tags = InventoryTagRuleService.ResolveComponentTags(
config.PossibleTag,
normalizedRarity,
InventoryTagSourceType.Shop,
instanceId,
config.Id,
_tagMinRarityByTag),
AttackSpeed = config.AttackSpeed != null ? (float[])config.AttackSpeed.Clone() : Array.Empty<float>(),
AttackPropertyType = config.AttackPropertyType
};
}
private void EnsureTagMinRarityLookup()
{
if (_tagMinRarityByTag.Count > 0 || _tagTable == null)
{
return;
}
DRTag[] rows = _tagTable.GetAllDataRows();
for (int i = 0; i < rows.Length; i++)
{
DRTag row = rows[i];
if (row == null || row.Id <= 0)
{
continue;
}
TagType tagType = (TagType)row.Id;
_tagMinRarityByTag[tagType] = row.MinRarity;
}
}
private int ResolveRandomPrice(RarityType rarity)
{
for (int i = 0; i < _shopPriceRows.Count; i++)
@@ -184,6 +184,7 @@ namespace GeometryTD.CustomUtility
AttackSpeed = CloneFloatArray(source.AttackSpeed),
AttackMethodType = source.AttackMethodType,
AttackPropertyType = source.AttackPropertyType,
TagRuntimes = CloneTagRuntimes(source.TagRuntimes),
Tags = CloneTags(source.Tags)
};
}
@@ -202,9 +203,34 @@ namespace GeometryTD.CustomUtility
{
return source != null ? (TagType[])source.Clone() : Array.Empty<TagType>();
}
public static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] source)
{
if (source == null || source.Length <= 0)
{
return Array.Empty<TagRuntimeData>();
}
TagRuntimeData[] cloned = new TagRuntimeData[source.Length];
for (int i = 0; i < source.Length; i++)
{
TagRuntimeData runtime = source[i];
if (runtime == null)
{
continue;
}
cloned[i] = new TagRuntimeData
{
TagType = runtime.TagType,
TotalStack = runtime.TotalStack
};
}
return cloned;
}
}
}
@@ -4,6 +4,14 @@ namespace GeometryTD.CustomUtility
{
public static class InventorySeedUtility
{
private static readonly TagType[] FirePool = { TagType.Fire };
private static readonly TagType[] IceFreezePool = { TagType.Ice, TagType.FreezeMask };
private static readonly TagType[] CritPiercePool = { TagType.Pierce, TagType.Crit };
private static readonly TagType[] IceShatterPool = { TagType.Ice, TagType.Shatter };
private static readonly TagType[] PierceOverpenetratePool = { TagType.Pierce, TagType.Overpenetrate };
private static readonly TagType[] IceAbsoluteZeroPool = { TagType.Ice, TagType.AbsoluteZero };
private static readonly TagType[] PierceExecutionPool = { TagType.Pierce, TagType.Execution };
public static BackpackInventoryData CreateSampleInventory()
{
BackpackInventoryData inventory = new BackpackInventoryData
@@ -23,7 +31,7 @@ namespace GeometryTD.CustomUtility
DamageRandomRate = 0.05f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
Tags = ResolveSeedTags(FirePool, RarityType.Green, 10001, 1)
};
BearingCompItemData bearing = new BearingCompItemData
@@ -37,7 +45,7 @@ namespace GeometryTD.CustomUtility
RotateSpeed = new[] { 100f, 120f, 130f, 140f, 150f },
AttackRange = new[] { 3f, 4f, 5f, 6f, 8f },
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
Tags = ResolveSeedTags(FirePool, RarityType.Green, 20001, 1)
};
BaseCompItemData baseComp = new BaseCompItemData
@@ -51,7 +59,7 @@ namespace GeometryTD.CustomUtility
AttackSpeed = new[] { 1f, 2f, 3f, 3.5f, 0.7f },
AttackPropertyType = AttackPropertyType.Fire,
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
Tags = ResolveSeedTags(FirePool, RarityType.Green, 30001, 1)
};
TowerItemData tower = new TowerItemData
@@ -75,9 +83,13 @@ namespace GeometryTD.CustomUtility
AttackSpeed = new[] { 1.0f, 1.2f, 1.3f, 1.4f, 0.5f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Fire,
Tags = new[] { TagType.Fire, TagType.BurnSpread }
TagRuntimes = TowerTagAggregationService.AggregateTowerTags(
muzzle.Tags,
bearing.Tags,
baseComp.Tags)
}
};
tower.Stats.Tags = TowerTagAggregationService.FlattenUniqueTags(tower.Stats.TagRuntimes);
inventory.MuzzleComponents.Add(muzzle);
inventory.BaseComponents.Add(baseComp);
@@ -96,7 +108,7 @@ namespace GeometryTD.CustomUtility
DamageRandomRate = 0.01f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.FreezeMask }
Tags = ResolveSeedTags(IceFreezePool, RarityType.Blue, 10002, 2)
});
inventory.MuzzleComponents.Add(new MuzzleCompItemData
@@ -111,7 +123,7 @@ namespace GeometryTD.CustomUtility
DamageRandomRate = 0.02f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Crit }
Tags = ResolveSeedTags(CritPiercePool, RarityType.Purple, 10003, 3)
});
inventory.BearingComponents.Add(new BearingCompItemData
@@ -125,7 +137,7 @@ namespace GeometryTD.CustomUtility
RotateSpeed = new[] { 200f, 250f, 300f, 320f, 350f },
AttackRange = new[] { 6f, 6.5f, 7f, 8f, 8f },
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.Shatter }
Tags = ResolveSeedTags(IceShatterPool, RarityType.Blue, 20002, 2)
});
inventory.BearingComponents.Add(new BearingCompItemData
@@ -139,7 +151,7 @@ namespace GeometryTD.CustomUtility
RotateSpeed = new[] { 60f, 70f, 80f, 90f, 100f },
AttackRange = new[] { 4f, 4.5f, 5f, 5.5f, 6f },
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Overpenetrate }
Tags = ResolveSeedTags(PierceOverpenetratePool, RarityType.Purple, 20003, 3)
});
inventory.BaseComponents.Add(new BaseCompItemData
@@ -153,7 +165,7 @@ namespace GeometryTD.CustomUtility
AttackSpeed = new[] { 4f, 4.2f, 4.4f, 4.6f, 4.8f },
AttackPropertyType = AttackPropertyType.Ice,
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.AbsoluteZero }
Tags = ResolveSeedTags(IceAbsoluteZeroPool, RarityType.Blue, 30002, 2)
});
inventory.BaseComponents.Add(new BaseCompItemData
@@ -167,12 +179,22 @@ namespace GeometryTD.CustomUtility
AttackSpeed = new[] { 1f, 1f, 1f, 1f, 1f },
AttackPropertyType = AttackPropertyType.Physics,
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Execution }
Tags = ResolveSeedTags(PierceExecutionPool, RarityType.Purple, 30003, 3)
});
inventory.ParticipantTowerInstanceIds.Add(90001);
return inventory;
}
private static TagType[] ResolveSeedTags(TagType[] possibleTags, RarityType rarity, long instanceId, int configId)
{
return InventoryTagRuleService.ResolveComponentTags(
possibleTags,
rarity,
InventoryTagSourceType.Seed,
instanceId,
configId);
}
}
}
@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using GeometryTD.DataTable;
using GeometryTD.Definition;
namespace GeometryTD.CustomUtility
{
public static class TagDisplayUtility
{
public static string ResolveTagName(TagType tagType)
{
if (tagType == TagType.None)
{
return string.Empty;
}
if (GameEntry.DataTable != null)
{
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
if (tagTable != null)
{
DRTag tagRow = tagTable.GetDataRow((int)tagType);
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
{
return tagRow.Name;
}
}
}
return tagType.ToString();
}
public static string[] BuildTagTexts(IReadOnlyList<TagType> tags)
{
if (tags == null || tags.Count <= 0)
{
return Array.Empty<string>();
}
List<string> results = new List<string>(tags.Count);
for (int i = 0; i < tags.Count; i++)
{
string tagName = ResolveTagName(tags[i]);
if (!string.IsNullOrWhiteSpace(tagName))
{
results.Add(tagName);
}
}
return results.ToArray();
}
public static string[] BuildTagTexts(IReadOnlyList<TagRuntimeData> tagRuntimes)
{
if (tagRuntimes == null || tagRuntimes.Count <= 0)
{
return Array.Empty<string>();
}
List<string> results = new List<string>(tagRuntimes.Count);
for (int i = 0; i < tagRuntimes.Count; i++)
{
TagRuntimeData tagRuntime = tagRuntimes[i];
if (tagRuntime == null || tagRuntime.TagType == TagType.None || tagRuntime.TotalStack <= 0)
{
continue;
}
string tagName = ResolveTagName(tagRuntime.TagType);
if (string.IsNullOrWhiteSpace(tagName))
{
continue;
}
results.Add(tagRuntime.TotalStack > 1 ? $"{tagName} x{tagRuntime.TotalStack}" : tagName);
}
return results.ToArray();
}
public static string[] BuildTowerTagTexts(TowerStatsData towerStats)
{
if (towerStats == null)
{
return Array.Empty<string>();
}
if (towerStats.TagRuntimes != null && towerStats.TagRuntimes.Length > 0)
{
return BuildTagTexts(towerStats.TagRuntimes);
}
return BuildTagTexts(towerStats.Tags);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93b93ce08ff34afea2cdf0cfe8f417f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: