804 lines
31 KiB
C#
804 lines
31 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using GameFramework.DataTable;
|
|
using GeometryTD.CustomComponent;
|
|
using GeometryTD.DataTable;
|
|
using GeometryTD.Definition;
|
|
using GeometryTD.Procedure;
|
|
using Newtonsoft.Json.Linq;
|
|
using NUnit.Framework;
|
|
using UnityEngine;
|
|
using Object = UnityEngine.Object;
|
|
|
|
namespace GeometryTD.Tests.EditMode
|
|
{
|
|
public sealed class EventOptionExecutorTests
|
|
{
|
|
private GameObject _inventoryGenerationObject;
|
|
private InventoryGenerationComponent _originalInventoryGeneration;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
TagDefinitionRegistry.ResetToDefaults();
|
|
TagGenerationRuleRegistry.ResetToDefaults();
|
|
RarityTagBudgetRuleRegistry.ResetToDefaults();
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
TagDefinitionRegistry.ResetToDefaults();
|
|
TagGenerationRuleRegistry.ResetToDefaults();
|
|
RarityTagBudgetRuleRegistry.ResetToDefaults();
|
|
|
|
SetStaticInventoryGeneration(_originalInventoryGeneration);
|
|
_originalInventoryGeneration = null;
|
|
|
|
if (_inventoryGenerationObject != null)
|
|
{
|
|
Object.DestroyImmediate(_inventoryGenerationObject);
|
|
_inventoryGenerationObject = null;
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void EvaluateOption_Blocks_When_Gold_Requirement_Not_Met()
|
|
{
|
|
EventOptionExecutor executor = new EventOptionExecutor();
|
|
EventOption option = new EventOption(
|
|
"下注 100",
|
|
new EventRequirementBase[]
|
|
{
|
|
new GoldAtLeastRequirement(new GoldAtLeastParam(100))
|
|
},
|
|
Array.Empty<EventEffectBase>(),
|
|
Array.Empty<EventEffectBase>());
|
|
|
|
EventOptionAvailability availability = executor.EvaluateOption(
|
|
option,
|
|
new BackpackInventoryData { Gold = 60 });
|
|
|
|
Assert.That(availability.IsSelectable, Is.False);
|
|
Assert.That(availability.BlockedReason, Is.EqualTo("需要至少 100 金币"));
|
|
}
|
|
|
|
[Test]
|
|
public void Execute_Applies_Cost_Before_Probability_And_Skips_Reward_On_Failure()
|
|
{
|
|
EventOptionExecutor executor = new EventOptionExecutor();
|
|
EventItem eventItem = new EventItem(
|
|
1,
|
|
"赌马",
|
|
"测试事件",
|
|
new[]
|
|
{
|
|
new EventOption(
|
|
"下注",
|
|
new EventRequirementBase[]
|
|
{
|
|
new GoldAtLeastRequirement(new GoldAtLeastParam(100))
|
|
},
|
|
new EventEffectBase[]
|
|
{
|
|
new AddGoldEffect(new AddGoldParam(-100))
|
|
},
|
|
new EventEffectBase[]
|
|
{
|
|
new AddGoldEffect(new AddGoldParam(250))
|
|
},
|
|
probability: 0f)
|
|
});
|
|
BackpackInventoryData inventory = new BackpackInventoryData { Gold = 120 };
|
|
|
|
EventOptionExecutionResult result = executor.Execute(
|
|
eventItem,
|
|
0,
|
|
CreateContext(),
|
|
inventory);
|
|
|
|
Assert.That(result.IsAccepted, Is.True);
|
|
Assert.That(result.IsProbabilitySuccess, Is.False);
|
|
Assert.That(inventory.Gold, Is.EqualTo(20));
|
|
}
|
|
|
|
[Test]
|
|
public void Execute_Removes_Only_Loose_Components_Of_Requested_Rarity()
|
|
{
|
|
EventOptionExecutor executor = new EventOptionExecutor();
|
|
EventItem eventItem = new EventItem(
|
|
2,
|
|
"工匠",
|
|
"测试事件",
|
|
new[]
|
|
{
|
|
new EventOption(
|
|
"交出 2 个白色组件",
|
|
new EventRequirementBase[]
|
|
{
|
|
new CompCountAtLeastRequirement(new CompCountAtLeastParam(2, RarityType.White))
|
|
},
|
|
new EventEffectBase[]
|
|
{
|
|
new RemoveRandomCompsEffect(new RemoveRandomCompsParam(2, RarityType.White))
|
|
},
|
|
Array.Empty<EventEffectBase>())
|
|
});
|
|
BackpackInventoryData inventory = new BackpackInventoryData
|
|
{
|
|
MuzzleComponents = new List<MuzzleCompItemData>
|
|
{
|
|
new MuzzleCompItemData
|
|
{
|
|
InstanceId = 10001,
|
|
Name = "已装配白枪口",
|
|
Rarity = RarityType.White,
|
|
IsAssembledIntoTower = true
|
|
},
|
|
new MuzzleCompItemData
|
|
{
|
|
InstanceId = 10002,
|
|
Name = "未装配白枪口",
|
|
Rarity = RarityType.White,
|
|
IsAssembledIntoTower = false
|
|
}
|
|
},
|
|
BearingComponents = new List<BearingCompItemData>
|
|
{
|
|
new BearingCompItemData
|
|
{
|
|
InstanceId = 20001,
|
|
Name = "未装配白轴承",
|
|
Rarity = RarityType.White,
|
|
IsAssembledIntoTower = false
|
|
},
|
|
new BearingCompItemData
|
|
{
|
|
InstanceId = 20002,
|
|
Name = "绿色轴承",
|
|
Rarity = RarityType.Green,
|
|
IsAssembledIntoTower = false
|
|
}
|
|
}
|
|
};
|
|
|
|
EventOptionExecutionResult result = executor.Execute(eventItem, 0, CreateContext(), inventory);
|
|
|
|
Assert.That(result.IsAccepted, Is.True);
|
|
Assert.That(inventory.MuzzleComponents.Count, Is.EqualTo(1));
|
|
Assert.That(inventory.MuzzleComponents[0].InstanceId, Is.EqualTo(10001));
|
|
Assert.That(inventory.BearingComponents.Count, Is.EqualTo(1));
|
|
Assert.That(inventory.BearingComponents[0].InstanceId, Is.EqualTo(20002));
|
|
}
|
|
|
|
[Test]
|
|
public void Execute_AddRandomComponents_Is_Stable_For_Same_Context()
|
|
{
|
|
BindInventoryGeneration();
|
|
EventOptionExecutor executor = new EventOptionExecutor();
|
|
EventItem eventItem = new EventItem(
|
|
3,
|
|
"奖励组件",
|
|
"测试事件",
|
|
new[]
|
|
{
|
|
new EventOption(
|
|
"获得两个蓝色组件",
|
|
Array.Empty<EventRequirementBase>(),
|
|
Array.Empty<EventEffectBase>(),
|
|
new EventEffectBase[]
|
|
{
|
|
new AddRandomCompsEffect(new AddRandomCompsParam(2, RarityType.Blue))
|
|
})
|
|
});
|
|
RunNodeExecutionContext context = CreateContext();
|
|
|
|
BackpackInventoryData firstInventory = new BackpackInventoryData();
|
|
BackpackInventoryData secondInventory = new BackpackInventoryData();
|
|
|
|
EventOptionExecutionResult firstResult = executor.Execute(eventItem, 0, context, firstInventory);
|
|
EventOptionExecutionResult secondResult = executor.Execute(eventItem, 0, context, secondInventory);
|
|
|
|
Assert.That(firstResult.IsAccepted, Is.True);
|
|
Assert.That(secondResult.IsAccepted, Is.True);
|
|
Assert.That(BuildComponentSignature(secondInventory), Is.EqualTo(BuildComponentSignature(firstInventory)));
|
|
}
|
|
|
|
[Test]
|
|
public void Execute_ComponentExchange_Removes_Two_And_Returns_One_In_Configured_Range()
|
|
{
|
|
BindInventoryGeneration();
|
|
EventOptionExecutor executor = new EventOptionExecutor();
|
|
EventItem eventItem = new EventItem(
|
|
5,
|
|
"组件交换",
|
|
"测试事件",
|
|
new[]
|
|
{
|
|
new EventOption(
|
|
"交出 2 个绿色组件,获得 1 个绿色或蓝色组件",
|
|
new EventRequirementBase[]
|
|
{
|
|
new CompCountAtLeastRequirement(new CompCountAtLeastParam(2, RarityType.Green))
|
|
},
|
|
new EventEffectBase[]
|
|
{
|
|
new RemoveRandomCompsEffect(new RemoveRandomCompsParam(2, RarityType.Green))
|
|
},
|
|
new EventEffectBase[]
|
|
{
|
|
new AddRandomCompsEffect(new AddRandomCompsParam(1, RarityType.Green, RarityType.Blue))
|
|
})
|
|
});
|
|
BackpackInventoryData inventory = new BackpackInventoryData
|
|
{
|
|
MuzzleComponents = new List<MuzzleCompItemData>
|
|
{
|
|
new MuzzleCompItemData
|
|
{
|
|
InstanceId = 10001,
|
|
Name = "绿枪口 A",
|
|
Rarity = RarityType.Green,
|
|
IsAssembledIntoTower = false
|
|
}
|
|
},
|
|
BearingComponents = new List<BearingCompItemData>
|
|
{
|
|
new BearingCompItemData
|
|
{
|
|
InstanceId = 20001,
|
|
Name = "绿轴承 B",
|
|
Rarity = RarityType.Green,
|
|
IsAssembledIntoTower = false
|
|
}
|
|
}
|
|
};
|
|
|
|
EventOptionExecutionResult result = executor.Execute(eventItem, 0, CreateContext(), inventory);
|
|
|
|
Assert.That(result.IsAccepted, Is.True);
|
|
Assert.That(CountLooseComponentsOfRarity(inventory, RarityType.Green), Is.LessThanOrEqualTo(1));
|
|
Assert.That(CountAllComponents(inventory), Is.EqualTo(1));
|
|
|
|
TowerCompItemData rewardItem = GetOnlyComponent(inventory);
|
|
Assert.That(rewardItem.Rarity == RarityType.Green || rewardItem.Rarity == RarityType.Blue, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void Execute_EnduranceForGold_Applies_Cost_Before_Reward()
|
|
{
|
|
EventOptionExecutor executor = new EventOptionExecutor();
|
|
EventItem eventItem = new EventItem(
|
|
6,
|
|
"耐久换金币",
|
|
"测试事件",
|
|
new[]
|
|
{
|
|
new EventOption(
|
|
"扣耐久换金币",
|
|
new EventRequirementBase[]
|
|
{
|
|
new TowerCountAtLeastRequirement(new TowerCountAtLeastParam(1))
|
|
},
|
|
new EventEffectBase[]
|
|
{
|
|
new DamageRandomTowerEnduranceEffect(new DamageRandomTowerEnduranceParam(1, 20))
|
|
},
|
|
new EventEffectBase[]
|
|
{
|
|
new AddGoldEffect(new AddGoldParam(50))
|
|
})
|
|
});
|
|
BackpackInventoryData inventory = CreateParticipantInventory(endurance: 30f);
|
|
inventory.Gold = 10;
|
|
|
|
EventOptionExecutionResult result = executor.Execute(eventItem, 0, CreateContext(), inventory);
|
|
|
|
Assert.That(result.IsAccepted, Is.True);
|
|
Assert.That(inventory.Gold, Is.EqualTo(60));
|
|
Assert.That(inventory.MuzzleComponents[0].Endurance, Is.EqualTo(10f));
|
|
Assert.That(inventory.BearingComponents[0].Endurance, Is.EqualTo(10f));
|
|
Assert.That(inventory.BaseComponents[0].Endurance, Is.EqualTo(10f));
|
|
}
|
|
|
|
[Test]
|
|
public void Execute_Damaged_Participant_Tower_Can_Be_Cleaned_Up_By_Procedure_Service()
|
|
{
|
|
EventOptionExecutor executor = new EventOptionExecutor();
|
|
EventItem eventItem = new EventItem(
|
|
4,
|
|
"耐久惩罚",
|
|
"测试事件",
|
|
new[]
|
|
{
|
|
new EventOption(
|
|
"损坏防御塔",
|
|
Array.Empty<EventRequirementBase>(),
|
|
Array.Empty<EventEffectBase>(),
|
|
new EventEffectBase[]
|
|
{
|
|
new DamageRandomTowerEnduranceEffect(new DamageRandomTowerEnduranceParam(1, 20))
|
|
})
|
|
});
|
|
BackpackInventoryData inventory = CreateParticipantInventory(endurance: 10f);
|
|
|
|
EventOptionExecutionResult result = executor.Execute(eventItem, 0, CreateContext(), inventory);
|
|
ProcedureMainParticipantTowerCleanupResult cleanupResult =
|
|
ProcedureMainParticipantTowerCleanupService.RemoveBrokenParticipantTowers(inventory, 4);
|
|
|
|
Assert.That(result.IsAccepted, Is.True);
|
|
Assert.That(cleanupResult.HasAnyRemovedTower, Is.True);
|
|
Assert.That(inventory.ParticipantTowerInstanceIds, Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void EventTable_Uses_Only_Runtime_Supported_Types()
|
|
{
|
|
string filePath = ResolveRepoFilePath("Assets/GameMain/DataTables/Event.txt");
|
|
Assert.That(File.Exists(filePath), Is.True, filePath);
|
|
|
|
HashSet<string> supportedRequirementTypes = new HashSet<string>
|
|
{
|
|
"GoldAtLeast",
|
|
"CompCountAtLeast",
|
|
"TowerCountAtLeast"
|
|
};
|
|
HashSet<string> supportedEffectTypes = new HashSet<string>
|
|
{
|
|
"AddGold",
|
|
"RemoveRandomComps",
|
|
"AddRandomComps",
|
|
"DamageRandomTowersEndurance"
|
|
};
|
|
|
|
foreach (string line in File.ReadLines(filePath))
|
|
{
|
|
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#", StringComparison.Ordinal))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
DREvent row = new DREvent();
|
|
Assert.That(row.ParseDataRow(line, null), Is.True, line);
|
|
JArray options = JArray.Parse(row.OptionsRaw);
|
|
for (int i = 0; i < options.Count; i++)
|
|
{
|
|
JObject optionObject = options[i] as JObject;
|
|
Assert.That(optionObject, Is.Not.Null, $"EventId={row.Id}, OptionIndex={i}");
|
|
AssertSupportedTypes(
|
|
row.Id,
|
|
i,
|
|
optionObject["requirements"] as JArray,
|
|
supportedRequirementTypes,
|
|
isRequirement: true);
|
|
AssertSupportedTypes(
|
|
row.Id,
|
|
i,
|
|
optionObject["costEffects"] as JArray,
|
|
supportedEffectTypes,
|
|
isRequirement: false);
|
|
AssertSupportedTypes(
|
|
row.Id,
|
|
i,
|
|
optionObject["rewardEffects"] as JArray,
|
|
supportedEffectTypes,
|
|
isRequirement: false);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void EventNodeComponent_Selects_Same_Event_For_Same_Context()
|
|
{
|
|
GameObject gameObject = new GameObject("EventNodeComponentTests");
|
|
try
|
|
{
|
|
EventNodeComponent component = gameObject.AddComponent<EventNodeComponent>();
|
|
List<EventItem> eventItems = GetPrivateField<List<EventItem>>(component, "_eventItems");
|
|
eventItems.Add(new EventItem(11, "事件一", string.Empty, Array.Empty<EventOption>()));
|
|
eventItems.Add(new EventItem(12, "事件二", string.Empty, Array.Empty<EventOption>()));
|
|
eventItems.Add(new EventItem(13, "事件三", string.Empty, Array.Empty<EventOption>()));
|
|
|
|
MethodInfo method = typeof(EventNodeComponent).GetMethod(
|
|
"SelectActiveEvent",
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(method, Is.Not.Null);
|
|
|
|
RunNodeExecutionContext context = CreateContext();
|
|
EventItem first = (EventItem)method.Invoke(component, new object[] { context });
|
|
EventItem second = (EventItem)method.Invoke(component, new object[] { context });
|
|
|
|
Assert.That(second, Is.Not.Null);
|
|
Assert.That(second.Id, Is.EqualTo(first.Id));
|
|
}
|
|
finally
|
|
{
|
|
Object.DestroyImmediate(gameObject);
|
|
}
|
|
}
|
|
|
|
private void BindInventoryGeneration()
|
|
{
|
|
_originalInventoryGeneration = GameEntry.InventoryGeneration;
|
|
_inventoryGenerationObject = new GameObject("EventInventoryGenerationTests");
|
|
InventoryGenerationComponent component = _inventoryGenerationObject.AddComponent<InventoryGenerationComponent>();
|
|
SetPrivateField(component, "_muzzleCompTable", new FakeDataTable<DRMuzzleComp>(
|
|
CreateMuzzleRow(1, "火焰枪口", "[Fire,Ice,Crit]"),
|
|
CreateMuzzleRow(2, "暴击枪口", "[Crit,Shatter,Execution]")));
|
|
SetPrivateField(component, "_bearingCompTable", new FakeDataTable<DRBearingComp>(
|
|
CreateBearingRow(1, "寒冰轴承", "[Ice,Shatter]"),
|
|
CreateBearingRow(2, "穿透轴承", "[Pierce,Crit]")));
|
|
SetPrivateField(component, "_baseCompTable", new FakeDataTable<DRBaseComp>(
|
|
CreateBaseRow(1, "迅捷底座", "[Fire,Crit]"),
|
|
CreateBaseRow(2, "处决底座", "[Execution,Ice]")));
|
|
SetStaticInventoryGeneration(component);
|
|
}
|
|
|
|
private static RunNodeExecutionContext CreateContext()
|
|
{
|
|
return new RunNodeExecutionContext
|
|
{
|
|
RunId = "testrun",
|
|
RunSeed = 12345,
|
|
NodeId = 101,
|
|
NodeType = RunNodeType.Event,
|
|
SequenceIndex = 3
|
|
};
|
|
}
|
|
|
|
private static BackpackInventoryData CreateParticipantInventory(float endurance)
|
|
{
|
|
return new BackpackInventoryData
|
|
{
|
|
MuzzleComponents = new List<MuzzleCompItemData>
|
|
{
|
|
new MuzzleCompItemData
|
|
{
|
|
InstanceId = 10001,
|
|
Name = "枪口",
|
|
Rarity = RarityType.Blue,
|
|
Endurance = endurance,
|
|
IsAssembledIntoTower = true
|
|
}
|
|
},
|
|
BearingComponents = new List<BearingCompItemData>
|
|
{
|
|
new BearingCompItemData
|
|
{
|
|
InstanceId = 20001,
|
|
Name = "轴承",
|
|
Rarity = RarityType.Blue,
|
|
Endurance = endurance,
|
|
IsAssembledIntoTower = true
|
|
}
|
|
},
|
|
BaseComponents = new List<BaseCompItemData>
|
|
{
|
|
new BaseCompItemData
|
|
{
|
|
InstanceId = 30001,
|
|
Name = "底座",
|
|
Rarity = RarityType.Blue,
|
|
Endurance = endurance,
|
|
IsAssembledIntoTower = true
|
|
}
|
|
},
|
|
Towers = new List<TowerItemData>
|
|
{
|
|
new TowerItemData
|
|
{
|
|
InstanceId = 90001,
|
|
Name = "测试防御塔",
|
|
MuzzleComponentInstanceId = 10001,
|
|
BearingComponentInstanceId = 20001,
|
|
BaseComponentInstanceId = 30001
|
|
}
|
|
},
|
|
ParticipantTowerInstanceIds = new List<long> { 90001 }
|
|
};
|
|
}
|
|
|
|
private static string BuildComponentSignature(BackpackInventoryData inventory)
|
|
{
|
|
IEnumerable<TowerCompItemData> items =
|
|
inventory.MuzzleComponents.Cast<TowerCompItemData>()
|
|
.Concat(inventory.BearingComponents)
|
|
.Concat(inventory.BaseComponents)
|
|
.OrderBy(item => item.InstanceId);
|
|
return string.Join(
|
|
"|",
|
|
items.Select(item =>
|
|
{
|
|
string tags = item.Tags == null || item.Tags.Length <= 0
|
|
? string.Empty
|
|
: string.Join(",", item.Tags.Select(tag => tag.ToString()));
|
|
return $"{item.InstanceId}:{item.SlotType}:{item.ConfigId}:{item.Name}:{item.Rarity}:{tags}";
|
|
}));
|
|
}
|
|
|
|
private static int CountLooseComponentsOfRarity(BackpackInventoryData inventory, RarityType rarity)
|
|
{
|
|
return GetAllComponents(inventory)
|
|
.Count(component => !component.IsAssembledIntoTower && component.Rarity == rarity);
|
|
}
|
|
|
|
private static int CountAllComponents(BackpackInventoryData inventory)
|
|
{
|
|
return GetAllComponents(inventory).Count();
|
|
}
|
|
|
|
private static TowerCompItemData GetOnlyComponent(BackpackInventoryData inventory)
|
|
{
|
|
return GetAllComponents(inventory).Single();
|
|
}
|
|
|
|
private static IEnumerable<TowerCompItemData> GetAllComponents(BackpackInventoryData inventory)
|
|
{
|
|
return inventory.MuzzleComponents.Cast<TowerCompItemData>()
|
|
.Concat(inventory.BearingComponents)
|
|
.Concat(inventory.BaseComponents);
|
|
}
|
|
|
|
private static void AssertSupportedTypes(
|
|
int eventId,
|
|
int optionIndex,
|
|
JArray entries,
|
|
HashSet<string> supportedTypes,
|
|
bool isRequirement)
|
|
{
|
|
if (entries == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < entries.Count; i++)
|
|
{
|
|
JObject entry = entries[i] as JObject;
|
|
Assert.That(entry, Is.Not.Null, $"EventId={eventId}, OptionIndex={optionIndex}, EntryIndex={i}");
|
|
string rawType = entry.Value<string>("type");
|
|
Assert.That(
|
|
supportedTypes.Contains(rawType),
|
|
Is.True,
|
|
$"{(isRequirement ? "Requirement" : "Effect")} type '{rawType}' is not supported at runtime. EventId={eventId}, OptionIndex={optionIndex}, EntryIndex={i}");
|
|
}
|
|
}
|
|
|
|
private static void SetStaticInventoryGeneration(InventoryGenerationComponent component)
|
|
{
|
|
FieldInfo backingField = typeof(GameEntry).GetField(
|
|
"<InventoryGeneration>k__BackingField",
|
|
BindingFlags.Static | BindingFlags.NonPublic);
|
|
Assert.That(backingField, Is.Not.Null);
|
|
backingField.SetValue(null, component);
|
|
}
|
|
|
|
private static TField GetPrivateField<TField>(object instance, string fieldName)
|
|
{
|
|
FieldInfo field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
|
Assert.That(field, Is.Not.Null, fieldName);
|
|
return (TField)field.GetValue(instance);
|
|
}
|
|
|
|
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, fieldName);
|
|
field.SetValue(instance, value);
|
|
}
|
|
|
|
private static string ResolveRepoFilePath(string relativePath)
|
|
{
|
|
DirectoryInfo directory = new DirectoryInfo(Directory.GetCurrentDirectory());
|
|
while (directory != null)
|
|
{
|
|
string candidate = Path.Combine(directory.FullName, relativePath);
|
|
if (File.Exists(candidate))
|
|
{
|
|
return candidate;
|
|
}
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
return Path.Combine(Directory.GetCurrentDirectory(), relativePath);
|
|
}
|
|
|
|
private static DRMuzzleComp CreateMuzzleRow(int id, string name, string possibleTags)
|
|
{
|
|
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{possibleTags}", null),
|
|
Is.True);
|
|
return row;
|
|
}
|
|
|
|
private static DRBearingComp CreateBearingRow(int id, string name, string possibleTags)
|
|
{
|
|
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{possibleTags}", null),
|
|
Is.True);
|
|
return row;
|
|
}
|
|
|
|
private static DRBaseComp CreateBaseRow(int id, string name, string possibleTags)
|
|
{
|
|
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{possibleTags}", 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)
|
|
{
|
|
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<TRow> condition) => GetDataRow(condition) != null;
|
|
|
|
public TRow GetDataRow(int id) => _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) => _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 = _rowsById.Keys.ToArray();
|
|
Array.Sort(ids);
|
|
return ids;
|
|
}
|
|
}
|
|
}
|
|
}
|