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(CreateMuzzleRow()), new FakeDataTable(CreateBearingRow()), new FakeDataTable(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(CreateMuzzleRow()), new FakeDataTable(CreateBearingRow()), new FakeDataTable(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(); SetStaticPlayerInventory(inventoryComponent); inventoryComponent.ReplaceInventorySnapshot(inventory); return inventoryComponent; } private static void SetStaticPlayerInventory(PlayerInventoryComponent inventoryComponent) { FieldInfo backingField = typeof(GameEntry).GetField( "k__BackingField", BindingFlags.Static | BindingFlags.NonPublic); Assert.That(backingField, Is.Not.Null); backingField.SetValue(null, inventoryComponent); } private static void InjectAssemblyTables( PlayerInventoryComponent inventoryComponent, IDataTable muzzleTable, IDataTable bearingTable, IDataTable 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 : IDataTable where TRow : class, IDataRow { private readonly Dictionary _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 condition) { return GetDataRow(condition) != null; } public TRow GetDataRow(int id) { return _rowsById.TryGetValue(id, out TRow row) ? row : null; } public TRow GetDataRow(Predicate 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 condition) { List results = new(); GetDataRows(condition, results); return results.ToArray(); } public void GetDataRows(Predicate condition, List 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 comparison) { List results = new(); GetDataRows(comparison, results); return results.ToArray(); } public void GetDataRows(Comparison comparison, List results) { results?.Clear(); if (results == null) { return; } results.AddRange(_rowsById.Values); if (comparison != null) { results.Sort(comparison); } } public TRow[] GetDataRows(Predicate condition, Comparison comparison) { List results = new(); GetDataRows(condition, comparison, results); return results.ToArray(); } public void GetDataRows(Predicate condition, Comparison comparison, List results) { GetDataRows(condition, results); if (results != null && comparison != null) { results.Sort(comparison); } } public TRow[] GetAllDataRows() { List results = new(); GetAllDataRows(results); return results.ToArray(); } public void GetAllDataRows(List 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 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; } } } }