Move cargo item projection into runtime

This commit is contained in:
Meta Vens 2026-06-21 22:20:21 +01:00
commit c2bbdff76e
4 changed files with 562 additions and 122 deletions

View file

@ -4225,6 +4225,7 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
"org.gamecult.aetheria.state",
"Runtime",
"AetheriaRuntimeEquippedItemDetailsSurfaceBuilder.cs");
var unityProjectPath = Path.Combine(root, "GameCult.Aetheria.State.Unity.csproj");
var requiredFiles = new[]
{
inventoryMenuPath,
@ -4234,7 +4235,8 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
eveUnitySurfaceHostPath,
runtimeEveSurfaceAdapterPath,
cargoItemSurfaceBuilderPath,
equippedItemSurfaceBuilderPath
equippedItemSurfaceBuilderPath,
unityProjectPath
};
var missingFiles = requiredFiles
.Where(path => !File.Exists(path))
@ -4251,6 +4253,7 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
var actionGameManager = File.ReadAllText(actionGameManagerPath);
var surfaceDocument = File.ReadAllText(surfaceDocumentPath);
var daemonItemStatQueries = File.ReadAllText(daemonItemStatQueriesPath);
var unityProject = File.ReadAllText(unityProjectPath);
var eveUnitySurfaceHost = File.ReadAllText(eveUnitySurfaceHostPath);
var runtimeEveSurfaceAdapter = File.ReadAllText(runtimeEveSurfaceAdapterPath);
var cargoItemSurfaceBuilder = File.ReadAllText(cargoItemSurfaceBuilderPath);
@ -4261,12 +4264,11 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
"HandleCargoItemDetailsSurfaceCommand(",
"AetheriaEveUnitySurfaceHost.RenderRuntime(",
"AetheriaEveUnitySurfaceHost.Hide(_cargoItemDetailsSurfaceDocument)",
"AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Build(ProjectCargoItemDetailsSurfaceState(",
"AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Build(ProjectCargoItemDetailsSurface(",
"AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Project(",
"ProjectCargoItemObservation(",
"AetheriaRuntimeCargoItemDetailsSurfaceCommands.TryRead(request, out var command)",
"AetheriaRuntimeCargoItemDetailsCommandKind.Close",
"ProjectCargoItemDetailsSurfaceState(",
"ProjectCargoItemBehaviorSections(",
"ProjectCargoItemBehaviorMetric("
"AetheriaRuntimeCargoItemDetailsCommandKind.Close"
};
var missingSymbols = requiredSymbols
@ -4307,6 +4309,7 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
!daemonItemStatQueries.Contains("public static string ItemStatRef(", StringComparison.Ordinal) ||
!daemonItemStatQueries.Contains("public static bool TryReadItemStatRef(", StringComparison.Ordinal) ||
!daemonItemStatQueries.Contains("public string ValueRef =>", StringComparison.Ordinal) ||
!unityProject.Contains("AetheriaRuntimeDaemonItemStatQueries.cs", StringComparison.Ordinal) ||
!eveUnitySurfaceHost.Contains("Func<string, string> stateRefResolver", StringComparison.Ordinal) ||
!eveUnitySurfaceHost.Contains("ContainsStateRefs(surface)", StringComparison.Ordinal) ||
!eveUnitySurfaceHost.Contains("CreateDefaultStateRefResolver()", StringComparison.Ordinal) ||
@ -4333,7 +4336,11 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
"ResolveCargoItemDetailsSurfaceDocument(",
"new EveUiToolkitSurfaceLowerer()",
"host.AddComponent<UIDocument>",
"string.Equals(request.Command, AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Close"
"string.Equals(request.Command, AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Close",
"new AetheriaRuntimeCargoItemDetailsSurfaceState(",
"ProjectCargoItemDetailsSurfaceState(",
"ProjectCargoItemBehaviorSections(",
"ProjectCargoItemBehaviorMetric("
};
var hits = forbiddenSymbols
.Where(symbol => source.Contains(symbol, StringComparison.Ordinal))
@ -4355,8 +4362,12 @@ static void RequireInventoryCargoItemDetailsUseEveSurface(string root)
"public static class AetheriaRuntimeCargoItemDetailsSurfaceCommands",
"public static bool TryRead(",
"AetheriaRuntimeCargoItemDetailsSurfaceState",
"AetheriaRuntimeCargoItemObservation",
"AetheriaRuntimeCargoItemSection",
"AetheriaRuntimeCargoItemMetric",
"public static AetheriaRuntimeCargoItemDetailsSurfaceState Project(",
"ProjectBehaviorSections(",
"ProjectBehaviorMetric(",
"public static AetheriaRuntimeSurfaceDocument Build("
};
var missingBuilderSymbols = requiredBuilderSymbols

