using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using GameCult.Aetheria.State.Unity; using Unity.Mathematics; public interface IRuntimeItemProjectionReader { AetheriaRuntimeCatalogItem GetRuntimeItem(Guid guid); IReadOnlyList GetBehaviorProjections(Guid guid); } public sealed class AetheriaRuntimeItemCatalog : IRuntimeItemProjectionReader { private readonly Dictionary _typedItems; private static readonly IReadOnlyDictionary BehaviorTypesByUnionKey = new Dictionary { { 0, typeof(GuidedWeaponData) }, { 1, typeof(LauncherData) }, { 2, typeof(ReactorData) }, { 3, typeof(RadiatorData) }, { 4, typeof(StatModifierData) }, { 5, typeof(SensorData) }, { 6, typeof(ReflectorData) }, { 7, typeof(ShieldData) }, { 8, typeof(ThrusterData) }, { 9, typeof(WearData) }, { 10, typeof(VelocityConversionData) }, { 11, typeof(VelocityLimitData) }, { 12, typeof(AetherDriveData) }, { 15, typeof(CooldownData) }, { 16, typeof(HeatData) }, { 18, typeof(ItemUsageData) }, { 20, typeof(SwitchData) }, { 21, typeof(TriggerData) }, { 22, typeof(VisibilityData) }, { 23, typeof(ThermotoggleData) }, { 24, typeof(EnergyDrawData) }, { 26, typeof(MiningToolData) }, { 28, typeof(ResourceScannerData) }, { 31, typeof(CapacitorData) }, { 32, typeof(CockpitData) }, { 33, typeof(HeatStorageData) }, { 34, typeof(TurretControllerData) }, { 35, typeof(InstantWeaponData) }, { 36, typeof(ConstantWeaponData) }, { 37, typeof(ChargedWeaponData) }, { 38, typeof(AutoWeaponData) } }; public AetheriaRuntimeItemCatalog(AetheriaRuntimeCatalogSnapshot catalog) { if (catalog == null) throw new ArgumentNullException(nameof(catalog)); _typedItems = catalog.Items .Where(item => Guid.TryParse(item.LegacyId, out _)) .ToDictionary(item => Guid.Parse(item.LegacyId), item => item); } public AetheriaRuntimeCatalogItem GetRuntimeItem(Guid guid) { _typedItems.TryGetValue(guid, out var item); return item; } public IReadOnlyList GetBehaviorProjections(Guid guid) { var item = GetRuntimeItem(guid); return item == null ? Array.Empty() : ProjectBehaviors(item); } private static BehaviorData[] ProjectBehaviors(AetheriaRuntimeCatalogItem item) { return item.BehaviorPayloads .Select(ProjectBehavior) .Where(behavior => behavior != null) .ToArray(); } private static PerformanceStat ProjectPerformanceStat(AetheriaRuntimePerformanceStat stat) { return new PerformanceStat { Min = (float)stat.Min, Max = (float)stat.Max, HeatExponentMultiplier = (float)stat.HeatExponentMultiplier, DurabilityExponentMultiplier = (float)stat.DurabilityExponentMultiplier, QualityExponent = (float)stat.QualityExponent }; } private static BehaviorData ProjectBehavior(AetheriaRuntimeBehaviorPayload payload) { if (!BehaviorTypesByUnionKey.TryGetValue(payload.UnionKey, out var behaviorType)) { return null; } var behavior = Activator.CreateInstance(behaviorType) as BehaviorData; if (behavior == null) { return null; } behavior.Group = payload.Group; foreach (var field in GetKeyedFields(behaviorType)) { var payloadField = payload.Fields.FirstOrDefault(candidate => candidate.Key == field.key); if (payloadField == null) { continue; } field.field.SetValue(behavior, ConvertValue(payloadField.Value, field.field.FieldType)); } return behavior; } private static IEnumerable<(int key, FieldInfo field)> GetKeyedFields(Type type) { return type .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Select(field => (attribute: field.GetCustomAttribute(), field)) .Where(entry => entry.attribute != null) .Select(entry => (entry.attribute.Key, entry.field)); } private static object ConvertValue(AetheriaRuntimeBehaviorValue value, Type targetType) { if (targetType == typeof(string)) return value.StringValue ?? ""; if (targetType == typeof(bool)) return value.BoolValue; if (targetType == typeof(float)) return (float)value.NumberValue; if (targetType == typeof(double)) return value.NumberValue; if (targetType == typeof(int)) return checked((int)value.NumberValue); if (targetType == typeof(uint)) return checked((uint)value.NumberValue); if (targetType == typeof(Guid)) return ParseGuid(value.LegacyIdValue); if (targetType == typeof(float2)) return ConvertFloat2(value); if (targetType == typeof(float3)) return ConvertFloat3(value); if (targetType == typeof(float4)) return ConvertFloat4(value); if (targetType == typeof(int2)) return ConvertInt2(value); if (targetType.IsEnum) return ParseEnumValue(value, targetType); if (targetType.IsArray) { return ConvertArray(value, targetType.GetElementType()); } if (typeof(IList).IsAssignableFrom(targetType) && targetType.IsGenericType) { return ConvertList(value, targetType); } if (GetKeyedFields(targetType).Any()) { return ConvertKeyedObject(value, targetType); } return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; } private static object ConvertArray(AetheriaRuntimeBehaviorValue value, Type elementType) { var array = Array.CreateInstance(elementType, value.Children.Count); for (var index = 0; index < value.Children.Count; index++) { array.SetValue(ConvertValue(value.Children[index], elementType), index); } return array; } private static object ConvertList(AetheriaRuntimeBehaviorValue value, Type targetType) { var elementType = targetType.GetGenericArguments()[0]; var list = Activator.CreateInstance(targetType) as IList; foreach (var child in value.Children) { list.Add(ConvertValue(child, elementType)); } return list; } private static object ConvertKeyedObject(AetheriaRuntimeBehaviorValue value, Type targetType) { var instance = Activator.CreateInstance(targetType); var children = value.Children; foreach (var field in GetKeyedFields(targetType)) { if (field.key >= 0 && field.key < children.Count) { field.field.SetValue(instance, ConvertValue(children[field.key], field.field.FieldType)); } } return instance; } private static object ParseEnumValue(AetheriaRuntimeBehaviorValue value, Type targetType) { if (!string.IsNullOrWhiteSpace(value.StringValue)) { return Enum.Parse(targetType, value.StringValue, true); } return Enum.ToObject(targetType, checked((int)value.NumberValue)); } private static Guid ParseGuid(string value) { return Guid.TryParse(value, out var result) ? result : Guid.Empty; } private static float2 ConvertFloat2(AetheriaRuntimeBehaviorValue value) { return new float2( value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0); } private static float3 ConvertFloat3(AetheriaRuntimeBehaviorValue value) { return new float3( value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0, value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0); } private static float4 ConvertFloat4(AetheriaRuntimeBehaviorValue value) { return new float4( value.Children.Count > 0 ? (float)value.Children[0].NumberValue : 0, value.Children.Count > 1 ? (float)value.Children[1].NumberValue : 0, value.Children.Count > 2 ? (float)value.Children[2].NumberValue : 0, value.Children.Count > 3 ? (float)value.Children[3].NumberValue : 0); } private static int2 ConvertInt2(AetheriaRuntimeBehaviorValue value) { return new int2( value.Children.Count > 0 ? checked((int)value.Children[0].NumberValue) : 0, value.Children.Count > 1 ? checked((int)value.Children[1].NumberValue) : 0); } }