Aetheria/Assets/Scripts/UI/Menu/TradeMenu.cs

1185 lines
45 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.EveRuntime;
using GameCult.Aetheria.State.Verse;
using GameCult.Eve.Surface;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.UIElements;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class TradeMenu : MonoBehaviour
{
public ActionGameManager GameManager;
public ConfirmationDialog Dialog;
public UnityEngine.UI.Button NewFilterButton;
public Prototype FilterPrototype;
public SizeFilter MinimumSizeFilter;
public SizeFilter MaximumSizeFilter;
public Spreadsheet Spreadsheet;
public TextMeshProUGUI TargetCargoLabel;
public UnityEngine.UI.Button FoldoutButton;
public TextMeshProUGUI CreditsLabel;
private EquippedCargoBay _targetCargo;
private (ItemFilter filter, HardpointType type) _hardpointFilter;
private (ItemFilter filter, SimpleCommodityCategory type) _commodityFilter;
private (ItemFilter filter, CompoundCommodityCategory type) _compoundCommodityFilter;
private List<BehaviorFilter> _behaviorFilters = new List<BehaviorFilter>();
private UIDocument _cargoSelectorSurfaceDocument;
private UIDocument _filterSurfaceDocument;
private UIDocument _rowActionSurfaceDocument;
private UIDocument _tradeItemSurfaceDocument;
private readonly AetheriaEveUnitySurfaceChrome _cargoSelectorSurfaceChrome = PanelChrome(360f, 420f, Align.FlexEnd);
private readonly AetheriaEveUnitySurfaceChrome _filterSurfaceChrome = PanelChrome(420f, 520f, Align.FlexStart);
private readonly AetheriaEveUnitySurfaceChrome _rowActionSurfaceChrome = PanelChrome(320f, 360f, Align.FlexStart);
private readonly AetheriaEveUnitySurfaceChrome _tradeItemSurfaceChrome = PanelChrome(420f, 520f, Align.FlexStart);
private AetheriaRuntimeTradeCargoSelectorSurfaceProjection _cargoSelectorSurfaceProjection;
private AetheriaRuntimeTradeFilterSurfaceProjection _filterSurfaceProjection;
private Action[] _rowActionCallbacks = Array.Empty<Action>();
private AetheriaRuntimeTradeRowActionSurfaceProjection _rowActionSurfaceProjection;
public EquippedCargoBay Inventory { get; set; }
private void OnEnable()
{
if (!GameManager.TryGetObservedDockingBay(out var dockingBay)) return;
_targetCargo = dockingBay;
TargetCargoLabel.text = "Docking Bay";
HideCargoSelectorSurface();
HideFilterSurface();
HideRowActionSurface();
HideTradeItemDetailsSurface();
UpdateCreditsLabel();
MinimumSizeFilter.Width.onEndEdit.RemoveAllListeners();
MinimumSizeFilter.Width.onEndEdit.AddListener(_ => Populate());
MinimumSizeFilter.Height.onEndEdit.RemoveAllListeners();
MinimumSizeFilter.Height.onEndEdit.AddListener(_ => Populate());
MaximumSizeFilter.Width.onEndEdit.RemoveAllListeners();
MaximumSizeFilter.Width.onEndEdit.AddListener(_ => Populate());
MaximumSizeFilter.Height.onEndEdit.RemoveAllListeners();
MaximumSizeFilter.Height.onEndEdit.AddListener(_ => Populate());
NewFilterButton.onClick.RemoveAllListeners();
NewFilterButton.onClick.AddListener(() =>
{
RenderFilterSurface();
});
Populate();
}
void Populate()
{
var columns = new List<(string name, int size, Func<TradeRow, Func<string>> output, Func<TradeRow, IComparable> sortKey)>();
columns.Add(("Name", 3,
x => () => x.Item is CraftedItemInstance && !string.IsNullOrWhiteSpace(x.TierColorHex) ?
$"<color=#{x.TierColorHex}>{x.Name}" :
x.Name,
x => x.Name));
if(_hardpointFilter.filter==null)
columns.Add(("Type", 2,
x => () =>
{
if (x.TryGetTypedSimpleCommodityCategory(out _))
return x.TypedItem.SimpleCommodityCategory;
if (x.TryGetTypedCompoundCommodityCategory(out _))
return x.TypedItem.CompoundCommodityCategory;
if (x.TryGetTypedHardpoint(out var hardpointType)) return Enum.GetName(typeof(HardpointType), hardpointType);
return "None";
},
x =>
{
if (x.TryGetTypedSimpleCommodityCategory(out var simpleCategory))
return (int)simpleCategory;
var offset = Enum.GetValues(typeof(SimpleCommodityCategory)).Length;
if(x.TryGetTypedCompoundCommodityCategory(out var compoundCategory))
return (int)compoundCategory + offset;
offset += Enum.GetValues(typeof(CompoundCommodityCategory)).Length;
if (x.TryGetTypedHardpoint(out var hardpointType)) return (int) hardpointType + offset;
return 0;
}));
columns.Add(("Mass", 1,
x => () => ActionGameManager.RuntimePlayerSettings.Format(x.Mass),
x => x.Mass));
columns.Add(("Price", 1,
x => () => x.Price.ToString("N0"),
x => x.Price));
columns.Add(("Size", 1,
x => () => $"{x.ShapeWidth}x{x.ShapeHeight}",
x => x.ShapeWidth * x.ShapeHeight));
var items = Inventory.Cargo.Keys
.Where(PassesTypedTradeFilters)
.Select(item => new TradeRow(item, FindTypedTradeItem(item), ProjectTradeItem(item)));
if (MinimumSizeFilter.gameObject.activeSelf)
items = items.Where(i =>
!(MinimumSizeFilter.Width.text.Length > 0 && i.ShapeWidth < int.Parse(MinimumSizeFilter.Width.text) ||
MinimumSizeFilter.Height.text.Length > 0 && i.ShapeHeight < int.Parse(MinimumSizeFilter.Height.text)));
if (MaximumSizeFilter.gameObject.activeSelf)
items = items.Where(i =>
!(MaximumSizeFilter.Width.text.Length > 0 && i.ShapeWidth > int.Parse(MaximumSizeFilter.Width.text) ||
MaximumSizeFilter.Height.text.Length > 0 && i.ShapeHeight > int.Parse(MaximumSizeFilter.Height.text)));
if(_commodityFilter.filter != null)
items = items.Where(i => i.TryGetTypedSimpleCommodityCategory(out var category) && category == _commodityFilter.type);
if(_compoundCommodityFilter.filter != null)
items = items.Where(i => i.TryGetTypedCompoundCommodityCategory(out var category) && category == _compoundCommodityFilter.type);
if (_hardpointFilter.filter != null)
items = items.Where(i => i.TryGetTypedHardpoint(out var hardpointType) && hardpointType == _hardpointFilter.type);
foreach (var behaviorFilter in _behaviorFilters)
{
items = items.Where(i => HasTypedBehavior(i.TypedItem, behaviorFilter));
foreach (var field in behaviorFilter.Metadata.DisplayFields)
{
if (field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.Number)
columns.Add((field.Name, 1, x =>
{
var value = GetTypedBehaviorNumber(x, behaviorFilter, field);
return () => ActionGameManager.RuntimePlayerSettings.Format((float)value);
}, x =>
{
return (float)GetTypedBehaviorNumber(x, behaviorFilter, field);
}));
else if (field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.Temperature)
columns.Add((field.Name, 1, x =>
{
var value = GetTypedBehaviorNumber(x, behaviorFilter, field);
return () => ActionGameManager.RuntimePlayerSettings.FormatTemperature((float)value);
}, x =>
{
return (float)GetTypedBehaviorNumber(x, behaviorFilter, field);
}));
else if (field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.Integer)
columns.Add((field.Name, 1, x =>
{
var value = GetTypedBehaviorNumber(x, behaviorFilter, field);
return () => ((int)value).ToString();
}, x =>
{
return (int)GetTypedBehaviorNumber(x, behaviorFilter, field);
}));
else if (field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat)
{
columns.Add((field.Name, 1, x =>
{
var value = GetTypedBehaviorNumber(x, behaviorFilter, field);
return () => ActionGameManager.RuntimePlayerSettings.Format((float)value);
}, x =>
{
return (float)GetTypedBehaviorNumber(x, behaviorFilter, field);
}));
}
}
}
columns.Add(("Owned", 1,
x => () =>
{
if (x.IsHull)
return CountAvailablePlayerShips(x.ItemKey).ToString();
if(x.Item is SimpleCommodity)
return (_targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Cast<SimpleCommodity>().Sum(s=>s.Quantity) : 0).ToString();
return (_targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Count : 0).ToString();
},
x =>
{
if (x.IsHull)
return CountAvailablePlayerShips(x.ItemKey);
if(x.Item is SimpleCommodity)
return _targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Cast<SimpleCommodity>().Sum(s=>s.Quantity) : 0;
return _targetCargo.ItemsOfType.ContainsKey(x.ItemKey) ? _targetCargo.ItemsOfType[x.ItemKey].Count : 0;
}));
Spreadsheet.ShowData(
columns.Select(x => x.name).ToArray(),
columns.Select(x => x.size).ToArray(),
items.Select(i => new SpreadsheetEntryRow
{
Columns = columns.Select(x => new SpreadsheetEntryColumn
{
Output = x.output(i),
SortKey = x.sortKey(i)
}).ToArray(),
OnClick = () => RenderTradeItemDetailsSurface(i.TypedItem),
OnDoubleClick = () =>
{
switch (i.Item)
{
case CraftedItemInstance c:
Buy(c);
break;
case SimpleCommodity s:
Buy(s, 1);
break;
}
Populate();
},
OnRightClick = () =>
{
if (i.Item is SimpleCommodity s)
{
RenderRowActionSurface(
$"Buying {i.Name}",
("Buy Quantity", () => ShowBuyQuantityDialog(i.Name, i.Price, s)));
}
}
}));
}
private bool PassesTypedTradeFilters(ItemInstance item)
{
var typedItem = FindTypedTradeItem(item);
if (typedItem == null)
{
return true;
}
if (MinimumSizeFilter.gameObject.activeSelf &&
(MinimumSizeFilter.Width.text.Length > 0 && typedItem.ShapeWidth < int.Parse(MinimumSizeFilter.Width.text) ||
MinimumSizeFilter.Height.text.Length > 0 && typedItem.ShapeHeight < int.Parse(MinimumSizeFilter.Height.text)))
{
return false;
}
if (MaximumSizeFilter.gameObject.activeSelf &&
(MaximumSizeFilter.Width.text.Length > 0 && typedItem.ShapeWidth > int.Parse(MaximumSizeFilter.Width.text) ||
MaximumSizeFilter.Height.text.Length > 0 && typedItem.ShapeHeight > int.Parse(MaximumSizeFilter.Height.text)))
{
return false;
}
if (_hardpointFilter.filter != null &&
!string.Equals(typedItem.HardpointType, _hardpointFilter.type.ToString(), StringComparison.Ordinal))
{
return false;
}
return _behaviorFilters.All(filter => HasTypedBehavior(typedItem, filter));
}
private static AetheriaRuntimeCatalogItem FindTypedTradeItem(ItemInstance item)
{
return ActionGameManager.RuntimeCatalog?.FindItem(item?.ItemKey ?? "");
}
private AetheriaRuntimeTradeItemProjection ProjectTradeItem(ItemInstance item)
{
var typedItem = FindTypedTradeItem(item);
return AetheriaRuntimeDaemonTradeItemQueries.ProjectTradeItem(
typedItem,
ProjectTradeItemCommit(item),
GameManager.ObservedTradeValueSettings());
}
private static AetheriaRuntimeLoadoutItemCommit? ProjectTradeItemCommit(ItemInstance item)
{
if (item is not CraftedItemInstance crafted)
return null;
var durability = item is EquippableItem equippable ? equippable.Durability : 1f;
return AetheriaRuntimeDaemonTradeItemQueries.CraftedItemCommit(
item.ItemKey,
crafted.Quality,
durability);
}
private static double GetTypedBehaviorNumber(TradeRow row, BehaviorFilter behaviorFilter, AetheriaRuntimeBehaviorFieldMetadata field)
{
var payload = FindTypedBehaviorPayload(row.TypedItem, behaviorFilter);
var payloadField = payload?.Fields.FirstOrDefault(candidate => candidate.Key == field.Key);
if (payloadField == null)
{
return 0;
}
if (field.ValueKind == AetheriaRuntimeBehaviorFieldValueKind.PerformanceStat)
{
return payloadField.Value.Children.Count > 1
? payloadField.Value.Children[1].NumberValue
: 0;
}
return payloadField.Value.NumberValue;
}
private static AetheriaRuntimeBehaviorPayload FindTypedBehaviorPayload(AetheriaRuntimeCatalogItem typedItem, BehaviorFilter behaviorFilter)
{
if (typedItem == null)
{
return null;
}
return typedItem.BehaviorPayloads.FirstOrDefault(payload => TypedBehaviorMatches(payload, behaviorFilter));
}
private static bool HasTypedBehavior(AetheriaRuntimeCatalogItem typedItem, BehaviorFilter behaviorFilter)
{
return typedItem?.BehaviorPayloads.Any(payload => TypedBehaviorMatches(payload, behaviorFilter)) ?? false;
}
private static bool TypedBehaviorMatches(AetheriaRuntimeBehaviorPayload payload, BehaviorFilter behaviorFilter)
{
return AetheriaRuntimeBehaviorMetadataCatalog.IsKindOrDescendant(payload.Kind, behaviorFilter.Kind);
}
private sealed class BehaviorFilter
{
public BehaviorFilter(ItemFilter filter, AetheriaRuntimeBehaviorMetadata metadata)
{
Filter = filter ?? throw new ArgumentNullException(nameof(filter));
Metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
}
public ItemFilter Filter { get; }
public AetheriaRuntimeBehaviorMetadata Metadata { get; }
public string Kind => Metadata.Kind;
}
private sealed class TradeRow
{
public TradeRow(
ItemInstance item,
AetheriaRuntimeCatalogItem typedItem,
AetheriaRuntimeTradeItemProjection tradeProjection)
{
Item = item;
TypedItem = typedItem;
TradeProjection = tradeProjection;
}
public ItemInstance Item { get; }
public AetheriaRuntimeCatalogItem TypedItem { get; }
public AetheriaRuntimeTradeItemProjection TradeProjection { get; }
public string ItemKey => Item?.ItemKey ?? "";
public string Name => !string.IsNullOrWhiteSpace(TypedItem?.Name) ? TypedItem.Name : "Unknown Item";
public float Mass => TypedItem != null ? (float)TypedItem.Mass : 0f;
public int Price => TradeProjection.Price;
public string TierColorHex => TradeProjection.TierColorHex;
public int ShapeWidth => TypedItem != null && TypedItem.ShapeWidth > 0 ? TypedItem.ShapeWidth : 0;
public int ShapeHeight => TypedItem != null && TypedItem.ShapeHeight > 0 ? TypedItem.ShapeHeight : 0;
public bool IsHull => !string.IsNullOrWhiteSpace(TypedItem?.HullType);
public bool TryGetTypedSimpleCommodityCategory(out SimpleCommodityCategory category)
{
category = SimpleCommodityCategory.Minerals;
return !string.IsNullOrWhiteSpace(TypedItem?.SimpleCommodityCategory) &&
Enum.TryParse(TypedItem.SimpleCommodityCategory, true, out category);
}
public bool TryGetTypedCompoundCommodityCategory(out CompoundCommodityCategory category)
{
category = CompoundCommodityCategory.Wearables;
return !string.IsNullOrWhiteSpace(TypedItem?.CompoundCommodityCategory) &&
Enum.TryParse(TypedItem.CompoundCommodityCategory, true, out category);
}
public bool TryGetTypedHardpoint(out HardpointType hardpointType)
{
hardpointType = HardpointType.Hull;
return !string.IsNullOrWhiteSpace(TypedItem?.HardpointType) &&
Enum.TryParse(TypedItem.HardpointType, true, out hardpointType);
}
}
private void UpdateCreditsLabel()
{
if (CreditsLabel != null)
{
CreditsLabel.text = GameManager.Credits.ToString("N0");
}
}
private void Buy(CraftedItemInstance item)
{
var typedItem = FindTypedTradeItem(item);
if (typedItem == null)
{
ShowUnableToBuy("Missing typed trade row!");
return;
}
var price = GetTypedTradePrice(item, typedItem);
var isShipHull = !string.IsNullOrWhiteSpace(typedItem?.HullType);
if (isShipHull &&
!string.Equals(typedItem.HullType, nameof(HullType.Ship), StringComparison.Ordinal))
{
ShowUnableToBuy("Unsupported hull purchase!");
return;
}
GameManager.RequestTradePurchase(Inventory, _targetCargo, item, price, isShipHull);
}
private void Buy(SimpleCommodity simpleCommodity, int quantity)
{
var typedItem = FindTypedTradeItem(simpleCommodity);
if (typedItem == null)
{
ShowUnableToBuy("Missing typed trade row!");
return;
}
var price = typedItem.Price;
GameManager.RequestTradePurchase(Inventory, _targetCargo, simpleCommodity, quantity, price);
}
private int GetTypedTradePrice(CraftedItemInstance item, AetheriaRuntimeCatalogItem typedItem)
{
return AetheriaRuntimeDaemonTradeItemQueries.ProjectTradeItem(
typedItem,
ProjectTradeItemCommit(item),
GameManager.ObservedTradeValueSettings()).Price;
}
private void ShowUnableToBuy(string reason)
{
Dialog.Clear();
Dialog.Title.text = $"Unable to buy: {reason}";
Dialog.Show();
Dialog.MoveToCursor();
}
private void ShowBuyQuantityDialog(string itemName, int price, SimpleCommodity simpleCommodity)
{
int quantity = 1;
Dialog.Clear();
Dialog.Title.text = $"Buying {itemName}";
Dialog.AddField(
"Quantity",
() => quantity,
q => quantity = q);
Dialog.Show(() =>
{
Buy(simpleCommodity, quantity);
Populate();
});
Dialog.MoveToCursor();
}
private void RenderTradeItemDetailsSurface(AetheriaRuntimeCatalogItem item)
{
if (item == null)
return;
_tradeItemSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
transform,
_tradeItemSurfaceDocument,
"Aetheria Trade Item Details Surface",
AetheriaRuntimeTradeItemDetailsSurfaceBuilder.Build(ProjectTradeItemDetailsSurfaceState(item)),
HandleTradeItemDetailsSurfaceCommand,
_tradeItemSurfaceChrome,
sortingOrder: 1003);
}
private void HandleTradeItemDetailsSurfaceCommand(EveSurfaceCommandRequest request)
{
if (!AetheriaRuntimeTradeItemDetailsSurfaceCommands.TryRead(request, out var command))
{
Debug.LogWarning($"Unknown trade item details command: {request?.Command}");
return;
}
if (command.Kind == AetheriaRuntimeTradeItemDetailsCommandKind.Close)
{
HideTradeItemDetailsSurface();
return;
}
Debug.LogWarning($"Unknown trade item details command: {request?.Command}");
}
private void HideTradeItemDetailsSurface()
{
if (_tradeItemSurfaceDocument == null)
return;
AetheriaEveUnitySurfaceHost.Hide(_tradeItemSurfaceDocument);
}
private AetheriaRuntimeTradeItemDetailsSurfaceState ProjectTradeItemDetailsSurfaceState(AetheriaRuntimeCatalogItem item)
{
var durability = "";
var thermalRange = "";
var behaviorSections = Array.Empty<AetheriaRuntimeTradeItemSection>();
if (!string.IsNullOrWhiteSpace(item.HardpointType))
{
durability = ActionGameManager.RuntimePlayerSettings.Format((float)item.Durability);
thermalRange = FormatTemperatureRange(item);
behaviorSections = ProjectTradeItemBehaviorSections(item).ToArray();
}
return new AetheriaRuntimeTradeItemDetailsSurfaceState(
item.Name,
item.Description ?? "",
ActionGameManager.RuntimeCatalog?.GetManufacturer(item)?.Name ?? "GameCult",
ActionGameManager.RuntimePlayerSettings.Format((float)item.Mass),
item.Price,
durability,
thermalRange,
behaviorSections,
DateTime.UtcNow.ToString("O"));
}
private IEnumerable<AetheriaRuntimeTradeItemSection> ProjectTradeItemBehaviorSections(AetheriaRuntimeCatalogItem item)
{
foreach (var behavior in item.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 AetheriaRuntimeTradeItemSection(
$"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.stat_modifier",
"Stat Modifier",
new[]
{
new AetheriaRuntimeTradeItemMetric(
$"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.target",
$"{statReference.Target.SplitCamelCase()}:{statReference.Stat.SplitCamelCase()}",
$"{(modifierType == StatModifierType.Constant ? "+" : "x")}{ActionGameManager.RuntimePlayerSettings.Format(modifier.Min)}")
});
continue;
}
var metadata = AetheriaRuntimeBehaviorMetadataCatalog.Get(behavior.Kind);
if (metadata == null)
continue;
var fields = metadata.DisplayFields
.Select(field => ProjectTradeItemBehaviorMetric(behavior, field))
.Where(metric => metric != null)
.ToArray();
if (fields.Length == 0)
continue;
yield return new AetheriaRuntimeTradeItemSection(
$"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}",
behavior.Kind.FormatTypeName(),
fields);
}
}
private AetheriaRuntimeTradeItemMetric ProjectTradeItemBehaviorMetric(
AetheriaRuntimeBehaviorPayload behavior,
AetheriaRuntimeBehaviorFieldMetadata field)
{
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 = ActionGameManager.RuntimePlayerSettings.Format(AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(payloadField.Value).Min);
break;
default:
return null;
}
return new AetheriaRuntimeTradeItemMetric(
$"{AetheriaRuntimeTradeItemDetailsSurfaceBuilder.SurfaceId}.behavior.{behavior.Kind}.{field.Key}",
field.Name.SplitCamelCase(),
value);
}
private static string FormatTemperatureRange(AetheriaRuntimeCatalogItem item)
{
if (item.MaximumTemperature > item.MinimumTemperature)
{
return
$"{ActionGameManager.RuntimePlayerSettings.FormatTemperature((float)item.MinimumTemperature)} to " +
$"{ActionGameManager.RuntimePlayerSettings.FormatTemperature((float)item.MaximumTemperature)}";
}
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);
}
void Start()
{
FoldoutButton.onClick.AddListener(() =>
{
RenderCargoSelectorSurface();
});
}
void Update()
{
}
private void RenderFilterSurface()
{
_filterSurfaceProjection = ProjectTradeFilterSurface();
_filterSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
transform,
_filterSurfaceDocument,
"Aetheria Trade Filter Surface",
AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildFilter(_filterSurfaceProjection.State),
HandleFilterSurfaceCommand,
_filterSurfaceChrome,
sortingOrder: 1001);
}
private void HandleFilterSurfaceCommand(EveSurfaceCommandRequest request)
{
if (!AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadFilter(request, out var command))
{
Debug.LogWarning($"Unknown trade filter command: {request?.Command}");
return;
}
if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Close)
{
HideFilterSurface();
return;
}
if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Select &&
_filterSurfaceProjection?.TryResolve(command.Command, out var selection) == true)
{
ExecuteTradeFilterSelection(selection);
HideFilterSurface();
return;
}
Debug.LogWarning($"Unknown trade filter command: {request?.Command}");
}
private void HideFilterSurface()
{
if (_filterSurfaceDocument == null)
return;
AetheriaEveUnitySurfaceHost.Hide(_filterSurfaceDocument);
}
private AetheriaRuntimeTradeFilterSurfaceProjection ProjectTradeFilterSurface()
{
var options = new List<AetheriaRuntimeTradeFilterOption>();
options.AddRange(((HardpointType[])Enum.GetValues(typeof(HardpointType)))
.Where(type => _hardpointFilter.filter == null || type != _hardpointFilter.type)
.Select(type => new AetheriaRuntimeTradeFilterOption(
AetheriaRuntimeTradeFilterSelectionKind.Hardpoint,
type.ToString(),
type.ToString().FormatTypeName())));
options.AddRange(((SimpleCommodityCategory[])Enum.GetValues(typeof(SimpleCommodityCategory)))
.Where(type => _commodityFilter.filter == null || type != _commodityFilter.type)
.Select(type => new AetheriaRuntimeTradeFilterOption(
AetheriaRuntimeTradeFilterSelectionKind.SimpleCommodity,
type.ToString(),
type.ToString().FormatTypeName())));
options.AddRange(((CompoundCommodityCategory[])Enum.GetValues(typeof(CompoundCommodityCategory)))
.Where(type => _compoundCommodityFilter.filter == null || type != _compoundCommodityFilter.type)
.Select(type => new AetheriaRuntimeTradeFilterOption(
AetheriaRuntimeTradeFilterSelectionKind.CompoundCommodity,
type.ToString(),
type.ToString().FormatTypeName())));
options.AddRange(AetheriaRuntimeBehaviorMetadataCatalog.All
.Where(option => _behaviorFilters.All(filter => filter.Kind != option.Kind))
.OrderBy(option => option.Kind, StringComparer.Ordinal)
.Select(option => new AetheriaRuntimeTradeFilterOption(
AetheriaRuntimeTradeFilterSelectionKind.Behavior,
option.Kind,
option.Kind.FormatTypeName())));
if (!MinimumSizeFilter.gameObject.activeSelf)
{
options.Add(new AetheriaRuntimeTradeFilterOption(
AetheriaRuntimeTradeFilterSelectionKind.MinimumSize,
"minimum",
"Minimum Size"));
}
if (!MaximumSizeFilter.gameObject.activeSelf)
{
options.Add(new AetheriaRuntimeTradeFilterOption(
AetheriaRuntimeTradeFilterSelectionKind.MaximumSize,
"maximum",
"Maximum Size"));
}
return AetheriaRuntimeTradeInteractionSurfaceBuilder.ProjectFilters(
BuildFilterSummary(),
options,
DateTime.UtcNow.ToString("O"));
}
private void ExecuteTradeFilterSelection(AetheriaRuntimeTradeFilterSelection selection)
{
switch (selection.Kind)
{
case AetheriaRuntimeTradeFilterSelectionKind.Hardpoint:
if (Enum.TryParse(selection.Token, out HardpointType hardpointType))
{
ApplyHardpointFilter(hardpointType);
}
return;
case AetheriaRuntimeTradeFilterSelectionKind.SimpleCommodity:
if (Enum.TryParse(selection.Token, out SimpleCommodityCategory simpleCategory))
{
ApplySimpleCommodityFilter(simpleCategory);
}
return;
case AetheriaRuntimeTradeFilterSelectionKind.CompoundCommodity:
if (Enum.TryParse(selection.Token, out CompoundCommodityCategory compoundCategory))
{
ApplyCompoundCommodityFilter(compoundCategory);
}
return;
case AetheriaRuntimeTradeFilterSelectionKind.Behavior:
var metadata = AetheriaRuntimeBehaviorMetadataCatalog.All
.FirstOrDefault(option => string.Equals(option.Kind, selection.Token, StringComparison.Ordinal));
if (metadata != null)
{
ApplyBehaviorFilter(metadata);
}
return;
case AetheriaRuntimeTradeFilterSelectionKind.MinimumSize:
EnableMinimumSizeFilter();
return;
case AetheriaRuntimeTradeFilterSelectionKind.MaximumSize:
EnableMaximumSizeFilter();
return;
}
}
private void RenderRowActionSurface(string title, params (string Label, Action Action)[] actions)
{
_rowActionCallbacks = actions
.Select(action => action.Action)
.ToArray();
_rowActionSurfaceProjection = AetheriaRuntimeTradeInteractionSurfaceBuilder.ProjectRowActions(
title,
actions.Select((action, index) => new AetheriaRuntimeTradeRowActionOption(index, action.Label)),
DateTime.UtcNow.ToString("O"));
_rowActionSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
transform,
_rowActionSurfaceDocument,
"Aetheria Trade Row Action Surface",
AetheriaRuntimeTradeInteractionSurfaceBuilder.BuildRowActions(_rowActionSurfaceProjection.State),
HandleRowActionSurfaceCommand,
_rowActionSurfaceChrome,
sortingOrder: 1002);
}
private void HandleRowActionSurfaceCommand(EveSurfaceCommandRequest request)
{
if (!AetheriaRuntimeTradeInteractionSurfaceCommands.TryReadRowAction(request, out var command))
{
Debug.LogWarning($"Unknown trade row action command: {request?.Command}");
return;
}
if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Close)
{
HideRowActionSurface();
return;
}
if (command.Kind == AetheriaRuntimeTradeInteractionCommandKind.Select &&
_rowActionSurfaceProjection?.TryResolve(command.Command, out var selection) == true &&
selection.Index >= 0 &&
selection.Index < _rowActionCallbacks.Length)
{
_rowActionCallbacks[selection.Index]?.Invoke();
HideRowActionSurface();
return;
}
Debug.LogWarning($"Unknown trade row action command: {request?.Command}");
}
private void HideRowActionSurface()
{
if (_rowActionSurfaceDocument == null)
return;
AetheriaEveUnitySurfaceHost.Hide(_rowActionSurfaceDocument);
}
private void ApplyHardpointFilter(HardpointType hardpointType)
{
EnsureHardpointFilter();
ClearCommodityFilters();
_hardpointFilter.filter.Label.text = $"Hardpoint: {Enum.GetName(typeof(HardpointType), hardpointType)}";
_hardpointFilter.type = hardpointType;
Populate();
}
private void ApplySimpleCommodityFilter(SimpleCommodityCategory category)
{
EnsureCommodityFilter();
ClearHardpointFilter();
ClearCompoundCommodityFilter();
_commodityFilter.filter.Label.text = $"Simple Commodity: {Enum.GetName(typeof(SimpleCommodityCategory), category)}";
_commodityFilter.type = category;
Populate();
}
private void ApplyCompoundCommodityFilter(CompoundCommodityCategory category)
{
EnsureCompoundCommodityFilter();
ClearHardpointFilter();
ClearCommodityFilter();
_compoundCommodityFilter.filter.Label.text = $"Compound Commodity: {Enum.GetName(typeof(CompoundCommodityCategory), category)}";
_compoundCommodityFilter.type = category;
Populate();
}
private void ApplyBehaviorFilter(AetheriaRuntimeBehaviorMetadata metadata)
{
var matchingType = _behaviorFilters.FirstOrDefault(filter =>
AetheriaRuntimeBehaviorMetadataCatalog.IsKindOrDescendant(filter.Kind, metadata.Kind) ||
AetheriaRuntimeBehaviorMetadataCatalog.IsKindOrDescendant(metadata.Kind, filter.Kind));
if (matchingType?.Filter != null)
{
matchingType.Filter.DisableButton.onClick.Invoke();
}
var filter = FilterPrototype.Instantiate<ItemFilter>();
filter.Label.text = metadata.Kind.FormatTypeName();
var behaviorFilter = new BehaviorFilter(filter, metadata);
filter.OnDisable += () =>
{
_behaviorFilters.Remove(behaviorFilter);
Populate();
};
_behaviorFilters.Add(behaviorFilter);
Populate();
}
private void EnableMinimumSizeFilter()
{
MinimumSizeFilter.gameObject.SetActive(true);
MinimumSizeFilter.OnDisable += () => Populate();
Populate();
}
private void EnableMaximumSizeFilter()
{
MaximumSizeFilter.gameObject.SetActive(true);
MaximumSizeFilter.OnDisable += () => Populate();
Populate();
}
private void EnsureHardpointFilter()
{
if (_hardpointFilter.filter != null)
return;
_hardpointFilter.filter = FilterPrototype.Instantiate<ItemFilter>();
_hardpointFilter.filter.OnDisable += () =>
{
_hardpointFilter.filter = null;
Populate();
};
}
private void EnsureCommodityFilter()
{
if (_commodityFilter.filter != null)
return;
_commodityFilter.filter = FilterPrototype.Instantiate<ItemFilter>();
_commodityFilter.filter.OnDisable += () =>
{
_commodityFilter.filter = null;
Populate();
};
}
private void EnsureCompoundCommodityFilter()
{
if (_compoundCommodityFilter.filter != null)
return;
_compoundCommodityFilter.filter = FilterPrototype.Instantiate<ItemFilter>();
_compoundCommodityFilter.filter.OnDisable += () =>
{
_compoundCommodityFilter.filter = null;
Populate();
};
}
private void ClearHardpointFilter()
{
if (_hardpointFilter.filter == null)
return;
_hardpointFilter.filter.GetComponent<Prototype>().ReturnToPool();
_hardpointFilter.filter = null;
}
private void ClearCommodityFilter()
{
if (_commodityFilter.filter == null)
return;
_commodityFilter.filter.GetComponent<Prototype>().ReturnToPool();
_commodityFilter.filter = null;
}
private void ClearCompoundCommodityFilter()
{
if (_compoundCommodityFilter.filter == null)
return;
_compoundCommodityFilter.filter.GetComponent<Prototype>().ReturnToPool();
_compoundCommodityFilter.filter = null;
}
private void ClearCommodityFilters()
{
ClearCommodityFilter();
ClearCompoundCommodityFilter();
}
private string BuildFilterSummary()
{
var activeFilters = new List<string>();
if (_hardpointFilter.filter != null)
{
activeFilters.Add($"Gear: {Enum.GetName(typeof(HardpointType), _hardpointFilter.type)}");
}
if (_commodityFilter.filter != null)
{
activeFilters.Add($"Simple: {Enum.GetName(typeof(SimpleCommodityCategory), _commodityFilter.type)}");
}
if (_compoundCommodityFilter.filter != null)
{
activeFilters.Add($"Compound: {Enum.GetName(typeof(CompoundCommodityCategory), _compoundCommodityFilter.type)}");
}
activeFilters.AddRange(_behaviorFilters.Select(filter => $"Behavior: {filter.Kind.FormatTypeName()}"));
if (MinimumSizeFilter.gameObject.activeSelf)
{
activeFilters.Add("Minimum size active");
}
if (MaximumSizeFilter.gameObject.activeSelf)
{
activeFilters.Add("Maximum size active");
}
return activeFilters.Count == 0
? "No active filters"
: string.Join(" | ", activeFilters);
}
private void RenderCargoSelectorSurface()
{
_cargoSelectorSurfaceProjection = ProjectTradeCargoSelectorSurface();
_cargoSelectorSurfaceDocument = AetheriaEveUnitySurfaceHost.RenderRuntime(
transform,
_cargoSelectorSurfaceDocument,
"Aetheria Trade Cargo Selector Surface",
AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Build(_cargoSelectorSurfaceProjection.State),
HandleCargoSelectorSurfaceCommand,
_cargoSelectorSurfaceChrome);
}
private void HandleCargoSelectorSurfaceCommand(EveSurfaceCommandRequest request)
{
if (!AetheriaRuntimeTradeCargoSelectorSurfaceCommands.TryRead(request, out var command))
{
Debug.LogWarning($"Unknown trade cargo selector command: {request?.Command}");
return;
}
if (command.Kind == AetheriaRuntimeTradeCargoSelectorCommandKind.Close)
{
HideCargoSelectorSurface();
return;
}
if (command.Kind == AetheriaRuntimeTradeCargoSelectorCommandKind.Select &&
_cargoSelectorSurfaceProjection?.TryResolve(command.Command, out var selection) == true)
{
ApplyCargoSelection(selection);
HideCargoSelectorSurface();
Populate();
return;
}
Debug.LogWarning($"Unknown trade cargo selector command: {request?.Command}");
}
private void HideCargoSelectorSurface()
{
if (_cargoSelectorSurfaceDocument == null)
return;
AetheriaEveUnitySurfaceHost.Hide(_cargoSelectorSurfaceDocument);
}
private static AetheriaEveUnitySurfaceChrome PanelChrome(float width, float maxWidth, Align alignItems)
{
return new AetheriaEveUnitySurfaceChrome
{
RootAlignItems = alignItems,
RootJustifyContent = Justify.FlexStart,
RootPaddingTop = 16f,
RootPaddingLeft = alignItems == Align.FlexStart ? 16f : 0f,
RootPaddingRight = alignItems == Align.FlexEnd ? 16f : 0f,
Width = width,
MinWidth = 0f,
MaxWidth = maxWidth,
PaddingLeft = 18f,
PaddingRight = 18f,
PaddingTop = 18f,
PaddingBottom = 18f
};
}
private AetheriaRuntimeTradeCargoSelectorSurfaceProjection ProjectTradeCargoSelectorSurface()
{
var targets = new List<AetheriaRuntimeTradeCargoProjectionOption>();
if (GameManager.TryGetObservedDockingBay(out var dockingBay))
{
targets.Add(new AetheriaRuntimeTradeCargoProjectionOption(
AetheriaRuntimeTradeCargoTargetKind.DockingBay,
"Docking Bay",
isCurrent: _targetCargo == dockingBay));
}
targets.AddRange(GameManager.ObservedAvailableEntities()
.OfType<Ship>()
.Where(ship => ship.IsPlayerShip)
.SelectMany((ship, shipIndex) => ship.CargoBays.Select((cargoBay, bayIndex) =>
new AetheriaRuntimeTradeCargoProjectionOption(
AetheriaRuntimeTradeCargoTargetKind.ShipBay,
$"{ship.Name} Bay {bayIndex + 1}",
shipIndex,
bayIndex,
_targetCargo == cargoBay))));
return AetheriaRuntimeTradeCargoSelectorSurfaceBuilder.Project(
TargetCargoLabel.text ?? "",
targets,
DateTime.UtcNow.ToString("O"));
}
private void ApplyCargoSelection(AetheriaRuntimeTradeCargoSelection selection)
{
switch (selection.Kind)
{
case AetheriaRuntimeTradeCargoTargetKind.DockingBay:
if (GameManager.TryGetObservedDockingBay(out var dockingBay))
{
_targetCargo = dockingBay;
TargetCargoLabel.text = selection.Label;
}
return;
case AetheriaRuntimeTradeCargoTargetKind.ShipBay:
var ships = GameManager.ObservedAvailableEntities()
.OfType<Ship>()
.Where(ship => ship.IsPlayerShip)
.ToArray();
if (selection.ShipIndex >= 0 &&
selection.ShipIndex < ships.Length &&
selection.BayIndex >= 0 &&
selection.BayIndex < ships[selection.ShipIndex].CargoBays.Count)
{
_targetCargo = ships[selection.ShipIndex].CargoBays[selection.BayIndex];
TargetCargoLabel.text = selection.Label;
}
return;
}
}
private int CountAvailablePlayerShips(string itemKey)
{
return GameManager.ObservedAvailableEntities()
.OfType<Ship>()
.Count(ship => ship.IsPlayerShip && ship.Hull?.ItemKey == itemKey);
}
private void OnDisable()
{
HideCargoSelectorSurface();
HideFilterSurface();
HideRowActionSurface();
HideTradeItemDetailsSurface();
}
private void OnDestroy()
{
if (_cargoSelectorSurfaceDocument != null)
{
AetheriaEveUnitySurfaceHost.DestroyDocument(_cargoSelectorSurfaceDocument);
_cargoSelectorSurfaceDocument = null;
}
if (_filterSurfaceDocument != null)
{
AetheriaEveUnitySurfaceHost.DestroyDocument(_filterSurfaceDocument);
_filterSurfaceDocument = null;
}
if (_rowActionSurfaceDocument != null)
{
AetheriaEveUnitySurfaceHost.DestroyDocument(_rowActionSurfaceDocument);
_rowActionSurfaceDocument = null;
}
if (_tradeItemSurfaceDocument != null)
{
AetheriaEveUnitySurfaceHost.DestroyDocument(_tradeItemSurfaceDocument);
_tradeItemSurfaceDocument = null;
}
}
}