S4-07 process 3
- 组件 Tag 数量预算不再写死在 ResolveTagBudget(...) 里,而是走 RarityTagBudget.txt -> DRRarityTagBudget -> RarityTagBudgetRuleRegistry -> InventoryTagRuleService 这条表驱动链。 - TagBudget.txt:1 新增了按品质的 MinCount/MaxCount,预算缓存和加载入口分别在 RarityTagBudgetRuleRegistry.cs:7 和 ProcedurePreload.cs:18。 - 生成逻辑已经接到新规则,InventoryTagRuleService.cs:10 现在会先按 Tag.txt 过滤/加权,再按 RarityTagBudget 决定抽几个 Tag。
This commit is contained in:
parent
b1b68ebde5
commit
515fe95441
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Id 列1 RarityType MinCount MaxCount
|
||||||
|
# int RarityType int int
|
||||||
|
# 预算编号 策划备注 稀有度 最低Tag数量 最高Tag数量
|
||||||
|
1 White 0 1
|
||||||
|
2 Green 0 2
|
||||||
|
3 Blue 1 3
|
||||||
|
4 Purple 1 3
|
||||||
|
5 Red 2 4
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b4b5ba2b4cd50e48ae0ef4ea3e6cecf
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using GeometryTD.CustomUtility;
|
||||||
|
using GeometryTD.Definition;
|
||||||
|
using UnityGameFramework.Runtime;
|
||||||
|
|
||||||
|
namespace GeometryTD.DataTable
|
||||||
|
{
|
||||||
|
public sealed class DRRarityTagBudget : DataRowBase
|
||||||
|
{
|
||||||
|
private int m_Id;
|
||||||
|
|
||||||
|
public override int Id => m_Id;
|
||||||
|
|
||||||
|
public RarityType Rarity { get; private set; }
|
||||||
|
|
||||||
|
public int MinCount { get; private set; }
|
||||||
|
|
||||||
|
public int MaxCount { get; private set; }
|
||||||
|
|
||||||
|
public override bool ParseDataRow(string dataRowString, object userData)
|
||||||
|
{
|
||||||
|
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
|
||||||
|
for (int i = 0; i < columnStrings.Length; i++)
|
||||||
|
{
|
||||||
|
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
index++;
|
||||||
|
m_Id = int.Parse(columnStrings[index++]);
|
||||||
|
index++;
|
||||||
|
Rarity = EnumUtility<RarityType>.Get(columnStrings[index++]);
|
||||||
|
MinCount = int.Parse(columnStrings[index++]);
|
||||||
|
MaxCount = int.Parse(columnStrings[index++]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d5e6052cd3fe424d8b6ac53c33c063c5
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -13,7 +13,8 @@ namespace GeometryTD.Definition
|
||||||
InventoryTagSourceType sourceType,
|
InventoryTagSourceType sourceType,
|
||||||
long itemInstanceId,
|
long itemInstanceId,
|
||||||
int configId,
|
int configId,
|
||||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> rulesByTag = null)
|
IReadOnlyDictionary<TagType, TagGenerationRuleData> rulesByTag = null,
|
||||||
|
IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> rarityTagBudgetRulesByRarity = null)
|
||||||
{
|
{
|
||||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
||||||
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, ruleLookup);
|
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, ruleLookup);
|
||||||
|
|
@ -23,7 +24,7 @@ namespace GeometryTD.Definition
|
||||||
}
|
}
|
||||||
|
|
||||||
Random random = new Random(BuildStableSeed(rarity, sourceType, itemInstanceId, configId));
|
Random random = new Random(BuildStableSeed(rarity, sourceType, itemInstanceId, configId));
|
||||||
int tagBudget = ResolveTagBudget(rarity, random);
|
int tagBudget = ResolveRarityTagBudget(rarity, random, rarityTagBudgetRulesByRarity);
|
||||||
if (tagBudget <= 0)
|
if (tagBudget <= 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<TagType>();
|
return Array.Empty<TagType>();
|
||||||
|
|
@ -88,26 +89,25 @@ namespace GeometryTD.Definition
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int ResolveTagBudget(RarityType rarity, Random random)
|
public static int ResolveRarityTagBudget(
|
||||||
|
RarityType rarity,
|
||||||
|
Random random,
|
||||||
|
IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> rarityTagBudgetRulesByRarity = null)
|
||||||
{
|
{
|
||||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||||
random ??= new Random(0);
|
IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> ruleLookup = rarityTagBudgetRulesByRarity ?? RarityTagBudgetRuleRegistry.Rules;
|
||||||
|
RarityTagBudgetRuleData rule = GetRarityTagBudgetRule(normalizedRarity, ruleLookup);
|
||||||
|
|
||||||
switch (normalizedRarity)
|
Debug.Assert(rule.MinCount >= 0);
|
||||||
|
Debug.Assert(rule.MaxCount >= rule.MinCount);
|
||||||
|
|
||||||
|
if (rule.MinCount == rule.MaxCount)
|
||||||
{
|
{
|
||||||
case RarityType.White:
|
return rule.MinCount;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
random ??= new Random(0);
|
||||||
|
return random.Next(rule.MinCount, rule.MaxCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsSupportedLaunchTag(TagType tagType)
|
private static bool IsSupportedLaunchTag(TagType tagType)
|
||||||
|
|
@ -131,6 +131,15 @@ namespace GeometryTD.Definition
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static RarityTagBudgetRuleData GetRarityTagBudgetRule(
|
||||||
|
RarityType rarity,
|
||||||
|
IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> ruleLookup)
|
||||||
|
{
|
||||||
|
Debug.Assert(ruleLookup != null);
|
||||||
|
Debug.Assert(ruleLookup.TryGetValue(rarity, out _));
|
||||||
|
return ruleLookup[rarity];
|
||||||
|
}
|
||||||
|
|
||||||
private static int RollWeightedIndex(
|
private static int RollWeightedIndex(
|
||||||
IReadOnlyList<TagType> pool,
|
IReadOnlyList<TagType> pool,
|
||||||
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup,
|
IReadOnlyDictionary<TagType, TagGenerationRuleData> ruleLookup,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace GeometryTD.Definition
|
||||||
|
{
|
||||||
|
public sealed class RarityTagBudgetRuleData
|
||||||
|
{
|
||||||
|
public RarityType Rarity { get; set; }
|
||||||
|
public int MinCount { get; set; }
|
||||||
|
public int MaxCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2c0b098910eb4f8486d306ecf1812423
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GeometryTD.DataTable;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace GeometryTD.Definition
|
||||||
|
{
|
||||||
|
public static class RarityTagBudgetRuleRegistry
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<RarityType, RarityTagBudgetRuleData> RulesByRarity = CreateDefaultRules();
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> Rules => RulesByRarity;
|
||||||
|
|
||||||
|
public static void ResetToDefaults()
|
||||||
|
{
|
||||||
|
RulesByRarity.Clear();
|
||||||
|
foreach (KeyValuePair<RarityType, RarityTagBudgetRuleData> pair in CreateDefaultRules())
|
||||||
|
{
|
||||||
|
RulesByRarity.Add(pair.Key, pair.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadFromRows(IEnumerable<DRRarityTagBudget> rows)
|
||||||
|
{
|
||||||
|
ResetToDefaults();
|
||||||
|
foreach (DRRarityTagBudget row in rows)
|
||||||
|
{
|
||||||
|
ApplyRow(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetRule(RarityType rarity, out RarityTagBudgetRuleData rule)
|
||||||
|
{
|
||||||
|
return RulesByRarity.TryGetValue(rarity, out rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<RarityType, RarityTagBudgetRuleData> CreateDefaultRules()
|
||||||
|
{
|
||||||
|
return new Dictionary<RarityType, RarityTagBudgetRuleData>
|
||||||
|
{
|
||||||
|
[RarityType.White] = CreateRule(RarityType.White, 0, 1),
|
||||||
|
[RarityType.Green] = CreateRule(RarityType.Green, 1, 1),
|
||||||
|
[RarityType.Blue] = CreateRule(RarityType.Blue, 1, 2),
|
||||||
|
[RarityType.Purple] = CreateRule(RarityType.Purple, 2, 2),
|
||||||
|
[RarityType.Red] = CreateRule(RarityType.Red, 2, 3)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RarityTagBudgetRuleData CreateRule(RarityType rarity, int minCount, int maxCount)
|
||||||
|
{
|
||||||
|
Debug.Assert(rarity >= RarityType.White && rarity <= RarityType.Red);
|
||||||
|
Debug.Assert(minCount >= 0);
|
||||||
|
Debug.Assert(maxCount >= minCount);
|
||||||
|
|
||||||
|
return new RarityTagBudgetRuleData
|
||||||
|
{
|
||||||
|
Rarity = rarity,
|
||||||
|
MinCount = minCount,
|
||||||
|
MaxCount = maxCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyRow(DRRarityTagBudget row)
|
||||||
|
{
|
||||||
|
Debug.Assert(row != null);
|
||||||
|
Debug.Assert(row.Id > 0);
|
||||||
|
Debug.Assert(row.Rarity >= RarityType.White && row.Rarity <= RarityType.Red);
|
||||||
|
Debug.Assert(row.MinCount >= 0);
|
||||||
|
Debug.Assert(row.MaxCount >= row.MinCount);
|
||||||
|
|
||||||
|
RulesByRarity[row.Rarity] = CreateRule(row.Rarity, row.MinCount, row.MaxCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bc43423fff7c416fa0a8396e670da212
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -31,6 +31,7 @@ namespace GeometryTD.Procedure
|
||||||
"ShopPrice",
|
"ShopPrice",
|
||||||
"Sound",
|
"Sound",
|
||||||
"Tag",
|
"Tag",
|
||||||
|
"RarityTagBudget",
|
||||||
"TagConfig",
|
"TagConfig",
|
||||||
"OutGameDropPool",
|
"OutGameDropPool",
|
||||||
"UIForm",
|
"UIForm",
|
||||||
|
|
@ -213,6 +214,15 @@ namespace GeometryTD.Procedure
|
||||||
TagGenerationRuleRegistry.LoadFromRows(tagTable.GetAllDataRows());
|
TagGenerationRuleRegistry.LoadFromRows(tagTable.GetAllDataRows());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ne.DataTableAssetName == AssetUtility.GetDataTableAsset("RarityTagBudget", false))
|
||||||
|
{
|
||||||
|
var tagBudgetTable = GameEntry.DataTable.GetDataTable<DRRarityTagBudget>();
|
||||||
|
if (tagBudgetTable != null)
|
||||||
|
{
|
||||||
|
RarityTagBudgetRuleRegistry.LoadFromRows(tagBudgetTable.GetAllDataRows());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
|
private void OnLoadDataTableFailure(object sender, GameEventArgs e)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,16 @@ namespace GeometryTD.Tests.EditMode
|
||||||
{ TagType.Execution, new TagGenerationRuleData { TagType = TagType.Execution, MinRarity = RarityType.Purple, Weight = 5 } },
|
{ TagType.Execution, new TagGenerationRuleData { TagType = TagType.Execution, MinRarity = RarityType.Purple, Weight = 5 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> RarityTagBudgetRulesByRarity =
|
||||||
|
new Dictionary<RarityType, RarityTagBudgetRuleData>
|
||||||
|
{
|
||||||
|
{ RarityType.White, new RarityTagBudgetRuleData { Rarity = RarityType.White, MinCount = 0, MaxCount = 1 } },
|
||||||
|
{ RarityType.Green, new RarityTagBudgetRuleData { Rarity = RarityType.Green, MinCount = 1, MaxCount = 1 } },
|
||||||
|
{ RarityType.Blue, new RarityTagBudgetRuleData { Rarity = RarityType.Blue, MinCount = 1, MaxCount = 2 } },
|
||||||
|
{ RarityType.Purple, new RarityTagBudgetRuleData { Rarity = RarityType.Purple, MinCount = 2, MaxCount = 2 } },
|
||||||
|
{ RarityType.Red, new RarityTagBudgetRuleData { Rarity = RarityType.Red, MinCount = 2, MaxCount = 3 } },
|
||||||
|
};
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void GetEligibleTags_Filters_Invalid_Unsupported_And_HighRarity_Tags()
|
public void GetEligibleTags_Filters_Invalid_Unsupported_And_HighRarity_Tags()
|
||||||
{
|
{
|
||||||
|
|
@ -70,7 +80,8 @@ namespace GeometryTD.Tests.EditMode
|
||||||
InventoryTagSourceType.Drop,
|
InventoryTagSourceType.Drop,
|
||||||
9001,
|
9001,
|
||||||
4,
|
4,
|
||||||
RulesByTag);
|
RulesByTag,
|
||||||
|
RarityTagBudgetRulesByRarity);
|
||||||
|
|
||||||
Assert.That(result.Length, Is.EqualTo(2));
|
Assert.That(result.Length, Is.EqualTo(2));
|
||||||
Assert.That(new HashSet<TagType>(result).Count, Is.EqualTo(result.Length));
|
Assert.That(new HashSet<TagType>(result).Count, Is.EqualTo(result.Length));
|
||||||
|
|
@ -85,7 +96,8 @@ namespace GeometryTD.Tests.EditMode
|
||||||
InventoryTagSourceType.Seed,
|
InventoryTagSourceType.Seed,
|
||||||
42,
|
42,
|
||||||
1,
|
1,
|
||||||
RulesByTag);
|
RulesByTag,
|
||||||
|
RarityTagBudgetRulesByRarity);
|
||||||
|
|
||||||
Assert.That(result, Is.EqualTo(new[] { TagType.Fire }));
|
Assert.That(result, Is.EqualTo(new[] { TagType.Fire }));
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +119,8 @@ namespace GeometryTD.Tests.EditMode
|
||||||
InventoryTagSourceType.Shop,
|
InventoryTagSourceType.Shop,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
weightedRules);
|
weightedRules,
|
||||||
|
RarityTagBudgetRulesByRarity);
|
||||||
|
|
||||||
Assert.That(result, Has.Length.EqualTo(1));
|
Assert.That(result, Has.Length.EqualTo(1));
|
||||||
Assert.That(result[0], Is.EqualTo(TagType.Fire));
|
Assert.That(result[0], Is.EqualTo(TagType.Fire));
|
||||||
|
|
@ -134,7 +147,8 @@ namespace GeometryTD.Tests.EditMode
|
||||||
InventoryTagSourceType.Shop,
|
InventoryTagSourceType.Shop,
|
||||||
1000 + i,
|
1000 + i,
|
||||||
1,
|
1,
|
||||||
weightedRules);
|
weightedRules,
|
||||||
|
RarityTagBudgetRulesByRarity);
|
||||||
|
|
||||||
Assert.That(result, Has.Length.EqualTo(1));
|
Assert.That(result, Has.Length.EqualTo(1));
|
||||||
if (result[0] == TagType.Fire)
|
if (result[0] == TagType.Fire)
|
||||||
|
|
@ -161,6 +175,69 @@ namespace GeometryTD.Tests.EditMode
|
||||||
TagGenerationRuleRegistry.ResetToDefaults();
|
TagGenerationRuleRegistry.ResetToDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveRarityTagBudget_Uses_TableDriven_Range_Rules()
|
||||||
|
{
|
||||||
|
int whiteBudget = InventoryTagRuleService.ResolveRarityTagBudget(
|
||||||
|
RarityType.White,
|
||||||
|
new System.Random(0),
|
||||||
|
RarityTagBudgetRulesByRarity);
|
||||||
|
int greenBudget = InventoryTagRuleService.ResolveRarityTagBudget(
|
||||||
|
RarityType.Green,
|
||||||
|
new System.Random(0),
|
||||||
|
RarityTagBudgetRulesByRarity);
|
||||||
|
int redBudget = InventoryTagRuleService.ResolveRarityTagBudget(
|
||||||
|
RarityType.Red,
|
||||||
|
new System.Random(0),
|
||||||
|
RarityTagBudgetRulesByRarity);
|
||||||
|
|
||||||
|
Assert.That(whiteBudget, Is.InRange(0, 1));
|
||||||
|
Assert.That(greenBudget, Is.EqualTo(1));
|
||||||
|
Assert.That(redBudget, Is.InRange(2, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveComponentTags_Uses_Custom_Purple_Budget_From_Rules()
|
||||||
|
{
|
||||||
|
IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> customBudgetRules =
|
||||||
|
new Dictionary<RarityType, RarityTagBudgetRuleData>(RarityTagBudgetRulesByRarity)
|
||||||
|
{
|
||||||
|
[RarityType.Purple] = new RarityTagBudgetRuleData
|
||||||
|
{
|
||||||
|
Rarity = RarityType.Purple,
|
||||||
|
MinCount = 3,
|
||||||
|
MaxCount = 3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
|
||||||
|
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter, TagType.Inferno, TagType.Execution },
|
||||||
|
RarityType.Purple,
|
||||||
|
InventoryTagSourceType.Drop,
|
||||||
|
99,
|
||||||
|
7,
|
||||||
|
RulesByTag,
|
||||||
|
customBudgetRules);
|
||||||
|
|
||||||
|
Assert.That(result, Has.Length.EqualTo(3));
|
||||||
|
Assert.That(new HashSet<TagType>(result).Count, Is.EqualTo(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RarityTagBudgetRuleRegistry_LoadFromRows_Overrides_Purple_Budget()
|
||||||
|
{
|
||||||
|
DRRarityTagBudget purpleRow = new DRRarityTagBudget();
|
||||||
|
Assert.That(purpleRow.ParseDataRow("\t4\t\tPurple\t3\t3", null), Is.True);
|
||||||
|
|
||||||
|
RarityTagBudgetRuleRegistry.LoadFromRows(new[] { purpleRow });
|
||||||
|
|
||||||
|
Assert.That(RarityTagBudgetRuleRegistry.TryGetRule(RarityType.Purple, out RarityTagBudgetRuleData rule), Is.True);
|
||||||
|
Assert.That(rule.MinCount, Is.EqualTo(3));
|
||||||
|
Assert.That(rule.MaxCount, Is.EqualTo(3));
|
||||||
|
|
||||||
|
RarityTagBudgetRuleRegistry.ResetToDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void CreateSampleInventory_Generates_SeedTags_Within_Launch_Set_And_Matches_Tower_Stats()
|
public void CreateSampleInventory_Generates_SeedTags_Within_Launch_Set_And_Matches_Tower_Stats()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,8 @@
|
||||||
- `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 已进入分类与配置骨架,但仍属于后续扩展,不计入当前 `S4` 的完成标准。
|
- `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 已进入分类与配置骨架,但仍属于后续扩展,不计入当前 `S4` 的完成标准。
|
||||||
- `S4-07` 已进入第一阶段实现。当前仓库已具备 `TagConfig.txt -> DRTagConfig -> TagConfigRegistry -> ItemDescForm` 的消费闭环,首发 7 个 Tag 的触发阶段、描述与核心参数已可由表覆盖。
|
- `S4-07` 已进入第一阶段实现。当前仓库已具备 `TagConfig.txt -> DRTagConfig -> TagConfigRegistry -> ItemDescForm` 的消费闭环,首发 7 个 Tag 的触发阶段、描述与核心参数已可由表覆盖。
|
||||||
- `S4-07` 已推进到第二阶段。当前仓库同时具备 `Tag.txt -> DRTag -> TagGenerationRuleRegistry -> InventoryTagRuleService` 与 `TagConfig.txt -> DRTagConfig -> TagConfigRegistry -> ItemDescForm` 两条消费闭环,首发 7 个 Tag 的生成规则、触发阶段、描述与核心参数都已开始由表驱动。
|
- `S4-07` 已推进到第二阶段。当前仓库同时具备 `Tag.txt -> DRTag -> TagGenerationRuleRegistry -> InventoryTagRuleService` 与 `TagConfig.txt -> DRTagConfig -> TagConfigRegistry -> ItemDescForm` 两条消费闭环,首发 7 个 Tag 的生成规则、触发阶段、描述与核心参数都已开始由表驱动。
|
||||||
- `S4-07` 仍未完成最终收口。当前采用的是 `Tag.txt + TagConfig.txt` 的分层方案,而不是文档原方案里的单独 `TagRule`;更深的规则字段与完整命名口径仍待后续决定是否继续统一。
|
- `S4-07` 已推进到第三阶段。当前仓库新增 `RarityTagBudget.txt -> DRRarityTagBudget -> RarityTagBudgetRuleRegistry -> InventoryTagRuleService`,组件 Tag 数量预算不再写死在 `ResolveRarityTagBudget(...)` 的 `switch` 中,而是按品质从表驱动;至此生成链的候选过滤、权重抽取、数量预算三段都已进入 DataTable 消费闭环。
|
||||||
|
- `S4-07` 仍未完成最终收口。当前采用的是 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 的分层方案,而不是文档原方案里的单独 `TagRule`;更深的元数据字段与完整命名口径仍待后续决定是否继续统一。
|
||||||
|
|
||||||
### S4-06 当前代码状态
|
### S4-06 当前代码状态
|
||||||
|
|
||||||
|
|
@ -206,8 +207,8 @@
|
||||||
|
|
||||||
### S4 后续执行计划
|
### S4 后续执行计划
|
||||||
|
|
||||||
1. 继续推进 `S4-07`:在现有 `Tag.txt + TagConfig.txt` 分层方案基础上,决定是否补成文档中的完整 `TagRule`,或明确当前双表就是本阶段的等价收口方案。
|
1. 继续推进 `S4-07`:在现有 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 分层方案基础上,决定是否补成文档中的完整 `TagRule`,或明确当前三表就是本阶段的等价收口方案。
|
||||||
2. `S4-07` 完成标准仍以“表字段被实际消费、参数可解释、运行时与文档口径一致”为准;当前已完成首发 7 个 Tag 的生成规则、参数和说明配置化,后续重点转向是否还要继续配置化更深层的规则字段。
|
2. `S4-07` 完成标准仍以“表字段被实际消费、参数可解释、运行时与文档口径一致”为准;当前已完成首发 7 个 Tag 的生成规则、数量预算、参数和说明配置化,后续重点转向是否还要继续配置化更深层的元数据字段。
|
||||||
3. `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 继续留在后续扩展阶段,不因为 `S4-06` 完成而提前进入当前迭代。
|
3. `BurnSpread`、`IgniteBurst`、`FreezeMask`、`Pierce`、`Overpenetrate` 继续留在后续扩展阶段,不因为 `S4-06` 完成而提前进入当前迭代。
|
||||||
|
|
||||||
## 阶段 S5 - 收口耐久规则
|
## 阶段 S5 - 收口耐久规则
|
||||||
|
|
|
||||||
|
|
@ -77,24 +77,23 @@ Tag 系统需要同时满足四个目标:
|
||||||
|
|
||||||
### 4.1 配置层
|
### 4.1 配置层
|
||||||
|
|
||||||
后续配置层固定采用“`Tag.txt` 基础字典 + `TagRule` 规则表”两层结构,而不是继续把所有规则硬塞进现有 `Tag.txt`。
|
当前实现固定采用“`Tag.txt + RarityTagBudget.txt + TagConfig.txt`”三层结构,而不是继续把所有规则硬塞进现有 `Tag.txt`。
|
||||||
|
|
||||||
- `Tag.txt`
|
- `Tag.txt`
|
||||||
- 保留 `TagType`、`Name` 等基础字典信息
|
- 保留 `TagType`、`Name` 等基础字典与按 Tag 的生成规则
|
||||||
- `TagRule`
|
- 当前实际承载:`MinRarity`、`Weight`
|
||||||
- 承载运行规则字段,至少包括以下内容:
|
- `RarityTagBudget.txt`
|
||||||
- `MinRarity`
|
- 承载按品质的数量预算
|
||||||
- `Weight`
|
- 当前实际承载:`Rarity`、`MinCount`、`MaxCount`
|
||||||
- `MaxComponentStack`
|
- `TagConfig.txt`
|
||||||
- `TriggerPhase`
|
- 承载战斗与展示相关配置
|
||||||
- `Description`
|
- 当前实际承载:`TriggerPhase`、`Description`、`ParamJson`
|
||||||
- `ParamJson` 或等价参数字段
|
|
||||||
|
|
||||||
用途:
|
用途:
|
||||||
|
|
||||||
- `MinRarity`:该 Tag 最低可出现品质
|
- `MinRarity`:该 Tag 最低可出现品质
|
||||||
- `Weight`:同一候选池内抽取权重
|
- `Weight`:同一候选池内抽取权重
|
||||||
- `MaxComponentStack`:单组件允许的最大层数
|
- `MinCount / MaxCount`:该品质组件本次可抽取的 Tag 数量预算
|
||||||
- `TriggerPhase`:用于战斗结算路由
|
- `TriggerPhase`:用于战斗结算路由
|
||||||
- `ParamJson`:承载伤害倍率、持续时间、范围等效果参数
|
- `ParamJson`:承载伤害倍率、持续时间、范围等效果参数
|
||||||
|
|
||||||
|
|
@ -159,7 +158,7 @@ Tag 随机应发生在“组件实例创建时”,而不是组塔时。
|
||||||
|
|
||||||
### 5.3 Tag 数量预算
|
### 5.3 Tag 数量预算
|
||||||
|
|
||||||
每个品质都保留独立的 Tag 数量预算,而不是按概率硬编码在代码里。
|
每个品质都保留独立的 Tag 数量预算,并由 `RarityTagBudget.txt` 驱动,而不是按概率硬编码在代码里。
|
||||||
|
|
||||||
推荐默认值:
|
推荐默认值:
|
||||||
|
|
||||||
|
|
@ -448,7 +447,7 @@ Tag 触发阶段固定拆成四段:
|
||||||
## 11. 后续文档与代码动作
|
## 11. 后续文档与代码动作
|
||||||
|
|
||||||
1. 先基于本设计回写 `docs/CodeX-TODO.md` 的 `S4-03` 边界
|
1. 先基于本设计回写 `docs/CodeX-TODO.md` 的 `S4-03` 边界
|
||||||
2. 在 `S4-07` 中新增并消费 `TagRule` 表,保留 `Tag.txt` 作为基础字典
|
2. 在 `S4-07` 中补齐并消费 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 三层表结构
|
||||||
3. 新增 `TagGenerationService`,先收口实例生成
|
3. 新增 `TagGenerationService`,先收口实例生成
|
||||||
4. 新增 `TowerTagAggregationService`,替换当前简单并集逻辑
|
4. 新增 `TowerTagAggregationService`,替换当前简单并集逻辑
|
||||||
5. 评估 `AttackPayload` 是否并入现有 `BulletData`,还是单独引入
|
5. 评估 `AttackPayload` 是否并入现有 `BulletData`,还是单独引入
|
||||||
|
|
@ -461,7 +460,7 @@ Tag 触发阶段固定拆成四段:
|
||||||
- Tag 在组件实例创建时随机,不在组塔时随机
|
- Tag 在组件实例创建时随机,不在组塔时随机
|
||||||
- `PossibleTag` 是候选池,不是最终实例值
|
- `PossibleTag` 是候选池,不是最终实例值
|
||||||
- 塔级 Tag 汇总保留 `Stack`
|
- 塔级 Tag 汇总保留 `Stack`
|
||||||
- 配置层采用 `Tag.txt + TagRule` 双表结构
|
- 配置层采用 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 分层结构
|
||||||
- 第一批战斗效果优先做状态类与数值修正类,不优先做穿透 / 爆炸 / 传播
|
- 第一批战斗效果优先做状态类与数值修正类,不优先做穿透 / 爆炸 / 传播
|
||||||
- MVP 正式首发集合固定为 `Fire`、`Ice`、`Crit`、`Execution`、`Shatter`、`Inferno`、`AbsoluteZero`
|
- MVP 正式首发集合固定为 `Fire`、`Ice`、`Crit`、`Execution`、`Shatter`、`Inferno`、`AbsoluteZero`
|
||||||
- `BurnSpread` 明确后移,不作为当前首发集合成员
|
- `BurnSpread` 明确后移,不作为当前首发集合成员
|
||||||
|
|
@ -666,4 +665,4 @@ Execution 斩杀强化
|
||||||
|
|
||||||
BurnSpread 最多传播 3 次。
|
BurnSpread 最多传播 3 次。
|
||||||
|
|
||||||
否则后期怪物多时会帧率爆炸。
|
否则后期怪物多时会帧率爆炸。
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue