S6 回归与文档收尾
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Reflection;
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.UI;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -110,6 +111,198 @@ namespace GeometryTD.Tests.EditMode
|
||||
Is.EqualTo(CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildSettlementContext_On_Full_BaseHp_Win_Adds_BonusGold_And_Opens_RewardSelection()
|
||||
{
|
||||
CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||
resourceStore.InitializeForCombat(level);
|
||||
|
||||
CombatSettlementContext settlementContext = new CombatSettlementService().BuildSettlementContext(
|
||||
didCombatWin: true,
|
||||
currentLevel: level,
|
||||
defeatedEnemyCount: 5,
|
||||
resourceStore: resourceStore);
|
||||
|
||||
Assert.That(settlementContext.Result.DidCombatWin, Is.True);
|
||||
Assert.That(settlementContext.Result.FinalBaseHp, Is.EqualTo(100));
|
||||
Assert.That(settlementContext.Result.MaxBaseHp, Is.EqualTo(100));
|
||||
Assert.That(settlementContext.Result.GainedGold, Is.EqualTo(39));
|
||||
Assert.That(settlementContext.Result.RewardInventory.Gold, Is.EqualTo(39));
|
||||
Assert.That(settlementContext.Summary.GainedGold, Is.EqualTo(39));
|
||||
Assert.That(settlementContext.Flags.ShouldOpenRewardSelection, Is.True);
|
||||
Assert.That(settlementContext.Flags.DidEnterRewardSelection, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildSettlementContext_On_High_BaseHp_Win_Adds_SettlementGold_Without_RewardSelection()
|
||||
{
|
||||
CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||
resourceStore.InitializeForCombat(level);
|
||||
SetCurrentBaseHp(resourceStore, 90);
|
||||
|
||||
CombatSettlementContext settlementContext = new CombatSettlementService().BuildSettlementContext(
|
||||
didCombatWin: true,
|
||||
currentLevel: level,
|
||||
defeatedEnemyCount: 3,
|
||||
resourceStore: resourceStore);
|
||||
|
||||
Assert.That(settlementContext.Result.FinalBaseHp, Is.EqualTo(90));
|
||||
Assert.That(settlementContext.Result.GainedGold, Is.EqualTo(33));
|
||||
Assert.That(settlementContext.Result.RewardInventory.Gold, Is.EqualTo(33));
|
||||
Assert.That(settlementContext.Flags.ShouldOpenRewardSelection, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildSettlementContext_On_Loss_Keeps_DropGold_Without_SettlementBonus_Or_RewardSelection()
|
||||
{
|
||||
CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||
resourceStore.InitializeForCombat(level);
|
||||
resourceStore.AddEnemyDefeatedReward(gainedCoin: 0, gainedGold: 7);
|
||||
|
||||
CombatSettlementContext settlementContext = new CombatSettlementService().BuildSettlementContext(
|
||||
didCombatWin: false,
|
||||
currentLevel: level,
|
||||
defeatedEnemyCount: 3,
|
||||
resourceStore: resourceStore);
|
||||
|
||||
Assert.That(settlementContext.Result.DidCombatWin, Is.False);
|
||||
Assert.That(settlementContext.Result.GainedGold, Is.EqualTo(7));
|
||||
Assert.That(settlementContext.Result.RewardInventory.Gold, Is.EqualTo(7));
|
||||
Assert.That(settlementContext.Flags.ShouldOpenRewardSelection, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryPrepareRewardSelection_Returns_False_When_Required_Dependency_Is_Missing()
|
||||
{
|
||||
CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||
CombatSettlementContext settlementContext = new CombatSettlementContext();
|
||||
settlementContext.Flags.ShouldOpenRewardSelection = true;
|
||||
|
||||
bool prepared = new CombatSettlementService().TryPrepareRewardSelection(
|
||||
settlementContext,
|
||||
null,
|
||||
displayPhaseIndex: 1,
|
||||
themeType: LevelThemeType.Plain,
|
||||
rewardSelectFormUseCase: new RewardSelectFormUseCase(),
|
||||
onRewardSelected: _ => { },
|
||||
onGiveUp: null);
|
||||
|
||||
Assert.That(prepared, Is.False);
|
||||
Assert.That(settlementContext.Flags.ShouldOpenRewardSelection, Is.True);
|
||||
Assert.That(settlementContext.Flags.DidEnterRewardSelection, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ApplySelectedReward_Appends_Selected_Component_To_RewardInventory()
|
||||
{
|
||||
CombatSettlementContext settlementContext = new CombatSettlementContext();
|
||||
settlementContext.Result.RewardInventory = new BackpackInventoryData();
|
||||
MuzzleCompItemData reward = new MuzzleCompItemData
|
||||
{
|
||||
InstanceId = 70001,
|
||||
Name = "奖励枪口"
|
||||
};
|
||||
|
||||
new CombatSettlementService().ApplySelectedReward(
|
||||
settlementContext,
|
||||
new RewardSelectItemRawData
|
||||
{
|
||||
RewardId = reward.InstanceId,
|
||||
SlotType = TowerCompSlotType.Muzzle,
|
||||
SourceItem = reward
|
||||
});
|
||||
|
||||
Assert.That(settlementContext.Result.RewardInventory.MuzzleComponents.Count, Is.EqualTo(1));
|
||||
Assert.That(settlementContext.Result.RewardInventory.MuzzleComponents[0], Is.SameAs(reward));
|
||||
Assert.That(settlementContext.Summary.RewardInventory, Is.SameAs(settlementContext.Result.RewardInventory));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CommitSettlementInventory_Merges_Selected_Reward_Component_Into_PlayerInventory()
|
||||
{
|
||||
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||
resourceStore.InitializeForCombat(level);
|
||||
int muzzleCountBeforeCommit = inventoryComponent.GetInventorySnapshot().MuzzleComponents.Count;
|
||||
|
||||
CombatSettlementService settlementService = new CombatSettlementService();
|
||||
CombatSettlementContext settlementContext = settlementService.BuildSettlementContext(
|
||||
didCombatWin: true,
|
||||
currentLevel: level,
|
||||
defeatedEnemyCount: 4,
|
||||
resourceStore: resourceStore);
|
||||
MuzzleCompItemData reward = new MuzzleCompItemData
|
||||
{
|
||||
InstanceId = 70002,
|
||||
Name = "结算奖励枪口"
|
||||
};
|
||||
settlementService.ApplySelectedReward(
|
||||
settlementContext,
|
||||
new RewardSelectItemRawData
|
||||
{
|
||||
RewardId = reward.InstanceId,
|
||||
SlotType = TowerCompSlotType.Muzzle,
|
||||
SourceItem = reward
|
||||
});
|
||||
|
||||
settlementService.CommitSettlementInventory(settlementContext);
|
||||
|
||||
BackpackInventoryData committedInventory = inventoryComponent.GetInventorySnapshot();
|
||||
Assert.That(committedInventory.MuzzleComponents.Count, Is.EqualTo(muzzleCountBeforeCommit + 1));
|
||||
MuzzleCompItemData mergedReward = committedInventory.MuzzleComponents[^1];
|
||||
Assert.That(mergedReward.Name, Is.EqualTo("结算奖励枪口"));
|
||||
Assert.That(mergedReward.SlotType, Is.EqualTo(TowerCompSlotType.Muzzle));
|
||||
Assert.That(mergedReward.InstanceId, Is.Not.EqualTo(70002));
|
||||
Assert.That(committedInventory.Gold, Is.EqualTo(39));
|
||||
Assert.That(GetTowerComponents(committedInventory, 90001).Muzzle.Endurance, Is.EqualTo(99f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CommitSettlementInventory_Is_Idempotent_After_First_Commit()
|
||||
{
|
||||
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateInventory(100f, 100f, 100f));
|
||||
CombatRunResourceStore resourceStore = new CombatRunResourceStore();
|
||||
DRLevel level = CreateLevel(baseHp: 100, startCoin: 20, rewardGold: 30);
|
||||
resourceStore.InitializeForCombat(level);
|
||||
|
||||
CombatSettlementService settlementService = new CombatSettlementService();
|
||||
CombatSettlementContext settlementContext = settlementService.BuildSettlementContext(
|
||||
didCombatWin: true,
|
||||
currentLevel: level,
|
||||
defeatedEnemyCount: 2,
|
||||
resourceStore: resourceStore);
|
||||
|
||||
settlementService.CommitSettlementInventory(settlementContext);
|
||||
BackpackInventoryData firstCommittedInventory = inventoryComponent.GetInventorySnapshot();
|
||||
float firstEndurance = GetTowerComponents(firstCommittedInventory, 90001).Muzzle.Endurance;
|
||||
int firstAffectedTowerCount = settlementContext.Result.Endurance.AffectedTowerCount;
|
||||
int firstGold = firstCommittedInventory.Gold;
|
||||
|
||||
settlementService.CommitSettlementInventory(settlementContext);
|
||||
BackpackInventoryData secondCommittedInventory = inventoryComponent.GetInventorySnapshot();
|
||||
|
||||
Assert.That(settlementContext.Flags.IsCommitted, Is.True);
|
||||
Assert.That(GetTowerComponents(secondCommittedInventory, 90001).Muzzle.Endurance, Is.EqualTo(firstEndurance));
|
||||
Assert.That(settlementContext.Result.Endurance.AffectedTowerCount, Is.EqualTo(firstAffectedTowerCount));
|
||||
Assert.That(secondCommittedInventory.Gold, Is.EqualTo(firstGold));
|
||||
}
|
||||
|
||||
private static void SetCurrentBaseHp(CombatRunResourceStore resourceStore, int currentBaseHp)
|
||||
{
|
||||
FieldInfo backingField = typeof(CombatRunResourceStore).GetField(
|
||||
"<CurrentBaseHp>k__BackingField",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(backingField, Is.Not.Null);
|
||||
backingField.SetValue(resourceStore, currentBaseHp);
|
||||
}
|
||||
|
||||
private PlayerInventoryComponent CreateBoundPlayerInventory(BackpackInventoryData inventory)
|
||||
{
|
||||
_originalPlayerInventory = GameEntry.PlayerInventory;
|
||||
|
||||
@@ -175,6 +175,20 @@ namespace GeometryTD.Tests.EditMode
|
||||
TagGenerationRuleRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagGenerationRuleRegistry_Defaults_Align_With_Current_TagTable_Baseline()
|
||||
{
|
||||
TagGenerationRuleRegistry.ResetToDefaults();
|
||||
|
||||
Assert.That(TagGenerationRuleRegistry.TryGetRule(TagType.Ice, out TagGenerationRule iceRule), Is.True);
|
||||
Assert.That(iceRule.MinRarity, Is.EqualTo(RarityType.White));
|
||||
Assert.That(iceRule.Weight, Is.EqualTo(1));
|
||||
|
||||
Assert.That(TagGenerationRuleRegistry.TryGetRule(TagType.AbsoluteZero, out TagGenerationRule absoluteZeroRule), Is.True);
|
||||
Assert.That(absoluteZeroRule.MinRarity, Is.EqualTo(RarityType.Purple));
|
||||
Assert.That(absoluteZeroRule.Weight, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ResolveRarityTagBudget_Uses_TableDriven_Range_Rules()
|
||||
{
|
||||
@@ -238,6 +252,28 @@ namespace GeometryTD.Tests.EditMode
|
||||
RarityTagBudgetRuleRegistry.ResetToDefaults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RarityTagBudgetRuleRegistry_Defaults_Align_With_Current_BudgetTable_Baseline()
|
||||
{
|
||||
RarityTagBudgetRuleRegistry.ResetToDefaults();
|
||||
|
||||
Assert.That(RarityTagBudgetRuleRegistry.TryGetRule(RarityType.Green, out RarityTagBudgetRule greenRule), Is.True);
|
||||
Assert.That(greenRule.MinCount, Is.EqualTo(0));
|
||||
Assert.That(greenRule.MaxCount, Is.EqualTo(2));
|
||||
|
||||
Assert.That(RarityTagBudgetRuleRegistry.TryGetRule(RarityType.Blue, out RarityTagBudgetRule blueRule), Is.True);
|
||||
Assert.That(blueRule.MinCount, Is.EqualTo(1));
|
||||
Assert.That(blueRule.MaxCount, Is.EqualTo(3));
|
||||
|
||||
Assert.That(RarityTagBudgetRuleRegistry.TryGetRule(RarityType.Purple, out RarityTagBudgetRule purpleRule), Is.True);
|
||||
Assert.That(purpleRule.MinCount, Is.EqualTo(1));
|
||||
Assert.That(purpleRule.MaxCount, Is.EqualTo(3));
|
||||
|
||||
Assert.That(RarityTagBudgetRuleRegistry.TryGetRule(RarityType.Red, out RarityTagBudgetRule redRule), Is.True);
|
||||
Assert.That(redRule.MinCount, Is.EqualTo(2));
|
||||
Assert.That(redRule.MaxCount, Is.EqualTo(4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateSampleInventory_Generates_SeedTags_Within_Launch_Set_And_Matches_Tower_Stats()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using GameFramework.DataTable;
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Tests.EditMode
|
||||
{
|
||||
public sealed class PlayerInventoryTowerAssemblyServiceTests
|
||||
{
|
||||
private GameObject _inventoryObject;
|
||||
private PlayerInventoryComponent _originalPlayerInventory;
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
SetStaticPlayerInventory(_originalPlayerInventory);
|
||||
if (_inventoryObject != null)
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(_inventoryObject);
|
||||
_inventoryObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryAssembleTower_Builds_Expected_Rarity_Stats_And_Tags()
|
||||
{
|
||||
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(new BackpackInventoryData
|
||||
{
|
||||
MuzzleComponents =
|
||||
{
|
||||
new MuzzleCompItemData
|
||||
{
|
||||
InstanceId = 10001,
|
||||
ConfigId = 1,
|
||||
Name = "测试枪口",
|
||||
Rarity = RarityType.Green,
|
||||
AttackDamage = new[] { 10, 20, 30, 40, 50 },
|
||||
DamageRandomRate = 0.15f,
|
||||
AttackMethodType = AttackMethodType.NormalBullet,
|
||||
Tags = new[] { TagType.Fire }
|
||||
}
|
||||
},
|
||||
BearingComponents =
|
||||
{
|
||||
new BearingCompItemData
|
||||
{
|
||||
InstanceId = 20001,
|
||||
ConfigId = 1,
|
||||
Name = "测试轴承",
|
||||
Rarity = RarityType.Blue,
|
||||
RotateSpeed = new[] { 1f, 2f, 3f, 4f, 5f },
|
||||
AttackRange = new[] { 10f, 20f, 30f, 40f, 50f },
|
||||
Tags = new[] { TagType.Ice }
|
||||
}
|
||||
},
|
||||
BaseComponents =
|
||||
{
|
||||
new BaseCompItemData
|
||||
{
|
||||
InstanceId = 30001,
|
||||
ConfigId = 1,
|
||||
Name = "测试底座",
|
||||
Rarity = RarityType.Purple,
|
||||
AttackSpeed = new[] { 2f, 4f, 6f, 8f, 10f },
|
||||
AttackPropertyType = AttackPropertyType.Fire,
|
||||
Tags = new[] { TagType.Fire }
|
||||
}
|
||||
}
|
||||
});
|
||||
InjectAssemblyTables(
|
||||
inventoryComponent,
|
||||
new FakeDataTable<DRMuzzleComp>(CreateMuzzleRow()),
|
||||
new FakeDataTable<DRBearingComp>(CreateBearingRow()),
|
||||
new FakeDataTable<DRBaseComp>(CreateBaseRow()));
|
||||
|
||||
bool assembled = inventoryComponent.TryAssembleTower(10001, 20001, 30001, out TowerItemData tower);
|
||||
BackpackInventoryData committedInventory = inventoryComponent.GetInventorySnapshot();
|
||||
|
||||
Assert.That(assembled, Is.True);
|
||||
Assert.That(tower, Is.Not.Null);
|
||||
Assert.That(tower.Rarity, Is.EqualTo(RarityType.Blue));
|
||||
Assert.That(tower.Stats.AttackDamage, Is.EqualTo(new[] { 20, 23, 26, 29, 32 }));
|
||||
Assert.That(tower.Stats.RotateSpeed, Is.EqualTo(new[] { 3f, 3.5f, 4f, 4.5f, 5f }));
|
||||
Assert.That(tower.Stats.AttackRange, Is.EqualTo(new[] { 30f, 31f, 32f, 33f, 34f }));
|
||||
Assert.That(tower.Stats.AttackSpeed, Is.EqualTo(new[] { 8f, 7.75f, 7.5f, 7.25f, 7f }));
|
||||
Assert.That(tower.Stats.DamageRandomRate, Is.EqualTo(0.15f));
|
||||
Assert.That(tower.Stats.AttackMethodType, Is.EqualTo(AttackMethodType.NormalBullet));
|
||||
Assert.That(tower.Stats.AttackPropertyType, Is.EqualTo(AttackPropertyType.Fire));
|
||||
Assert.That(tower.Stats.Tags, Is.EqualTo(new[] { TagType.Fire, TagType.Ice }));
|
||||
Assert.That(tower.Stats.TagRuntimes, Has.Length.EqualTo(2));
|
||||
Assert.That(tower.Stats.TagRuntimes[0].TagType, Is.EqualTo(TagType.Fire));
|
||||
Assert.That(tower.Stats.TagRuntimes[0].TotalStack, Is.EqualTo(2));
|
||||
Assert.That(tower.Stats.TagRuntimes[1].TagType, Is.EqualTo(TagType.Ice));
|
||||
Assert.That(tower.Stats.TagRuntimes[1].TotalStack, Is.EqualTo(1));
|
||||
Assert.That(committedInventory.Towers, Has.Count.EqualTo(1));
|
||||
Assert.That(committedInventory.MuzzleComponents[0].IsAssembledIntoTower, Is.True);
|
||||
Assert.That(committedInventory.BearingComponents[0].IsAssembledIntoTower, Is.True);
|
||||
Assert.That(committedInventory.BaseComponents[0].IsAssembledIntoTower, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryAssembleTower_Returns_False_When_Required_Config_Row_Is_Missing()
|
||||
{
|
||||
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(new BackpackInventoryData
|
||||
{
|
||||
MuzzleComponents =
|
||||
{
|
||||
new MuzzleCompItemData
|
||||
{
|
||||
InstanceId = 10001,
|
||||
ConfigId = 1,
|
||||
Rarity = RarityType.White,
|
||||
AttackDamage = new[] { 10, 20, 30, 40, 50 },
|
||||
AttackMethodType = AttackMethodType.NormalBullet
|
||||
}
|
||||
},
|
||||
BearingComponents =
|
||||
{
|
||||
new BearingCompItemData
|
||||
{
|
||||
InstanceId = 20001,
|
||||
ConfigId = 1,
|
||||
Rarity = RarityType.White,
|
||||
RotateSpeed = new[] { 1f, 2f, 3f, 4f, 5f },
|
||||
AttackRange = new[] { 1f, 2f, 3f, 4f, 5f }
|
||||
}
|
||||
},
|
||||
BaseComponents =
|
||||
{
|
||||
new BaseCompItemData
|
||||
{
|
||||
InstanceId = 30001,
|
||||
ConfigId = 999,
|
||||
Rarity = RarityType.White,
|
||||
AttackSpeed = new[] { 1f, 2f, 3f, 4f, 5f },
|
||||
AttackPropertyType = AttackPropertyType.Physics
|
||||
}
|
||||
}
|
||||
});
|
||||
InjectAssemblyTables(
|
||||
inventoryComponent,
|
||||
new FakeDataTable<DRMuzzleComp>(CreateMuzzleRow()),
|
||||
new FakeDataTable<DRBearingComp>(CreateBearingRow()),
|
||||
new FakeDataTable<DRBaseComp>(CreateBaseRow()));
|
||||
|
||||
bool assembled = inventoryComponent.TryAssembleTower(10001, 20001, 30001, out TowerItemData tower);
|
||||
BackpackInventoryData committedInventory = inventoryComponent.GetInventorySnapshot();
|
||||
|
||||
Assert.That(assembled, Is.False);
|
||||
Assert.That(tower, Is.Null);
|
||||
Assert.That(committedInventory.Towers, Has.Count.EqualTo(0));
|
||||
Assert.That(committedInventory.MuzzleComponents[0].IsAssembledIntoTower, Is.False);
|
||||
Assert.That(committedInventory.BearingComponents[0].IsAssembledIntoTower, Is.False);
|
||||
Assert.That(committedInventory.BaseComponents[0].IsAssembledIntoTower, Is.False);
|
||||
}
|
||||
|
||||
private PlayerInventoryComponent CreateBoundPlayerInventory(BackpackInventoryData inventory)
|
||||
{
|
||||
_originalPlayerInventory = GameEntry.PlayerInventory;
|
||||
_inventoryObject = new GameObject("TestPlayerInventory");
|
||||
PlayerInventoryComponent inventoryComponent = _inventoryObject.AddComponent<PlayerInventoryComponent>();
|
||||
SetStaticPlayerInventory(inventoryComponent);
|
||||
inventoryComponent.ReplaceInventorySnapshot(inventory);
|
||||
return inventoryComponent;
|
||||
}
|
||||
|
||||
private static void SetStaticPlayerInventory(PlayerInventoryComponent inventoryComponent)
|
||||
{
|
||||
FieldInfo backingField = typeof(GameEntry).GetField(
|
||||
"<PlayerInventory>k__BackingField",
|
||||
BindingFlags.Static | BindingFlags.NonPublic);
|
||||
Assert.That(backingField, Is.Not.Null);
|
||||
backingField.SetValue(null, inventoryComponent);
|
||||
}
|
||||
|
||||
private static void InjectAssemblyTables(
|
||||
PlayerInventoryComponent inventoryComponent,
|
||||
IDataTable<DRMuzzleComp> muzzleTable,
|
||||
IDataTable<DRBearingComp> bearingTable,
|
||||
IDataTable<DRBaseComp> baseTable)
|
||||
{
|
||||
FieldInfo serviceField = typeof(PlayerInventoryComponent).GetField(
|
||||
"_towerAssemblyService",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(serviceField, Is.Not.Null);
|
||||
object assemblyService = serviceField.GetValue(inventoryComponent);
|
||||
Assert.That(assemblyService, Is.Not.Null);
|
||||
|
||||
SetPrivateField(assemblyService, "_drMuzzleComp", muzzleTable);
|
||||
SetPrivateField(assemblyService, "_drBearingComp", bearingTable);
|
||||
SetPrivateField(assemblyService, "_drBaseComp", baseTable);
|
||||
}
|
||||
|
||||
private static void SetPrivateField(object instance, string fieldName, object value)
|
||||
{
|
||||
FieldInfo field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
field.SetValue(instance, value);
|
||||
}
|
||||
|
||||
private static DRMuzzleComp CreateMuzzleRow()
|
||||
{
|
||||
DRMuzzleComp row = new DRMuzzleComp();
|
||||
Assert.That(row.ParseDataRow("\t1\t\t测试枪口\t[10,20,30,40,50]\t3\t0.15\tNormalBullet\t\t[Fire]", null), Is.True);
|
||||
return row;
|
||||
}
|
||||
|
||||
private static DRBearingComp CreateBearingRow()
|
||||
{
|
||||
DRBearingComp row = new DRBearingComp();
|
||||
Assert.That(row.ParseDataRow("\t1\t\t测试轴承\t[1,2,3,4,5]\t0.5\t[10,20,30,40,50]\t1\t\t[Ice]", null), Is.True);
|
||||
return row;
|
||||
}
|
||||
|
||||
private static DRBaseComp CreateBaseRow()
|
||||
{
|
||||
DRBaseComp row = new DRBaseComp();
|
||||
Assert.That(row.ParseDataRow("\t1\t\t测试底座\t[2,4,6,8,10]\t-0.25\tFire\t\t[Fire]", null), Is.True);
|
||||
return row;
|
||||
}
|
||||
|
||||
private sealed class FakeDataTable<TRow> : IDataTable<TRow> where TRow : class, IDataRow
|
||||
{
|
||||
private readonly Dictionary<int, TRow> _rowsById = new();
|
||||
|
||||
public FakeDataTable(params TRow[] rows)
|
||||
{
|
||||
if (rows != null)
|
||||
{
|
||||
for (int i = 0; i < rows.Length; i++)
|
||||
{
|
||||
TRow row = rows[i];
|
||||
if (row != null)
|
||||
{
|
||||
_rowsById[row.Id] = row;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Name => typeof(TRow).Name;
|
||||
|
||||
public string FullName => typeof(TRow).FullName;
|
||||
|
||||
public Type Type => typeof(TRow);
|
||||
|
||||
public int Count => _rowsById.Count;
|
||||
|
||||
public TRow this[int id] => GetDataRow(id);
|
||||
|
||||
public TRow MinIdDataRow => _rowsById.Count <= 0 ? null : GetDataRow(GetOrderedIds()[0]);
|
||||
|
||||
public TRow MaxIdDataRow => _rowsById.Count <= 0 ? null : GetDataRow(GetOrderedIds()[^1]);
|
||||
|
||||
public bool HasDataRow(int id)
|
||||
{
|
||||
return _rowsById.ContainsKey(id);
|
||||
}
|
||||
|
||||
public bool HasDataRow(Predicate<TRow> condition)
|
||||
{
|
||||
return GetDataRow(condition) != null;
|
||||
}
|
||||
|
||||
public TRow GetDataRow(int id)
|
||||
{
|
||||
return _rowsById.TryGetValue(id, out TRow row) ? row : null;
|
||||
}
|
||||
|
||||
public TRow GetDataRow(Predicate<TRow> condition)
|
||||
{
|
||||
if (condition == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (TRow row in _rowsById.Values)
|
||||
{
|
||||
if (row != null && condition(row))
|
||||
{
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TRow[] GetDataRows(Predicate<TRow> condition)
|
||||
{
|
||||
List<TRow> results = new();
|
||||
GetDataRows(condition, results);
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public void GetDataRows(Predicate<TRow> condition, List<TRow> results)
|
||||
{
|
||||
results?.Clear();
|
||||
if (condition == null || results == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (TRow row in _rowsById.Values)
|
||||
{
|
||||
if (row != null && condition(row))
|
||||
{
|
||||
results.Add(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TRow[] GetDataRows(Comparison<TRow> comparison)
|
||||
{
|
||||
List<TRow> results = new();
|
||||
GetDataRows(comparison, results);
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public void GetDataRows(Comparison<TRow> comparison, List<TRow> results)
|
||||
{
|
||||
results?.Clear();
|
||||
if (results == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
results.AddRange(_rowsById.Values);
|
||||
if (comparison != null)
|
||||
{
|
||||
results.Sort(comparison);
|
||||
}
|
||||
}
|
||||
|
||||
public TRow[] GetDataRows(Predicate<TRow> condition, Comparison<TRow> comparison)
|
||||
{
|
||||
List<TRow> results = new();
|
||||
GetDataRows(condition, comparison, results);
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public void GetDataRows(Predicate<TRow> condition, Comparison<TRow> comparison, List<TRow> results)
|
||||
{
|
||||
GetDataRows(condition, results);
|
||||
if (results != null && comparison != null)
|
||||
{
|
||||
results.Sort(comparison);
|
||||
}
|
||||
}
|
||||
|
||||
public TRow[] GetAllDataRows()
|
||||
{
|
||||
List<TRow> results = new();
|
||||
GetAllDataRows(results);
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public void GetAllDataRows(List<TRow> results)
|
||||
{
|
||||
results?.Clear();
|
||||
if (results == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (int id in GetOrderedIds())
|
||||
{
|
||||
results.Add(_rowsById[id]);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddDataRow(string dataRowString, object userData)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool AddDataRow(byte[] dataRowBytes, int startIndex, int length, object userData)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool RemoveDataRow(int id)
|
||||
{
|
||||
return _rowsById.Remove(id);
|
||||
}
|
||||
|
||||
public void RemoveAllDataRows()
|
||||
{
|
||||
_rowsById.Clear();
|
||||
}
|
||||
|
||||
public IEnumerator<TRow> GetEnumerator()
|
||||
{
|
||||
foreach (int id in GetOrderedIds())
|
||||
{
|
||||
yield return _rowsById[id];
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
private int[] GetOrderedIds()
|
||||
{
|
||||
int[] ids = new int[_rowsById.Count];
|
||||
_rowsById.Keys.CopyTo(ids, 0);
|
||||
Array.Sort(ids);
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa889597670a1524ebf4e952871c5414
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,531 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using CustomComponent;
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.Procedure;
|
||||
using GeometryTD.UI;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Tests.EditMode
|
||||
{
|
||||
public sealed class ProcedureMainFlowTests
|
||||
{
|
||||
private GameObject _uiRouterObject;
|
||||
private GameObject _inventoryObject;
|
||||
private GameObject _nodeMapFormObject;
|
||||
private UIRouterComponent _originalUIRouter;
|
||||
private PlayerInventoryComponent _originalPlayerInventory;
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
SetStaticUIRouter(_originalUIRouter);
|
||||
SetStaticPlayerInventory(_originalPlayerInventory);
|
||||
|
||||
if (_uiRouterObject != null)
|
||||
{
|
||||
Object.DestroyImmediate(_uiRouterObject);
|
||||
_uiRouterObject = null;
|
||||
}
|
||||
|
||||
if (_inventoryObject != null)
|
||||
{
|
||||
Object.DestroyImmediate(_inventoryObject);
|
||||
_inventoryObject = null;
|
||||
}
|
||||
|
||||
if (_nodeMapFormObject != null)
|
||||
{
|
||||
Object.DestroyImmediate(_nodeMapFormObject);
|
||||
_nodeMapFormObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeEnter_Accepted_Event_Closes_Hub_And_Enters_NodeActive()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
ProcedureMain procedure = CreateProcedureMain(CreateTwoNodeRun(), ProcedureMainFlowPhase.Hub);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeEnter",
|
||||
null,
|
||||
NodeEnterEventArgs.Create("testrun", 101, RunNodeType.Combat, 0));
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.NodeActive));
|
||||
Assert.That(recorderRouter.NodeMapController.CloseCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.MainController.CloseCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeEnter_Ignores_Mismatched_Node_And_Leaves_Hub_Visible()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
ProcedureMain procedure = CreateProcedureMain(CreateTwoNodeRun(), ProcedureMainFlowPhase.Hub);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeEnter",
|
||||
null,
|
||||
NodeEnterEventArgs.Create("testrun", 999, RunNodeType.Combat, 0));
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.Hub));
|
||||
Assert.That(recorderRouter.NodeMapController.CloseCount, Is.EqualTo(0));
|
||||
Assert.That(recorderRouter.MainController.CloseCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeComplete_Normal_Completion_Returns_To_Hub_And_Advances_Run()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
RunState runState = CreateTwoNodeRun();
|
||||
ProcedureMain procedure = CreateProcedureMain(runState, ProcedureMainFlowPhase.NodeActive);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeComplete",
|
||||
null,
|
||||
NodeCompleteEventArgs.Create(
|
||||
"testrun",
|
||||
101,
|
||||
RunNodeType.Combat,
|
||||
0,
|
||||
RunNodeCompletionStatus.Completed,
|
||||
true,
|
||||
new BackpackInventoryData { Gold = 77 }));
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.Hub));
|
||||
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(1));
|
||||
Assert.That(runState.Nodes[0].Status, Is.EqualTo(RunNodeStatus.Completed));
|
||||
Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Available));
|
||||
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(77));
|
||||
Assert.That(recorderRouter.NodeMapController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.MainController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(0));
|
||||
|
||||
NodeMapFormUseCase nodeMapUseCase = GetNodeMapUseCase(procedure);
|
||||
NodeMapFormRawData rawData = nodeMapUseCase.CreateInitialModel();
|
||||
Assert.That(rawData.ProgressText, Is.EqualTo("1 / 2"));
|
||||
Assert.That(rawData.CurrentNodeText, Is.EqualTo("当前节点: 事件"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeComplete_Exception_Returns_To_Hub_And_Marks_Current_Node_Exception()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
RunState runState = CreateTwoNodeRun();
|
||||
ProcedureMain procedure = CreateProcedureMain(runState, ProcedureMainFlowPhase.NodeActive);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeComplete",
|
||||
null,
|
||||
NodeCompleteEventArgs.Create(
|
||||
"testrun",
|
||||
101,
|
||||
RunNodeType.Combat,
|
||||
0,
|
||||
RunNodeCompletionStatus.Exception,
|
||||
false,
|
||||
new BackpackInventoryData { Gold = 12 }));
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.Hub));
|
||||
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(0));
|
||||
Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Exception));
|
||||
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(12));
|
||||
Assert.That(recorderRouter.NodeMapController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.MainController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeComplete_With_Broken_Participant_Tower_Opens_Removal_Dialog_After_Returning_To_Hub()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateParticipantInventory(100f));
|
||||
RunState runState = CreateTwoNodeRun();
|
||||
ProcedureMain procedure = CreateProcedureMain(runState, ProcedureMainFlowPhase.NodeActive);
|
||||
BackpackInventoryData brokenSnapshot = inventoryComponent.GetInventorySnapshot();
|
||||
brokenSnapshot.MuzzleComponents[0].Endurance = 0f;
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeComplete",
|
||||
null,
|
||||
NodeCompleteEventArgs.Create(
|
||||
"testrun",
|
||||
101,
|
||||
RunNodeType.Combat,
|
||||
0,
|
||||
RunNodeCompletionStatus.Completed,
|
||||
true,
|
||||
brokenSnapshot));
|
||||
|
||||
BackpackInventoryData replacedInventory = inventoryComponent.GetInventorySnapshot();
|
||||
DialogFormRawData dialogRawData = recorderRouter.DialogController.LastOpenedUserData as DialogFormRawData;
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.Hub));
|
||||
Assert.That(replacedInventory.ParticipantTowerInstanceIds.Count, Is.EqualTo(0));
|
||||
Assert.That(recorderRouter.NodeMapController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.MainController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(dialogRawData, Is.Not.Null);
|
||||
Assert.That(dialogRawData.Title, Is.EqualTo("出战塔已损坏"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeComplete_Boss_Run_Completed_Shows_Run_Complete_Dialog_And_Does_Not_Override_It_With_Cleanup_Dialog()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateParticipantInventory(100f));
|
||||
RunState runState = RunStateFactory.Create(
|
||||
LevelThemeType.Plain,
|
||||
inventoryComponent.GetInventorySnapshot(),
|
||||
new[]
|
||||
{
|
||||
new RunNodeSeed { NodeId = 901, NodeType = RunNodeType.BossCombat, LinkedLevelId = 4 }
|
||||
},
|
||||
"testrun");
|
||||
ProcedureMain procedure = CreateProcedureMain(runState, ProcedureMainFlowPhase.NodeActive);
|
||||
BackpackInventoryData brokenSnapshot = inventoryComponent.GetInventorySnapshot();
|
||||
brokenSnapshot.MuzzleComponents[0].Endurance = 0f;
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeComplete",
|
||||
null,
|
||||
NodeCompleteEventArgs.Create(
|
||||
"testrun",
|
||||
901,
|
||||
RunNodeType.BossCombat,
|
||||
0,
|
||||
RunNodeCompletionStatus.Completed,
|
||||
true,
|
||||
brokenSnapshot));
|
||||
|
||||
BackpackInventoryData replacedInventory = inventoryComponent.GetInventorySnapshot();
|
||||
DialogFormRawData dialogRawData = recorderRouter.DialogController.LastOpenedUserData as DialogFormRawData;
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.RunCompletedPendingFinish));
|
||||
Assert.That(runState.IsCompleted, Is.True);
|
||||
Assert.That(replacedInventory.ParticipantTowerInstanceIds.Count, Is.EqualTo(0));
|
||||
Assert.That(recorderRouter.NodeMapController.CloseCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.MainController.CloseCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(dialogRawData, Is.Not.Null);
|
||||
Assert.That(dialogRawData.Title, Is.EqualTo("Run Complete"));
|
||||
Assert.That(dialogRawData.Message, Is.EqualTo("Boss node completed. This run is finished and will return to the main menu."));
|
||||
Assert.That(dialogRawData.OnClickConfirm, Is.Not.Null);
|
||||
|
||||
dialogRawData.OnClickConfirm(null);
|
||||
|
||||
Assert.That(GetReturnToMenuPending(procedure), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeMapNodeEnterRequested_Ignores_Request_When_FlowPhase_Is_Not_Hub()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
ProcedureMain procedure = CreateProcedureMain(CreateTwoNodeRun(), ProcedureMainFlowPhase.NodeActive);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeMapNodeEnterRequested",
|
||||
CreateNodeMapFormSender(),
|
||||
NodeMapNodeEnterRequestedEventArgs.Create("testrun", 101, RunNodeType.Combat, 0));
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.NodeActive));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(0));
|
||||
Assert.That(recorderRouter.NodeMapController.CloseCount, Is.EqualTo(0));
|
||||
Assert.That(recorderRouter.MainController.CloseCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeMapNodeEnterRequested_Ignores_Request_When_Target_Node_Does_Not_Match_Current_Node()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
ProcedureMain procedure = CreateProcedureMain(CreateTwoNodeRun(), ProcedureMainFlowPhase.Hub);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeMapNodeEnterRequested",
|
||||
CreateNodeMapFormSender(),
|
||||
NodeMapNodeEnterRequestedEventArgs.Create("testrun", 102, RunNodeType.Event, 1));
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.Hub));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(0));
|
||||
Assert.That(recorderRouter.NodeMapController.CloseCount, Is.EqualTo(0));
|
||||
Assert.That(recorderRouter.MainController.CloseCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeMapNodeEnterRequested_Blocked_Combat_With_Null_Inventory_Opens_Block_Dialog()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
ProcedureMain procedure = CreateProcedureMain(CreateTwoNodeRun(), ProcedureMainFlowPhase.Hub);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeMapNodeEnterRequested",
|
||||
CreateNodeMapFormSender(),
|
||||
NodeMapNodeEnterRequestedEventArgs.Create("testrun", 101, RunNodeType.Combat, 0));
|
||||
|
||||
DialogFormRawData dialogRawData = recorderRouter.DialogController.LastOpenedUserData as DialogFormRawData;
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.Hub));
|
||||
Assert.That(recorderRouter.DialogController.CloseCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(dialogRawData, Is.Not.Null);
|
||||
Assert.That(dialogRawData.Title, Is.EqualTo("无法进入战斗"));
|
||||
Assert.That(dialogRawData.Message, Is.EqualTo("当前无法读取库存快照,暂时不能进入战斗。请重新进入本轮流程后再试。"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnNodeMapNodeEnterRequested_Blocked_Combat_With_No_Valid_Participant_Tower_Opens_Block_Dialog()
|
||||
{
|
||||
RecorderUIRouter recorderRouter = CreateBoundUIRouter();
|
||||
PlayerInventoryComponent inventoryComponent = CreateBoundPlayerInventory(CreateParticipantInventory(100f));
|
||||
BackpackInventoryData invalidInventory = inventoryComponent.GetInventorySnapshot();
|
||||
invalidInventory.ParticipantTowerInstanceIds.Clear();
|
||||
inventoryComponent.ReplaceInventorySnapshot(invalidInventory);
|
||||
|
||||
ProcedureMain procedure = CreateProcedureMain(CreateTwoNodeRun(), ProcedureMainFlowPhase.Hub);
|
||||
|
||||
InvokePrivate(
|
||||
procedure,
|
||||
"OnNodeMapNodeEnterRequested",
|
||||
CreateNodeMapFormSender(),
|
||||
NodeMapNodeEnterRequestedEventArgs.Create("testrun", 101, RunNodeType.Combat, 0));
|
||||
|
||||
DialogFormRawData dialogRawData = recorderRouter.DialogController.LastOpenedUserData as DialogFormRawData;
|
||||
|
||||
Assert.That(GetFlowPhase(procedure), Is.EqualTo(ProcedureMainFlowPhase.Hub));
|
||||
Assert.That(recorderRouter.DialogController.CloseCount, Is.EqualTo(1));
|
||||
Assert.That(recorderRouter.DialogController.OpenCount, Is.EqualTo(1));
|
||||
Assert.That(dialogRawData, Is.Not.Null);
|
||||
Assert.That(dialogRawData.Title, Is.EqualTo("无法进入战斗"));
|
||||
Assert.That(
|
||||
dialogRawData.Message,
|
||||
Is.EqualTo("参战区至少需要 1 座完整装配了枪口、轴承、底座的塔,才能进入战斗。"));
|
||||
}
|
||||
|
||||
private RecorderUIRouter CreateBoundUIRouter()
|
||||
{
|
||||
_originalUIRouter = GameEntry.UIRouter;
|
||||
_uiRouterObject = new GameObject("ProcedureMainFlowTests-UIRouter");
|
||||
|
||||
UIRouterComponent uiRouter = _uiRouterObject.AddComponent<UIRouterComponent>();
|
||||
SetStaticUIRouter(uiRouter);
|
||||
|
||||
RecorderUIRouter recorderRouter = new RecorderUIRouter(uiRouter);
|
||||
recorderRouter.Bind(UIFormType.NodeMapForm);
|
||||
recorderRouter.Bind(UIFormType.MainForm);
|
||||
recorderRouter.Bind(UIFormType.DialogForm);
|
||||
recorderRouter.Bind(UIFormType.RepoForm);
|
||||
return recorderRouter;
|
||||
}
|
||||
|
||||
private PlayerInventoryComponent CreateBoundPlayerInventory(BackpackInventoryData inventory)
|
||||
{
|
||||
_originalPlayerInventory = GameEntry.PlayerInventory;
|
||||
_inventoryObject = new GameObject("ProcedureMainFlowTests-Inventory");
|
||||
PlayerInventoryComponent inventoryComponent = _inventoryObject.AddComponent<PlayerInventoryComponent>();
|
||||
SetStaticPlayerInventory(inventoryComponent);
|
||||
inventoryComponent.ReplaceInventorySnapshot(inventory);
|
||||
return inventoryComponent;
|
||||
}
|
||||
|
||||
private NodeMapForm CreateNodeMapFormSender()
|
||||
{
|
||||
_nodeMapFormObject = new GameObject("ProcedureMainFlowTests-NodeMapForm");
|
||||
return _nodeMapFormObject.AddComponent<NodeMapForm>();
|
||||
}
|
||||
|
||||
private static ProcedureMain CreateProcedureMain(RunState runState, ProcedureMainFlowPhase flowPhase)
|
||||
{
|
||||
ProcedureMain procedure = new ProcedureMain();
|
||||
NodeMapFormUseCase nodeMapUseCase = new NodeMapFormUseCase();
|
||||
nodeMapUseCase.SetRunState(runState);
|
||||
|
||||
SetPrivateField(procedure, "_currentRunState", runState);
|
||||
SetPrivateField(procedure, "_nodeMapFormUseCase", nodeMapUseCase);
|
||||
SetPrivateField(procedure, "_flowPhase", flowPhase);
|
||||
SetPrivateField(procedure, "_isRunCompleteDialogShown", false);
|
||||
SetPrivateField(procedure, "_isReturnToMenuPending", false);
|
||||
return procedure;
|
||||
}
|
||||
|
||||
private static void SetStaticUIRouter(UIRouterComponent uiRouter)
|
||||
{
|
||||
FieldInfo backingField = typeof(GameEntry).GetField(
|
||||
"<UIRouter>k__BackingField",
|
||||
BindingFlags.Static | BindingFlags.NonPublic);
|
||||
Assert.That(backingField, Is.Not.Null);
|
||||
backingField.SetValue(null, uiRouter);
|
||||
}
|
||||
|
||||
private static void SetStaticPlayerInventory(PlayerInventoryComponent inventoryComponent)
|
||||
{
|
||||
FieldInfo backingField = typeof(GameEntry).GetField(
|
||||
"<PlayerInventory>k__BackingField",
|
||||
BindingFlags.Static | BindingFlags.NonPublic);
|
||||
Assert.That(backingField, Is.Not.Null);
|
||||
backingField.SetValue(null, inventoryComponent);
|
||||
}
|
||||
|
||||
private static void InvokePrivate(object instance, string methodName, params object[] args)
|
||||
{
|
||||
MethodInfo method = instance.GetType().GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(method, Is.Not.Null);
|
||||
method.Invoke(instance, args);
|
||||
}
|
||||
|
||||
private static void SetPrivateField(object instance, string fieldName, object value)
|
||||
{
|
||||
FieldInfo field = instance.GetType().GetField(
|
||||
fieldName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
field.SetValue(instance, value);
|
||||
}
|
||||
|
||||
private static T GetPrivateField<T>(object instance, string fieldName)
|
||||
{
|
||||
FieldInfo field = instance.GetType().GetField(
|
||||
fieldName,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(field, Is.Not.Null);
|
||||
return (T)field.GetValue(instance);
|
||||
}
|
||||
|
||||
private static ProcedureMainFlowPhase GetFlowPhase(ProcedureMain procedure)
|
||||
{
|
||||
return GetPrivateField<ProcedureMainFlowPhase>(procedure, "_flowPhase");
|
||||
}
|
||||
|
||||
private static bool GetReturnToMenuPending(ProcedureMain procedure)
|
||||
{
|
||||
return GetPrivateField<bool>(procedure, "_isReturnToMenuPending");
|
||||
}
|
||||
|
||||
private static NodeMapFormUseCase GetNodeMapUseCase(ProcedureMain procedure)
|
||||
{
|
||||
return GetPrivateField<NodeMapFormUseCase>(procedure, "_nodeMapFormUseCase");
|
||||
}
|
||||
|
||||
private static RunState CreateTwoNodeRun()
|
||||
{
|
||||
return RunStateFactory.Create(
|
||||
LevelThemeType.Plain,
|
||||
new BackpackInventoryData { Gold = 50 },
|
||||
new[]
|
||||
{
|
||||
new RunNodeSeed { NodeId = 101, NodeType = RunNodeType.Combat, LinkedLevelId = 1, SequenceIndex = 0 },
|
||||
new RunNodeSeed { NodeId = 102, NodeType = RunNodeType.Event, SequenceIndex = 1 }
|
||||
},
|
||||
"testrun");
|
||||
}
|
||||
|
||||
private static BackpackInventoryData CreateParticipantInventory(float endurance)
|
||||
{
|
||||
BackpackInventoryData inventory = new BackpackInventoryData();
|
||||
inventory.MuzzleComponents.Add(new MuzzleCompItemData
|
||||
{
|
||||
InstanceId = 10001,
|
||||
Name = "枪口",
|
||||
Endurance = endurance
|
||||
});
|
||||
inventory.BearingComponents.Add(new BearingCompItemData
|
||||
{
|
||||
InstanceId = 20001,
|
||||
Name = "轴承",
|
||||
Endurance = endurance
|
||||
});
|
||||
inventory.BaseComponents.Add(new BaseCompItemData
|
||||
{
|
||||
InstanceId = 30001,
|
||||
Name = "底座",
|
||||
Endurance = endurance
|
||||
});
|
||||
inventory.Towers.Add(new TowerItemData
|
||||
{
|
||||
InstanceId = 90001,
|
||||
Name = "合法塔",
|
||||
MuzzleComponentInstanceId = 10001,
|
||||
BearingComponentInstanceId = 20001,
|
||||
BaseComponentInstanceId = 30001
|
||||
});
|
||||
inventory.ParticipantTowerInstanceIds.Add(90001);
|
||||
return inventory;
|
||||
}
|
||||
|
||||
private sealed class RecorderUIRouter
|
||||
{
|
||||
private readonly Dictionary<UIFormType, RecorderController> _controllers = new();
|
||||
|
||||
public RecorderUIRouter(UIRouterComponent uiRouter)
|
||||
{
|
||||
UIRouter = uiRouter;
|
||||
}
|
||||
|
||||
public UIRouterComponent UIRouter { get; }
|
||||
|
||||
public RecorderController NodeMapController => _controllers[UIFormType.NodeMapForm];
|
||||
|
||||
public RecorderController MainController => _controllers[UIFormType.MainForm];
|
||||
|
||||
public RecorderController DialogController => _controllers[UIFormType.DialogForm];
|
||||
|
||||
public void Bind(UIFormType formType)
|
||||
{
|
||||
RecorderController controller = new RecorderController();
|
||||
_controllers[formType] = controller;
|
||||
|
||||
FieldInfo routeControllersField = typeof(UIRouterComponent).GetField(
|
||||
"_routeControllers",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(routeControllersField, Is.Not.Null);
|
||||
|
||||
Dictionary<UIFormType, IUIFormController> routeControllers =
|
||||
(Dictionary<UIFormType, IUIFormController>)routeControllersField.GetValue(UIRouter);
|
||||
Assert.That(routeControllers, Is.Not.Null);
|
||||
routeControllers[formType] = controller;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecorderController : IUIFormController
|
||||
{
|
||||
public int OpenCount { get; private set; }
|
||||
|
||||
public int CloseCount { get; private set; }
|
||||
|
||||
public object LastOpenedUserData { get; private set; }
|
||||
|
||||
public IUIUseCase BoundUseCase { get; private set; }
|
||||
|
||||
public int? OpenUI(object userData = null)
|
||||
{
|
||||
OpenCount++;
|
||||
LastOpenedUserData = userData;
|
||||
return OpenCount;
|
||||
}
|
||||
|
||||
public void CloseUI()
|
||||
{
|
||||
CloseCount++;
|
||||
}
|
||||
|
||||
public void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
BoundUseCase = useCase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b622fd9e87fa514da333f9d8313fdd2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -50,6 +50,32 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(rawData.ConfirmText, Is.EqualTo("知道了"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveBrokenParticipantTowers_Does_Not_Remove_Towers_With_Missing_Components()
|
||||
{
|
||||
BackpackInventoryData inventory = CreateInventory();
|
||||
inventory.BaseComponents.RemoveAt(0);
|
||||
|
||||
ProcedureMainParticipantTowerCleanupResult result =
|
||||
ProcedureMainParticipantTowerCleanupService.RemoveBrokenParticipantTowers(inventory, 4);
|
||||
|
||||
Assert.That(result.HasAnyRemovedTower, Is.False);
|
||||
CollectionAssert.AreEqual(new long[] { 90001, 90002 }, inventory.ParticipantTowerInstanceIds);
|
||||
Assert.That(inventory.Towers[0].IsParticipatingInCombat, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildRemovedTowerDialogRawData_Returns_Default_Message_When_No_Tower_Was_Removed()
|
||||
{
|
||||
DialogFormRawData rawData =
|
||||
ProcedureMainParticipantTowerCleanupService.BuildRemovedTowerDialogRawData(
|
||||
new ProcedureMainParticipantTowerCleanupResult());
|
||||
|
||||
Assert.That(rawData.Title, Is.EqualTo("出战塔已损坏"));
|
||||
Assert.That(rawData.Message, Is.EqualTo("当前没有需要移出参战区的损坏防御塔。"));
|
||||
Assert.That(rawData.ConfirmText, Is.EqualTo("知道了"));
|
||||
}
|
||||
|
||||
private static BackpackInventoryData CreateInventory()
|
||||
{
|
||||
BackpackInventoryData inventory = new BackpackInventoryData();
|
||||
|
||||
@@ -90,6 +90,22 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(completedRun.RunInventorySnapshot.Gold, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryAdvanceRun_Returns_NoChange_But_Still_Replaces_Snapshot_For_NonTerminal_Status()
|
||||
{
|
||||
RunState runState = CreateTwoNodeRun();
|
||||
|
||||
ProcedureMainRunAdvanceResult result = ProcedureMainRunFlowService.TryAdvanceRun(
|
||||
runState,
|
||||
RunNodeCompletionStatus.None,
|
||||
new BackpackInventoryData { Gold = 999 });
|
||||
|
||||
Assert.That(result, Is.EqualTo(ProcedureMainRunAdvanceResult.NoChange));
|
||||
Assert.That(runState.CurrentNodeIndex, Is.EqualTo(0));
|
||||
Assert.That(runState.CurrentNode.Status, Is.EqualTo(RunNodeStatus.Available));
|
||||
Assert.That(runState.RunInventorySnapshot.Gold, Is.EqualTo(999));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MatchesCurrentNode_Returns_True_For_Current_Node_And_Default_Wildcards()
|
||||
{
|
||||
@@ -136,6 +152,23 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(sequenceMismatch, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MatchesCurrentNode_Returns_False_When_Run_Has_No_Current_Node()
|
||||
{
|
||||
RunState completedRun = RunStateFactory.Create(
|
||||
LevelThemeType.Plain,
|
||||
new BackpackInventoryData { Gold = 10 },
|
||||
new RunNodeSeed[0]);
|
||||
|
||||
bool match = ProcedureMainNodeEventGuardService.MatchesCurrentNode(
|
||||
completedRun,
|
||||
1,
|
||||
RunNodeType.Combat,
|
||||
0);
|
||||
|
||||
Assert.That(match, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryEnterCompletedPendingFinish_Shows_Dialog_Only_Once()
|
||||
{
|
||||
|
||||
@@ -90,6 +90,41 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(hitContext.IsCriticalHit, Is.True);
|
||||
Assert.That(hitContext.FinalDamage, Is.EqualTo(281));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTagStack_Returns_Zero_For_Missing_Invalid_Or_Empty_Data()
|
||||
{
|
||||
Assert.That(TagEffectResolver.GetTagStack(null, TagType.Fire), Is.EqualTo(0));
|
||||
Assert.That(TagEffectResolver.GetTagStack(System.Array.Empty<TagRuntimeData>(), TagType.Fire), Is.EqualTo(0));
|
||||
Assert.That(TagEffectResolver.GetTagStack(
|
||||
new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 0 },
|
||||
new TagRuntimeData { TagType = TagType.Ice, TotalStack = 2 }
|
||||
},
|
||||
TagType.Fire), Is.EqualTo(0));
|
||||
Assert.That(TagEffectResolver.GetTagStack(
|
||||
new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Ice, TotalStack = 2 }
|
||||
},
|
||||
TagType.None), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetTagStack_Returns_First_Positive_Matching_Stack()
|
||||
{
|
||||
int stack = TagEffectResolver.GetTagStack(
|
||||
new[]
|
||||
{
|
||||
null,
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 2 },
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 4 }
|
||||
},
|
||||
TagType.Fire);
|
||||
|
||||
Assert.That(stack, Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnemyTagStatusRuntimeTests
|
||||
@@ -273,6 +308,35 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(totalDamage, Is.EqualTo(0));
|
||||
Assert.That(runtime.GetMoveSpeedMultiplier(), Is.EqualTo(1f).Within(0.001f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SampleInventory_TowerFireTags_Flow_From_ComponentAggregation_To_BurnDamage()
|
||||
{
|
||||
BackpackInventoryData inventory = InventorySeedUtility.CreateSampleInventory();
|
||||
TowerItemData tower = inventory.Towers[0];
|
||||
|
||||
Assert.That(tower.Stats, Is.Not.Null);
|
||||
Assert.That(tower.Stats.Tags, Is.EqualTo(new[] { TagType.Fire }));
|
||||
Assert.That(tower.Stats.TagRuntimes, Has.Length.EqualTo(1));
|
||||
Assert.That(tower.Stats.TagRuntimes[0].TagType, Is.EqualTo(TagType.Fire));
|
||||
Assert.That(tower.Stats.TagRuntimes[0].TotalStack, Is.EqualTo(3));
|
||||
|
||||
EnemyTagStatusRuntime runtime = new EnemyTagStatusRuntime();
|
||||
int totalDamage = 0;
|
||||
TagEffectResolver.ApplyAfterHit(new AttackPayload
|
||||
{
|
||||
BaseDamage = tower.Stats.AttackDamage[0],
|
||||
AttackPropertyType = tower.Stats.AttackPropertyType,
|
||||
TagRuntimes = tower.Stats.TagRuntimes
|
||||
}, runtime);
|
||||
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
runtime.Tick(1f, damage => totalDamage += damage);
|
||||
|
||||
Assert.That(runtime.HasStatus(TagType.Fire), Is.False);
|
||||
Assert.That(totalDamage, Is.EqualTo(180));
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnemyStatusTagRegistryTests
|
||||
|
||||
@@ -49,6 +49,38 @@ namespace GeometryTD.Tests.EditMode
|
||||
Assert.That(tags, Is.EqualTo(new[] { TagType.Fire, TagType.Crit }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildRuntimeTagsFromUniqueTags_Filters_Invalid_Values_And_Assigns_One_Stack()
|
||||
{
|
||||
TagRuntimeData[] runtimes = TowerTagAggregationService.BuildRuntimeTagsFromUniqueTags(new[]
|
||||
{
|
||||
TagType.None,
|
||||
TagType.Ice,
|
||||
TagType.Fire,
|
||||
TagType.Ice,
|
||||
(TagType)99
|
||||
});
|
||||
|
||||
Assert.That(runtimes, Has.Length.EqualTo(2));
|
||||
Assert.That(runtimes[0].TagType, Is.EqualTo(TagType.Fire));
|
||||
Assert.That(runtimes[0].TotalStack, Is.EqualTo(1));
|
||||
Assert.That(runtimes[1].TagType, Is.EqualTo(TagType.Ice));
|
||||
Assert.That(runtimes[1].TotalStack, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FlattenUniqueTags_Ignores_Runtime_Entries_With_NonPositive_Stack()
|
||||
{
|
||||
TagType[] tags = TowerTagAggregationService.FlattenUniqueTags(new[]
|
||||
{
|
||||
new TagRuntimeData { TagType = TagType.Fire, TotalStack = 0 },
|
||||
new TagRuntimeData { TagType = TagType.Ice, TotalStack = -1 },
|
||||
new TagRuntimeData { TagType = TagType.Crit, TotalStack = 1 }
|
||||
});
|
||||
|
||||
Assert.That(tags, Is.EqualTo(new[] { TagType.Crit }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildTowerTagTexts_Uses_Runtime_Stacks_For_Display()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user