View file

@ -258,7 +258,7 @@ public class InventoryMenu : MonoBehaviour
transform,
_cargoItemDetailsSurfaceDocument,
"Aetheria Inventory Cargo Item Details Surface",
AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Build(ProjectCargoItemDetailsSurfaceState(item, typedItem)),
AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Build(ProjectCargoItemDetailsSurface(item, typedItem)),
HandleCargoItemDetailsSurfaceCommand,
_cargoItemDetailsSurfaceChrome,
ResolveInventorySurfaceStateRef,
@ -494,127 +494,30 @@ public class InventoryMenu : MonoBehaviour
}
}
private AetheriaRuntimeCargoItemDetailsSurfaceState ProjectCargoItemDetailsSurfaceState(
private AetheriaRuntimeCargoItemDetailsSurfaceState ProjectCargoItemDetailsSurface(
ItemInstance item,
AetheriaRuntimeCatalogItem typedItem)
{
var quantity = item is SimpleCommodity simpleCommodity ? simpleCommodity.Quantity : 0;
var tier = "";
var durability = "";
var thermalRange = "";
var behaviorSections = Array.Empty<AetheriaRuntimeCargoItemSection>();
if (item is EquippableItem equippableItem)
{
tier = FormatItemTier(typedItem, equippableItem);
durability = $"{(int)(equippableItem.Durability / GetMaxDurability(typedItem, equippableItem) * 100)}%";
thermalRange = FormatTemperatureRange(typedItem);
behaviorSections = ProjectCargoItemBehaviorSections(typedItem, equippableItem).ToArray();
}
return new AetheriaRuntimeCargoItemDetailsSurfaceState(
typedItem.Name,
typedItem.Description ?? "",
return AetheriaRuntimeCargoItemDetailsSurfaceBuilder.Project(
typedItem,
ProjectCargoItemObservation(item),
ActionGameManager.RuntimeCatalog?.GetManufacturer(typedItem)?.Name ?? "GameCult",
ActionGameManager.RuntimePlayerSettings.Format(GetCargoItemMass(item, typedItem)),
typedItem.Price,
quantity,
tier,
durability,
thermalRange,
behaviorSections,
DateTime.UtcNow.ToString("O"));
item is EquippableItem equippableItem ? FormatItemTier(typedItem, equippableItem) : "",
ActionGameManager.RuntimePlayerSettings.Format,
ActionGameManager.RuntimePlayerSettings.FormatTemperature);
}
private IEnumerable<AetheriaRuntimeCargoItemSection> ProjectCargoItemBehaviorSections(
AetheriaRuntimeCatalogItem typedItem,
EquippableItem equippableItem)
private static AetheriaRuntimeCargoItemObservation ProjectCargoItemObservation(ItemInstance item)
{
foreach (var behavior in typedItem.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>())
{
if (string.Equals(behavior.Kind, AetheriaRuntimeBehaviorKinds.StatModifier, StringComparison.Ordinal))
{
var statReference = AetheriaRuntimeBehaviorValueReader.ReadStatReference(FindTypedBehaviorField(behavior, 1)?.Value);
var modifier = AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value);
var modifierType = AetheriaRuntimeBehaviorValueReader.ReadEnum(
FindTypedBehaviorField(behavior, 3)?.Value,
StatModifierType.Constant);
yield return new AetheriaRuntimeCargoItemSection(
$"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.stat_modifier",
"Stat Modifier",
new[]
{
new AetheriaRuntimeCargoItemMetric(
$"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.target",
$"{statReference.Target.SplitCamelCase()}:{statReference.Stat.SplitCamelCase()}",
$"{(modifierType == StatModifierType.Constant ? "+" : "x")}{FormatCurrentItemStat(modifier, equippableItem)}",
AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(
equippableItem.ItemKey,
behavior.Kind,
behavior.Group,
2))
});
continue;
}
var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind);
if (metadata == null)
continue;
var fields = metadata.DisplayFields
.Select(field => ProjectCargoItemBehaviorMetric(behavior, field, equippableItem))
.Where(metric => metric != null)
.ToArray();
if (fields.Length == 0)
continue;
yield return new AetheriaRuntimeCargoItemSection(
$"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}",
behavior.Kind.FormatTypeName(),
fields);
}
}
private AetheriaRuntimeCargoItemMetric ProjectCargoItemBehaviorMetric(
AetheriaRuntimeBehaviorPayload behavior,
AetheriaRuntimeBehaviorFieldMetadata field,
EquippableItem equippableItem)
{
var payloadField = FindTypedBehaviorField(behavior, field.Key);
if (payloadField == null)
return null;
string value;
switch (field.ValueKind)
{
case AetheriaRuntimeBehaviorFieldValueKind.Number:
value = ActionGameManager.RuntimePlayerSettings.Format((float)payloadField.Value.NumberValue);
break;
case AetheriaRuntimeBehaviorFieldValueKind.Temperature:
value = ActionGameManager.RuntimePlayerSettings.FormatTemperature((float)payloadField.Value.NumberValue);
break;
case AetheriaRuntimeBehaviorFieldValueKind.Integer:
value = ((int)payloadField.Value.NumberValue).ToString();
break;
case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat:
value = FormatCurrentItemStat(payloadField.Value, equippableItem);
break;
default:
return null;
}
return new AetheriaRuntimeCargoItemMetric(
$"{AetheriaRuntimeCargoItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.{field.Key}",
field.Name.SplitCamelCase(),
value,
field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat
? AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(
equippableItem.ItemKey,
behavior.Kind,
behavior.Group,
field.Key)
: "");
var equippableItem = item as EquippableItem;
return new AetheriaRuntimeCargoItemObservation(
item?.ItemKey ?? "",
item is SimpleCommodity simpleCommodity ? simpleCommodity.Quantity : 0,
equippableItem != null,
equippableItem?.Quality ?? 1,
equippableItem?.Durability ?? 1,
equippableItem?.Temperature ?? 0,
equippableItem != null && equippableItem.OverrideShutdown);
}
private IEnumerable<AetheriaRuntimeEquippedItemSection> ProjectEquippedItemBehaviorSections(

View file

@ -82,6 +82,7 @@
<Compile Include="Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeMenuTabsSurfaceBuilder.cs" />
<Compile Include="Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimePlayerSettingsSurfaceBuilder.cs" />
<Compile Include="Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonStateRefs.cs" />
<Compile Include="Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonItemStatQueries.cs" />
<Compile Include="Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonGameSurfaceBuilder.cs" />
<Compile Include="Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonEditorSurfaceBuilder.cs" />
<Compile Include="Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonOperationClient.cs" />

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using GameCult.Eve.Surface;
@ -83,11 +84,94 @@ namespace GameCult.Aetheria.State.Verse
!string.IsNullOrWhiteSpace(ThermalRange);
}
public sealed class AetheriaRuntimeCargoItemObservation
{
public AetheriaRuntimeCargoItemObservation(
string itemKey,
int quantity,
bool isEquippable,
double quality,
double durability,
double temperature,
bool overrideShutdown)
{
ItemKey = itemKey ?? "";
Quantity = quantity;
IsEquippable = isEquippable;
Quality = quality;
Durability = durability;
Temperature = temperature;
OverrideShutdown = overrideShutdown;
}
public string ItemKey { get; }
public int Quantity { get; }
public bool IsEquippable { get; }
public double Quality { get; }
public double Durability { get; }
public double Temperature { get; }
public bool OverrideShutdown { get; }
}
public static class AetheriaRuntimeCargoItemDetailsSurfaceBuilder
{
public const string SurfaceId = "aetheria.inventory.cargo_item_details";
public const string Close = "aetheria.inventory.cargo_item_details.close";
public static AetheriaRuntimeCargoItemDetailsSurfaceState Project(
AetheriaRuntimeCatalogItem typedItem,
AetheriaRuntimeCargoItemObservation item,
string manufacturer,
string tier,
Func<float, string> formatValue,
Func<float, string> formatTemperature,
DateTime updatedAtUtc = default(DateTime))
{
if (updatedAtUtc == default(DateTime))
updatedAtUtc = DateTime.UtcNow;
if (typedItem == null)
{
return new AetheriaRuntimeCargoItemDetailsSurfaceState(
"",
"",
manufacturer ?? "",
"",
0,
item?.Quantity ?? 0,
"",
"",
"",
Array.Empty<AetheriaRuntimeCargoItemSection>(),
updatedAtUtc.ToString("O", CultureInfo.InvariantCulture));
}
var quantity = item?.Quantity ?? 0;
var durability = "";
var thermalRange = "";
var behaviorSections = Array.Empty<AetheriaRuntimeCargoItemSection>();
if (item != null && item.IsEquippable)
{
durability = $"{(int)(item.Durability / MaxDurability(typedItem, item) * 100)}%";
thermalRange = FormatTemperatureRange(typedItem, formatTemperature);
behaviorSections = ProjectBehaviorSections(typedItem, item, formatValue, formatTemperature).ToArray();
}
return new AetheriaRuntimeCargoItemDetailsSurfaceState(
typedItem.Name,
typedItem.Description ?? "",
manufacturer ?? "",
FormatValue(Mass(typedItem, quantity), formatValue),
typedItem.Price,
quantity,
tier ?? "",
durability,
thermalRange,
behaviorSections,
updatedAtUtc.ToString("O", CultureInfo.InvariantCulture));
}
public static AetheriaRuntimeSurfaceDocument Build(
AetheriaRuntimeCargoItemDetailsSurfaceState state,
long version = 1)
@ -187,6 +271,429 @@ namespace GameCult.Aetheria.State.Verse
});
}
private static IEnumerable<AetheriaRuntimeCargoItemSection> ProjectBehaviorSections(
AetheriaRuntimeCatalogItem typedItem,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue,
Func<float, string> formatTemperature)
{
foreach (var behavior in typedItem.BehaviorPayloads ?? Array.Empty<AetheriaRuntimeBehaviorPayload>())
{
if (string.Equals(behavior.Kind, AetheriaRuntimeBehaviorKinds.StatModifier, StringComparison.Ordinal))
{
var statReference = ReadStatReference(FindTypedBehaviorField(behavior, 1)?.Value);
var modifier = ReadPerformanceStat(FindTypedBehaviorField(behavior, 2)?.Value);
var modifierType = ReadEnum(
FindTypedBehaviorField(behavior, 3)?.Value,
AetheriaRuntimeCargoItemStatModifierType.Constant);
yield return new AetheriaRuntimeCargoItemSection(
$"{SurfaceId}.behavior.{behavior.Kind}.stat_modifier",
"Stat Modifier",
new[]
{
new AetheriaRuntimeCargoItemMetric(
$"{SurfaceId}.behavior.{behavior.Kind}.target",
$"{SplitCamelCase(statReference.Target)}:{SplitCamelCase(statReference.Stat)}",
$"{(modifierType == AetheriaRuntimeCargoItemStatModifierType.Constant ? "+" : "x")}{FormatCurrentItemStat(modifier, item, formatValue)}",
AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(
item.ItemKey,
behavior.Kind,
behavior.Group,
2))
});
continue;
}
var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind);
if (metadata == null)
continue;
var fields = metadata.DisplayFields
.Select(field => ProjectBehaviorMetric(behavior, field, item, formatValue, formatTemperature))
.Where(metric => metric != null)
.ToArray();
if (fields.Length == 0)
continue;
yield return new AetheriaRuntimeCargoItemSection(
$"{SurfaceId}.behavior.{behavior.Kind}",
FormatTypeName(behavior.Kind),
fields);
}
}
private static AetheriaRuntimeCargoItemMetric ProjectBehaviorMetric(
AetheriaRuntimeBehaviorPayload behavior,
AetheriaRuntimeBehaviorFieldMetadata field,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue,
Func<float, string> formatTemperature)
{
var payloadField = FindTypedBehaviorField(behavior, field.Key);
if (payloadField == null)
return null;
string value;
switch (field.ValueKind)
{
case AetheriaRuntimeBehaviorFieldValueKind.Number:
value = FormatValue(payloadField.Value.NumberValue, formatValue);
break;
case AetheriaRuntimeBehaviorFieldValueKind.Temperature:
value = FormatTemperature(payloadField.Value.NumberValue, formatTemperature);
break;
case AetheriaRuntimeBehaviorFieldValueKind.Integer:
value = ((int)payloadField.Value.NumberValue).ToString(CultureInfo.InvariantCulture);
break;
case AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat:
value = FormatCurrentItemStat(payloadField.Value, item, formatValue);
break;
default:
return null;
}
return new AetheriaRuntimeCargoItemMetric(
$"{SurfaceId}.behavior.{behavior.Kind}.{field.Key}",
SplitCamelCase(field.Name),
value,
field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat
? AetheriaRuntimeDaemonItemStatQueries.ItemStatRef(
item.ItemKey,
behavior.Kind,
behavior.Group,
field.Key)
: "");
}
private static string FormatCurrentItemStat(
AetheriaRuntimePerformanceStat stat,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue)
{
return FormatValue(
AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(
ToBehaviorValue(stat),
ToLoadoutItem(item),
item?.Temperature ?? 0),
formatValue);
}
private static string FormatCurrentItemStat(
AetheriaRuntimeBehaviorValue stat,
AetheriaRuntimeCargoItemObservation item,
Func<float, string> formatValue)
{
return FormatValue(
AetheriaRuntimeDaemonItemStatQueries.EvaluatePerformanceStat(
stat,
ToLoadoutItem(item),
item?.Temperature ?? 0),
formatValue);
}
private static AetheriaRuntimeLoadoutItemCommit ToLoadoutItem(AetheriaRuntimeCargoItemObservation item)
{
return AetheriaRuntimeDaemonItemStatQueries.ItemCommit(
item?.ItemKey ?? "",
item?.Quality ?? 1,
item?.Durability ?? 1,
enabled: true,
overrideShutdown: item != null && item.OverrideShutdown);
}
private static double Mass(AetheriaRuntimeCatalogItem typedItem, int quantity)
{
if (typedItem == null)
return 0;
return quantity > 0
? typedItem.Mass * quantity
: typedItem.Mass;
}
private static double MaxDurability(
AetheriaRuntimeCatalogItem typedItem,
AetheriaRuntimeCargoItemObservation item)
{
if (typedItem != null && typedItem.Durability > 0)
return typedItem.Durability;
return Math.Max(item?.Durability ?? 1, 1);
}
private static string FormatTemperatureRange(
AetheriaRuntimeCatalogItem item,
Func<float, string> formatTemperature)
{
if (item.MaximumTemperature > item.MinimumTemperature)
{
return
$"{FormatTemperature(item.MinimumTemperature, formatTemperature)} to " +
$"{FormatTemperature(item.MaximumTemperature, formatTemperature)}";
}
return "No typed thermal range";
}
private static AetheriaRuntimeBehaviorField FindTypedBehaviorField(
AetheriaRuntimeBehaviorPayload behavior,
int? key)
{
return key == null
? null
: behavior?.Fields?.FirstOrDefault(field => field.Key == key.Value);
}
private static AetheriaRuntimePerformanceStat ReadPerformanceStat(AetheriaRuntimeBehaviorValue value)
{
return new AetheriaRuntimePerformanceStat(
ChildNumber(value, 0),
ChildNumber(value, 1),
ChildNumber(value, 2),
ChildNumber(value, 3),
ChildNumber(value, 4),
ReadStatRecipe(ChildValue(value, 5)));
}
private static AetheriaRuntimeStatRecipe ReadStatRecipe(AetheriaRuntimeBehaviorValue value)
{
if (value == null || value.Children.Count == 0)
return null;
var modifiers = ChildValue(value, 1)?.Children
.Select(ReadStatRecipeModifier)
.Where(modifier => modifier != null)
.ToArray() ?? Array.Empty<AetheriaRuntimeStatRecipeModifier>();
return new AetheriaRuntimeStatRecipe(ChildNumber(value, 0), modifiers);
}
private static AetheriaRuntimeStatRecipeModifier ReadStatRecipeModifier(AetheriaRuntimeBehaviorValue value)
{
if (value == null)
return null;
return new AetheriaRuntimeStatRecipeModifier(
ChildString(value, 0),
ChildString(value, 1),
ChildNumber(value, 2),
ReadCurveKeys(ChildValue(value, 3)),
value.Children.Count <= 4 || ChildValue(value, 4)?.BoolValue == true);
}
private static IReadOnlyList<AetheriaRuntimeCurveKey> ReadCurveKeys(AetheriaRuntimeBehaviorValue value)
{
if (value?.Children == null || value.Children.Count == 0)
return Array.Empty<AetheriaRuntimeCurveKey>();
return value.Children
.Where(key => key.Children.Count >= 4)
.Select(key => new AetheriaRuntimeCurveKey(
ChildNumber(key, 0),
ChildNumber(key, 1),
ChildNumber(key, 2),
ChildNumber(key, 3)))
.ToArray();
}
private static AetheriaRuntimeCargoItemStatReference ReadStatReference(AetheriaRuntimeBehaviorValue value)
{
return new AetheriaRuntimeCargoItemStatReference(
ChildString(value, 1),
ChildString(value, 2));
}
private static T ReadEnum<T>(AetheriaRuntimeBehaviorValue value, T fallback) where T : struct
{
if (!string.IsNullOrWhiteSpace(value?.StringValue) && Enum.TryParse(value.StringValue, true, out T parsed))
return parsed;
return value != null && Enum.IsDefined(typeof(T), (int)value.NumberValue)
? (T)Enum.ToObject(typeof(T), (int)value.NumberValue)
: fallback;
}
private static AetheriaRuntimeBehaviorValue ToBehaviorValue(AetheriaRuntimePerformanceStat stat)
{
return new AetheriaRuntimeBehaviorValue(
"performance-stat",
"",
0,
false,
"",
"",
new[]
{
Number(stat?.Min ?? 0),
Number(stat?.Max ?? 0),
Number(stat?.HeatExponentMultiplier ?? 0),
Number(stat?.DurabilityExponentMultiplier ?? 0),
Number(stat?.QualityExponent ?? 0),
StatRecipeValue(stat?.Recipe)
},
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue StatRecipeValue(AetheriaRuntimeStatRecipe recipe)
{
if (recipe == null)
return EmptyValue("stat-recipe");
return new AetheriaRuntimeBehaviorValue(
"stat-recipe",
"",
0,
false,
"",
"",
new[]
{
Number(recipe.BaseValue),
new AetheriaRuntimeBehaviorValue(
"stat-recipe-modifiers",
"",
0,
false,
"",
"",
(recipe.Modifiers ?? Array.Empty<AetheriaRuntimeStatRecipeModifier>())
.Select(StatRecipeModifierValue)
.ToArray(),
EmptyMapEntries())
},
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue StatRecipeModifierValue(AetheriaRuntimeStatRecipeModifier modifier)
{
if (modifier == null)
return EmptyValue("stat-recipe-modifier");
return new AetheriaRuntimeBehaviorValue(
"stat-recipe-modifier",
"",
0,
false,
"",
"",
new[]
{
TextValue(modifier.Condition),
TextValue(modifier.Operation),
Number(modifier.Amount),
CurveValue(modifier.CurveKeys),
BoolValue(modifier.Enabled)
},
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue CurveValue(IReadOnlyList<AetheriaRuntimeCurveKey> keys)
{
return new AetheriaRuntimeBehaviorValue(
"curve",
"",
0,
false,
"",
"",
(keys ?? Array.Empty<AetheriaRuntimeCurveKey>())
.Select(key => new AetheriaRuntimeBehaviorValue(
"curve-key",
"",
0,
false,
"",
"",
new[]
{
Number(key.Time),
Number(key.Value),
Number(key.InTangent),
Number(key.OutTangent)
},
EmptyMapEntries()))
.ToArray(),
EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue Number(double value)
{
return new AetheriaRuntimeBehaviorValue("", "", value, false, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue TextValue(string value)
{
return new AetheriaRuntimeBehaviorValue("", value ?? "", 0, false, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue BoolValue(bool value)
{
return new AetheriaRuntimeBehaviorValue("", "", 0, value, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static AetheriaRuntimeBehaviorValue EmptyValue(string kind)
{
return new AetheriaRuntimeBehaviorValue(kind ?? "", "", 0, false, "", "", Array.Empty<AetheriaRuntimeBehaviorValue>(), EmptyMapEntries());
}
private static IReadOnlyList<AetheriaRuntimeBehaviorMapEntry> EmptyMapEntries()
{
return Array.Empty<AetheriaRuntimeBehaviorMapEntry>();
}
private static double ChildNumber(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index].NumberValue : 0;
}
private static string ChildString(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index].StringValue ?? "" : "";
}
private static AetheriaRuntimeBehaviorValue ChildValue(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index] : null;
}
private static string FormatValue(
double value,
Func<float, string> formatValue)
{
return formatValue == null
? value.ToString("0.###", CultureInfo.InvariantCulture)
: formatValue((float)value);
}
private static string FormatTemperature(
double value,
Func<float, string> formatTemperature)
{
return formatTemperature == null
? value.ToString("0.###", CultureInfo.InvariantCulture)
: formatTemperature((float)value);
}
private static string FormatTypeName(string typeName)
{
if (string.IsNullOrEmpty(typeName))
return "";
return SplitCamelCase(typeName.StartsWith("I", StringComparison.Ordinal) && typeName.Length > 1
? typeName.Substring(1)
: typeName);
}
private static string SplitCamelCase(string value)
{
if (string.IsNullOrEmpty(value))
return "";
return string.Concat(value.Select((character, index) =>
index > 0 && char.IsUpper(character) ? " " + character : character.ToString()));
}
private static string SafeId(string value)
{
if (string.IsNullOrWhiteSpace(value))
@ -256,6 +763,24 @@ namespace GameCult.Aetheria.State.Verse
Close = 1
}
internal enum AetheriaRuntimeCargoItemStatModifierType
{
Constant = 0,
Multiplier = 1
}
internal readonly struct AetheriaRuntimeCargoItemStatReference
{
public AetheriaRuntimeCargoItemStatReference(string target, string stat)
{
Target = target ?? "";
Stat = stat ?? "";
}
public string Target { get; }
public string Stat { get; }
}
public readonly struct AetheriaRuntimeCargoItemDetailsCommand
{
public AetheriaRuntimeCargoItemDetailsCommand(AetheriaRuntimeCargoItemDetailsCommandKind kind)