S4 收尾

This commit is contained in:
2026-03-11 15:02:44 +08:00
parent 515fe95441
commit c019f9f527
103 changed files with 367 additions and 230 deletions
@@ -6,34 +6,34 @@ using NUnit.Framework;
namespace GeometryTD.Tests.EditMode
{
public sealed class InventoryTagRuleServiceTests
public sealed class ComponentTagGenerationServiceTests
{
private static readonly IReadOnlyDictionary<TagType, TagGenerationRuleData> RulesByTag =
new Dictionary<TagType, TagGenerationRuleData>
private static readonly IReadOnlyDictionary<TagType, TagGenerationRule> RulesByTag =
new Dictionary<TagType, TagGenerationRule>
{
{ TagType.Fire, new TagGenerationRuleData { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 20 } },
{ TagType.Ice, new TagGenerationRuleData { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 20 } },
{ TagType.Crit, new TagGenerationRuleData { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 20 } },
{ TagType.Shatter, new TagGenerationRuleData { TagType = TagType.Shatter, MinRarity = RarityType.Green, Weight = 15 } },
{ TagType.Inferno, new TagGenerationRuleData { TagType = TagType.Inferno, MinRarity = RarityType.Purple, Weight = 5 } },
{ TagType.AbsoluteZero, new TagGenerationRuleData { TagType = TagType.AbsoluteZero, MinRarity = RarityType.Purple, Weight = 5 } },
{ TagType.Execution, new TagGenerationRuleData { TagType = TagType.Execution, MinRarity = RarityType.Purple, Weight = 5 } },
{ TagType.Fire, new TagGenerationRule { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 20 } },
{ TagType.Ice, new TagGenerationRule { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 20 } },
{ TagType.Crit, new TagGenerationRule { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 20 } },
{ TagType.Shatter, new TagGenerationRule { TagType = TagType.Shatter, MinRarity = RarityType.Green, Weight = 15 } },
{ TagType.Inferno, new TagGenerationRule { TagType = TagType.Inferno, MinRarity = RarityType.Purple, Weight = 5 } },
{ TagType.AbsoluteZero, new TagGenerationRule { TagType = TagType.AbsoluteZero, MinRarity = RarityType.Purple, Weight = 5 } },
{ TagType.Execution, new TagGenerationRule { TagType = TagType.Execution, MinRarity = RarityType.Purple, Weight = 5 } },
};
private static readonly IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> RarityTagBudgetRulesByRarity =
new Dictionary<RarityType, RarityTagBudgetRuleData>
private static readonly IReadOnlyDictionary<RarityType, RarityTagBudgetRule> RarityTagBudgetRulesByRarity =
new Dictionary<RarityType, RarityTagBudgetRule>
{
{ 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 } },
{ RarityType.White, new RarityTagBudgetRule { Rarity = RarityType.White, MinCount = 0, MaxCount = 1 } },
{ RarityType.Green, new RarityTagBudgetRule { Rarity = RarityType.Green, MinCount = 1, MaxCount = 1 } },
{ RarityType.Blue, new RarityTagBudgetRule { Rarity = RarityType.Blue, MinCount = 1, MaxCount = 2 } },
{ RarityType.Purple, new RarityTagBudgetRule { Rarity = RarityType.Purple, MinCount = 2, MaxCount = 2 } },
{ RarityType.Red, new RarityTagBudgetRule { Rarity = RarityType.Red, MinCount = 2, MaxCount = 3 } },
};
[Test]
public void GetEligibleTags_Filters_Invalid_Unsupported_And_HighRarity_Tags()
{
TagType[] result = InventoryTagRuleService.GetEligibleTags(
TagType[] result = ComponentTagGenerationService.GetEligibleTags(
new[]
{
TagType.None,
@@ -52,7 +52,7 @@ namespace GeometryTD.Tests.EditMode
[Test]
public void ResolveComponentTags_Is_Deterministic_For_Same_Context()
{
TagType[] first = InventoryTagRuleService.ResolveComponentTags(
TagType[] first = ComponentTagGenerationService.ResolveComponentTags(
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter },
RarityType.Blue,
InventoryTagSourceType.Shop,
@@ -60,7 +60,7 @@ namespace GeometryTD.Tests.EditMode
7,
RulesByTag);
TagType[] second = InventoryTagRuleService.ResolveComponentTags(
TagType[] second = ComponentTagGenerationService.ResolveComponentTags(
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter },
RarityType.Blue,
InventoryTagSourceType.Shop,
@@ -74,7 +74,7 @@ namespace GeometryTD.Tests.EditMode
[Test]
public void ResolveComponentTags_Uses_Purple_Budget_And_Does_Not_Repeat()
{
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
TagType[] result = ComponentTagGenerationService.ResolveComponentTags(
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter, TagType.Inferno, TagType.Execution },
RarityType.Purple,
InventoryTagSourceType.Drop,
@@ -90,7 +90,7 @@ namespace GeometryTD.Tests.EditMode
[Test]
public void ResolveComponentTags_Falls_Back_When_Eligible_Count_Is_Lower_Than_Budget()
{
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
TagType[] result = ComponentTagGenerationService.ResolveComponentTags(
new[] { TagType.Fire, TagType.BurnSpread },
RarityType.Red,
InventoryTagSourceType.Seed,
@@ -105,15 +105,15 @@ namespace GeometryTD.Tests.EditMode
[Test]
public void ResolveComponentTags_Uses_Weight_Within_EligiblePool()
{
IReadOnlyDictionary<TagType, TagGenerationRuleData> weightedRules =
new Dictionary<TagType, TagGenerationRuleData>
IReadOnlyDictionary<TagType, TagGenerationRule> weightedRules =
new Dictionary<TagType, TagGenerationRule>
{
{ TagType.Fire, new TagGenerationRuleData { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 100 } },
{ TagType.Ice, new TagGenerationRuleData { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Crit, new TagGenerationRuleData { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Fire, new TagGenerationRule { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 100 } },
{ TagType.Ice, new TagGenerationRule { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Crit, new TagGenerationRule { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 1 } },
};
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
TagType[] result = ComponentTagGenerationService.ResolveComponentTags(
new[] { TagType.Fire, TagType.Ice, TagType.Crit },
RarityType.Green,
InventoryTagSourceType.Shop,
@@ -129,19 +129,19 @@ namespace GeometryTD.Tests.EditMode
[Test]
public void ResolveComponentTags_Prefers_HighWeight_Tag_In_ExpandedPool()
{
IReadOnlyDictionary<TagType, TagGenerationRuleData> weightedRules =
new Dictionary<TagType, TagGenerationRuleData>
IReadOnlyDictionary<TagType, TagGenerationRule> weightedRules =
new Dictionary<TagType, TagGenerationRule>
{
{ TagType.Fire, new TagGenerationRuleData { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 500 } },
{ TagType.Ice, new TagGenerationRuleData { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Crit, new TagGenerationRuleData { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Shatter, new TagGenerationRuleData { TagType = TagType.Shatter, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Fire, new TagGenerationRule { TagType = TagType.Fire, MinRarity = RarityType.White, Weight = 500 } },
{ TagType.Ice, new TagGenerationRule { TagType = TagType.Ice, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Crit, new TagGenerationRule { TagType = TagType.Crit, MinRarity = RarityType.White, Weight = 1 } },
{ TagType.Shatter, new TagGenerationRule { TagType = TagType.Shatter, MinRarity = RarityType.White, Weight = 1 } },
};
int fireCount = 0;
for (int i = 0; i < 32; i++)
{
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
TagType[] result = ComponentTagGenerationService.ResolveComponentTags(
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter },
RarityType.Green,
InventoryTagSourceType.Shop,
@@ -168,7 +168,7 @@ namespace GeometryTD.Tests.EditMode
TagGenerationRuleRegistry.LoadFromRows(new[] { fireRow });
Assert.That(TagGenerationRuleRegistry.TryGetRule(TagType.Fire, out TagGenerationRuleData rule), Is.True);
Assert.That(TagGenerationRuleRegistry.TryGetRule(TagType.Fire, out TagGenerationRule rule), Is.True);
Assert.That(rule.MinRarity, Is.EqualTo(RarityType.Green));
Assert.That(rule.Weight, Is.EqualTo(33));
@@ -178,15 +178,15 @@ namespace GeometryTD.Tests.EditMode
[Test]
public void ResolveRarityTagBudget_Uses_TableDriven_Range_Rules()
{
int whiteBudget = InventoryTagRuleService.ResolveRarityTagBudget(
int whiteBudget = ComponentTagGenerationService.ResolveRarityTagBudget(
RarityType.White,
new System.Random(0),
RarityTagBudgetRulesByRarity);
int greenBudget = InventoryTagRuleService.ResolveRarityTagBudget(
int greenBudget = ComponentTagGenerationService.ResolveRarityTagBudget(
RarityType.Green,
new System.Random(0),
RarityTagBudgetRulesByRarity);
int redBudget = InventoryTagRuleService.ResolveRarityTagBudget(
int redBudget = ComponentTagGenerationService.ResolveRarityTagBudget(
RarityType.Red,
new System.Random(0),
RarityTagBudgetRulesByRarity);
@@ -199,10 +199,10 @@ namespace GeometryTD.Tests.EditMode
[Test]
public void ResolveComponentTags_Uses_Custom_Purple_Budget_From_Rules()
{
IReadOnlyDictionary<RarityType, RarityTagBudgetRuleData> customBudgetRules =
new Dictionary<RarityType, RarityTagBudgetRuleData>(RarityTagBudgetRulesByRarity)
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> customBudgetRules =
new Dictionary<RarityType, RarityTagBudgetRule>(RarityTagBudgetRulesByRarity)
{
[RarityType.Purple] = new RarityTagBudgetRuleData
[RarityType.Purple] = new RarityTagBudgetRule
{
Rarity = RarityType.Purple,
MinCount = 3,
@@ -210,7 +210,7 @@ namespace GeometryTD.Tests.EditMode
}
};
TagType[] result = InventoryTagRuleService.ResolveComponentTags(
TagType[] result = ComponentTagGenerationService.ResolveComponentTags(
new[] { TagType.Fire, TagType.Ice, TagType.Crit, TagType.Shatter, TagType.Inferno, TagType.Execution },
RarityType.Purple,
InventoryTagSourceType.Drop,
@@ -231,7 +231,7 @@ namespace GeometryTD.Tests.EditMode
RarityTagBudgetRuleRegistry.LoadFromRows(new[] { purpleRow });
Assert.That(RarityTagBudgetRuleRegistry.TryGetRule(RarityType.Purple, out RarityTagBudgetRuleData rule), Is.True);
Assert.That(RarityTagBudgetRuleRegistry.TryGetRule(RarityType.Purple, out RarityTagBudgetRule rule), Is.True);
Assert.That(rule.MinCount, Is.EqualTo(3));
Assert.That(rule.MaxCount, Is.EqualTo(3));
+38 -23
View File
@@ -278,7 +278,7 @@ namespace GeometryTD.Tests.EditMode
public sealed class EnemyStatusTagRegistryTests
{
[Test]
public void Registry_Registers_All_Status_Tags()
public void Registry_Registers_Only_Independent_Status_Tags()
{
Assert.That(EnemyStatusTagRegistry.TryGetEffect(TagType.Fire, out IEnemyStatusTagEffect fireEffect), Is.True);
Assert.That(fireEffect, Is.TypeOf<FireTagEffect>());
@@ -286,50 +286,49 @@ namespace GeometryTD.Tests.EditMode
Assert.That(EnemyStatusTagRegistry.TryGetEffect(TagType.Ice, out IEnemyStatusTagEffect iceEffect), Is.True);
Assert.That(iceEffect, Is.TypeOf<IceTagEffect>());
Assert.That(EnemyStatusTagRegistry.TryGetEffect(TagType.Inferno, out IEnemyStatusTagEffect infernoEffect), Is.True);
Assert.That(infernoEffect, Is.TypeOf<InfernoTagEffect>());
Assert.That(EnemyStatusTagRegistry.TryGetEffect(TagType.AbsoluteZero, out IEnemyStatusTagEffect absoluteZeroEffect), Is.True);
Assert.That(absoluteZeroEffect, Is.TypeOf<AbsoluteZeroTagEffect>());
Assert.That(EnemyStatusTagRegistry.TryGetEffect(TagType.Inferno, out _), Is.False);
Assert.That(EnemyStatusTagRegistry.TryGetEffect(TagType.AbsoluteZero, out _), Is.False);
}
}
public sealed class TagConfigRegistryTests
public sealed class TagDefinitionRegistryTests
{
[Test]
public void Definitions_Register_All_Twelve_Tags()
{
Assert.That(TagConfigRegistry.Definitions.Count, Is.EqualTo(12));
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Fire, out _), Is.True);
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Execution, out _), Is.True);
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Overpenetrate, out _), Is.True);
Assert.That(TagDefinitionRegistry.Definitions.Count, Is.EqualTo(12));
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Fire, out _), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Execution, out _), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Overpenetrate, out _), Is.True);
}
[Test]
public void Definitions_Assign_Documented_Categories_And_Phases()
{
AssertDefinition(TagType.Fire, TagCategory.Status, TagTriggerPhase.OnAfterHit);
AssertDefinition(TagType.Inferno, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit);
AssertDefinition(TagType.Crit, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit);
AssertDefinition(TagType.Pierce, TagCategory.AttackShape, TagTriggerPhase.OnHit);
AssertDefinition(TagType.BurnSpread, TagCategory.AttackShape, TagTriggerPhase.OnKill);
AssertDefinition(TagType.AbsoluteZero, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit);
}
[Test]
public void Definitions_Mark_FirstBatch_SevenTags_AsImplemented()
{
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Shatter, out TagDefinitionData shatter), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Shatter, out TagDefinition shatter), Is.True);
Assert.That(shatter.Config, Is.TypeOf<ShatterTagConfig>());
Assert.That(((ShatterTagConfig)shatter.Config).IsImplemented, Is.True);
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Inferno, out TagDefinitionData inferno), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Inferno, out TagDefinition inferno), Is.True);
Assert.That(inferno.Config, Is.TypeOf<InfernoTagConfig>());
Assert.That(((InfernoTagConfig)inferno.Config).IsImplemented, Is.True);
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.AbsoluteZero, out TagDefinitionData absoluteZero), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.AbsoluteZero, out TagDefinition absoluteZero), Is.True);
Assert.That(absoluteZero.Config, Is.TypeOf<AbsoluteZeroTagConfig>());
Assert.That(((AbsoluteZeroTagConfig)absoluteZero.Config).IsImplemented, Is.True);
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Pierce, out TagDefinitionData pierce), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Pierce, out TagDefinition pierce), Is.True);
Assert.That(pierce.Config, Is.TypeOf<PierceTagConfig>());
Assert.That(((PierceTagConfig)pierce.Config).IsImplemented, Is.False);
}
@@ -341,9 +340,9 @@ namespace GeometryTD.Tests.EditMode
bool parsed = fireRow.ParseDataRow("\t1\t元素\tFire\tOnAfterHit\t火焰测试描述\t{\"BurnDurationSeconds\":4,\"BurnDamagePerSecondPerStack\":25,\"MaxEffectiveStack\":3}", null);
Assert.That(parsed, Is.True);
TagConfigRegistry.LoadFromRows(new[] { fireRow });
TagDefinitionRegistry.LoadFromRows(new[] { fireRow });
Assert.That(TagConfigRegistry.TryGetDefinition(TagType.Fire, out TagDefinitionData fire), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Fire, out TagDefinition fire), Is.True);
Assert.That(fire.TriggerPhase, Is.EqualTo(TagTriggerPhase.OnAfterHit));
Assert.That(fire.Description, Is.EqualTo("火焰测试描述"));
Assert.That(fire.Config, Is.TypeOf<FireTagConfig>());
@@ -353,7 +352,23 @@ namespace GeometryTD.Tests.EditMode
Assert.That(config.BurnDamagePerSecondPerStack, Is.EqualTo(25f).Within(0.001f));
Assert.That(config.MaxEffectiveStack, Is.EqualTo(3));
TagConfigRegistry.ResetToDefaults();
TagDefinitionRegistry.ResetToDefaults();
}
[Test]
public void ApplyTagRows_Overrides_IsImplemented_From_TagTable()
{
DRTag pierceRow = new DRTag();
Assert.That(pierceRow.ParseDataRow("\t9\t穿透\tPierce\tPenetrate\tWhite\t20\tTrue", null), Is.True);
TagDefinitionRegistry.ResetToDefaults();
TagDefinitionRegistry.ApplyTagRows(new[] { pierceRow });
Assert.That(TagDefinitionRegistry.TryGetDefinition(TagType.Pierce, out TagDefinition pierce), Is.True);
Assert.That(pierce.Config, Is.TypeOf<PierceTagConfig>());
Assert.That(((PierceTagConfig)pierce.Config).IsImplemented, Is.True);
TagDefinitionRegistry.ResetToDefaults();
}
[Test]
@@ -364,14 +379,14 @@ namespace GeometryTD.Tests.EditMode
Assert.That(fireRow.ParseDataRow("\t1\t元素\tFire\tOnAfterHit\t持续燃烧\t{\"BurnDurationSeconds\":3,\"BurnDamagePerSecondPerStack\":20,\"MaxEffectiveStack\":5}", null), Is.True);
Assert.That(shatterRow.ParseDataRow("\t7\t控制\tShatter\tOnBeforeHit\t对减速目标增伤\t{\"RequiresSlowedTarget\":true,\"DamageBonusPerStack\":0.25}", null), Is.True);
TagConfigRegistry.LoadFromRows(new[] { fireRow, shatterRow });
TagDefinitionRegistry.LoadFromRows(new[] { fireRow, shatterRow });
string description = TagDisplayUtility.BuildTagDescriptionText(new[] { TagType.Fire, TagType.Shatter });
Assert.That(description, Does.Contain("持续燃烧"));
Assert.That(description, Does.Contain("对减速目标增伤"));
TagConfigRegistry.ResetToDefaults();
TagDefinitionRegistry.ResetToDefaults();
}
[Test]
@@ -380,7 +395,7 @@ namespace GeometryTD.Tests.EditMode
DRTagConfig iceRow = new DRTagConfig();
Assert.That(iceRow.ParseDataRow("\t5\t控制\tIce\tOnAfterHit\t命中附加减速\t{\"SlowDurationSeconds\":2,\"SlowRatioPerStack\":0.2,\"MinMoveSpeedMultiplier\":0.4}", null), Is.True);
TagConfigRegistry.LoadFromRows(new[] { iceRow });
TagDefinitionRegistry.LoadFromRows(new[] { iceRow });
string description = TagDisplayUtility.BuildTagDescriptionText(new[]
{
@@ -390,7 +405,7 @@ namespace GeometryTD.Tests.EditMode
Assert.That(description, Does.Contain("Ice x2"));
Assert.That(description, Does.Contain("命中附加减速"));
TagConfigRegistry.ResetToDefaults();
TagDefinitionRegistry.ResetToDefaults();
}
[Test]
@@ -418,7 +433,7 @@ namespace GeometryTD.Tests.EditMode
private static void AssertDefinition(TagType tagType, TagCategory expectedCategory, TagTriggerPhase expectedPhase)
{
Assert.That(TagConfigRegistry.TryGetDefinition(tagType, out TagDefinitionData definition), Is.True);
Assert.That(TagDefinitionRegistry.TryGetDefinition(tagType, out TagDefinition definition), Is.True);
Assert.That(definition, Is.Not.Null);
Assert.That(definition.Category, Is.EqualTo(expectedCategory));
Assert.That(definition.TriggerPhase, Is.EqualTo(expectedPhase));