using System.Collections; using System; using System.Collections.Generic; using System.Reflection; using GameFramework.DataTable; using GeometryTD.CustomComponent; using GeometryTD.DataTable; using GeometryTD.Definition; using GeometryTD.UI; using NUnit.Framework; namespace GeometryTD.Tests.EditMode { public sealed class ShopPricingRuleTests { [Test] public void BuildGoods_Uses_Price_Row_Range_Matching_Item_Rarity() { ShopGoodsBuilder builder = CreateBuilder( CreateShopPriceRow(1, RarityType.Blue, 30, 35), CreateShopPriceRow(2, RarityType.Purple, 60, 70)); List goods = builder.BuildGoods(goodsCount: 12, runSeed: 3001, sequenceIndex: 4); Assert.That(goods, Has.Count.EqualTo(12)); for (int i = 0; i < goods.Count; i++) { GoodsItemRawData item = goods[i]; Assert.That(item, Is.Not.Null); Assert.That(item.SourceItem, Is.Not.Null); switch (item.SourceItem.Rarity) { case RarityType.Blue: Assert.That(item.Price, Is.InRange(30, 35)); break; case RarityType.Purple: Assert.That(item.Price, Is.InRange(60, 70)); break; default: Assert.Fail($"Unexpected rarity generated for goods item {i}: {item.SourceItem.Rarity}"); break; } } } [Test] public void BuildGoods_Is_Reproducible_For_Same_RunSeed_And_SequenceIndex() { ShopGoodsBuilder builder = CreateBuilder( CreateShopPriceRow(1, RarityType.Blue, 30, 35), CreateShopPriceRow(2, RarityType.Purple, 60, 70)); string first = BuildGoodsSignature(builder.BuildGoods(goodsCount: 4, runSeed: 901, sequenceIndex: 7)); string second = BuildGoodsSignature(builder.BuildGoods(goodsCount: 4, runSeed: 901, sequenceIndex: 7)); Assert.That(second, Is.EqualTo(first)); } [Test] public void ResolveRandomPrice_Returns_Zero_When_Rarity_Has_No_Price_Row() { ShopGoodsBuilder builder = CreateBuilder(CreateShopPriceRow(1, RarityType.Blue, 30, 35)); MethodInfo method = typeof(ShopGoodsBuilder).GetMethod( "ResolveRandomPrice", BindingFlags.Instance | BindingFlags.NonPublic); Assert.That(method, Is.Not.Null); int resolvedPrice = (int)method.Invoke(builder, new object[] { RarityType.Red, new Random(7) }); Assert.That(resolvedPrice, Is.EqualTo(0)); } private static ShopGoodsBuilder CreateBuilder(params DRShopPrice[] shopPriceRows) { return new ShopGoodsBuilder( shopPriceRows, new FakeDataTable(CreateMuzzleRow(1, "火焰枪口")), new FakeDataTable(CreateBearingRow(1, "寒冰轴承")), new FakeDataTable(CreateBaseRow(1, "迅捷底座"))); } private static string BuildGoodsSignature(IReadOnlyList goods) { List parts = new List(goods.Count); for (int i = 0; i < goods.Count; i++) { GoodsItemRawData item = goods[i]; parts.Add($"{item.GoodsIndex}:{item.Price}:{item.SourceItem.SlotType}:{item.SourceItem.Rarity}:{item.SourceItem.ConfigId}"); } return string.Join("|", parts); } private static DRShopPrice CreateShopPriceRow(int id, RarityType rarity, int minPrice, int maxPrice) { DRShopPrice row = new DRShopPrice(); Assert.That(row.ParseDataRow($"\t{id}\t\t{rarity}\t{minPrice}\t{maxPrice}", null), Is.True); return row; } private static DRMuzzleComp CreateMuzzleRow(int id, string name) { DRMuzzleComp row = new DRMuzzleComp(); Assert.That( row.ParseDataRow($"\t{id}\t\t{name}\t[10,20,30,40,50]\t3\t0.15\tNormalBullet\t\t[Fire,Crit]", null), Is.True); return row; } private static DRBearingComp CreateBearingRow(int id, string name) { DRBearingComp row = new DRBearingComp(); Assert.That( row.ParseDataRow($"\t{id}\t\t{name}\t[1,2,3,4,5]\t0.5\t[10,20,30,40,50]\t1\t\t[Ice,Shatter]", null), Is.True); return row; } private static DRBaseComp CreateBaseRow(int id, string name) { DRBaseComp row = new DRBaseComp(); Assert.That( row.ParseDataRow($"\t{id}\t\t{name}\t[2,4,6,8,10]\t-0.25\tFire\t\t[Fire,Crit]", 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) { return; } 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) => _rowsById.ContainsKey(id); public bool HasDataRow(Predicate condition) => GetDataRow(condition) != null; public TRow GetDataRow(int id) => _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) => _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; } } } }