Aetheria/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonTradeItemQueries.cs
2026-07-01 14:33:46 +01:00

279 lines
9.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using MessagePack;
#nullable enable
namespace GameCult.Aetheria.State.Verse
{
[MessagePackObject(true)]
public readonly struct AetheriaRuntimeExponentialLerp
{
public AetheriaRuntimeExponentialLerp(double exponent, double minimum, double maximum)
{
Exponent = exponent;
Minimum = minimum;
Maximum = maximum;
}
public double Exponent { get; }
public double Minimum { get; }
public double Maximum { get; }
public double Evaluate(double value)
{
var t = value < 0 ? 0 : value > 1 ? 1 : value;
return Minimum + Math.Pow(t, Exponent) * (Maximum - Minimum);
}
}
[MessagePackObject(true)]
public readonly struct AetheriaRuntimeItemRarityTier
{
public AetheriaRuntimeItemRarityTier(string name, double quality, double red, double green, double blue)
{
Name = name ?? "";
Quality = quality;
Red = red;
Green = green;
Blue = blue;
}
public string Name { get; }
public double Quality { get; }
public double Red { get; }
public double Green { get; }
public double Blue { get; }
}
[MessagePackObject(true)]
public sealed class AetheriaRuntimeTradeValueSettings
{
public static AetheriaRuntimeTradeValueSettings Default { get; } = new AetheriaRuntimeTradeValueSettings(
new AetheriaRuntimeExponentialLerp(3, 0.25, 8),
new[]
{
new AetheriaRuntimeItemRarityTier("Common", 0.3, 0.75, 0.75, 0.75),
new AetheriaRuntimeItemRarityTier("Uncommon", 0.45, 0, 1, 0.5),
new AetheriaRuntimeItemRarityTier("Rare", 0.6, 0, 0.6, 1),
new AetheriaRuntimeItemRarityTier("Epic", 0.75, 0.75, 0, 1),
new AetheriaRuntimeItemRarityTier("Legendary", 0.9, 1, 0.4, 0),
});
public AetheriaRuntimeTradeValueSettings(
AetheriaRuntimeExponentialLerp qualityPriceModifier,
IReadOnlyList<AetheriaRuntimeItemRarityTier> tiers)
{
QualityPriceModifier = qualityPriceModifier;
Tiers = tiers ?? Array.Empty<AetheriaRuntimeItemRarityTier>();
}
public AetheriaRuntimeExponentialLerp QualityPriceModifier { get; set; }
public IReadOnlyList<AetheriaRuntimeItemRarityTier> Tiers { get; set; } =
Array.Empty<AetheriaRuntimeItemRarityTier>();
}
public readonly struct AetheriaRuntimeTradeItemValue
{
public AetheriaRuntimeTradeItemValue(
int price,
string tierName,
string tierColorHex,
int upgrades)
{
Price = price;
TierName = tierName ?? "";
TierColorHex = tierColorHex ?? "";
Upgrades = upgrades;
}
public int Price { get; }
public string TierName { get; }
public string TierColorHex { get; }
public int Upgrades { get; }
public bool HasTier => !string.IsNullOrWhiteSpace(TierName);
}
public static class AetheriaRuntimeDaemonTradeItemQueries
{
public static AetheriaRuntimeTradeItemValue TradeItemValue(
AetheriaRuntimeCatalogItem? typedItem,
AetheriaRuntimeLoadoutItemCommit? item,
AetheriaRuntimeTradeValueSettings? settings)
{
if (typedItem == null)
return default;
var quality = item?.Quality;
var price = quality.HasValue && settings != null
? (int)(settings.QualityPriceModifier.Evaluate(quality.Value) * typedItem.Price)
: typedItem.Price;
var tier = quality.HasValue && settings != null
? SelectTier(settings.Tiers, quality.Value)
: null;
var upgrades = tier.HasValue && quality.HasValue
? Upgrades(tier.Value, quality.Value)
: 0;
return new AetheriaRuntimeTradeItemValue(
price,
tier?.Name ?? "",
tier.HasValue ? ToHex(tier.Value) : "",
upgrades);
}
public static AetheriaRuntimeLoadoutItemCommit CraftedItemCommit(
string itemKey,
double quality,
double durability = 1)
{
return AetheriaRuntimeDaemonItemStatQueries.ItemCommit(
itemKey,
quality,
durability);
}
public static bool TryPriceLoadoutTemplate(
AetheriaRuntimeLoadoutTemplateSnapshot? template,
AetheriaRuntimeCatalogSnapshot? catalog,
AetheriaRuntimeTradeValueSettings? settings,
out int price)
{
price = 0;
return TryPriceEntityLoadout(template?.RootEntity, catalog, settings, out price);
}
private static bool TryPriceEntityLoadout(
AetheriaRuntimeEntityLoadoutSnapshot? entity,
AetheriaRuntimeCatalogSnapshot? catalog,
AetheriaRuntimeTradeValueSettings? settings,
out int price)
{
price = 0;
if (entity == null || catalog == null)
return false;
if (!TryAddLoadoutItemPrice(entity.Hull, catalog, settings, ref price))
return false;
if (!TryAddSlotPrices(entity.Equipment, catalog, settings, ref price) ||
!TryAddSlotPrices(entity.CargoBays, catalog, settings, ref price) ||
!TryAddSlotPrices(entity.DockingBays, catalog, settings, ref price) ||
!TryAddCargoPrices(entity.CargoContents, catalog, settings, ref price) ||
!TryAddCargoPrices(entity.DockingBayContents, catalog, settings, ref price))
{
return false;
}
foreach (var child in entity.Children ?? Array.Empty<AetheriaRuntimeEntityLoadoutSnapshot>())
{
if (!TryPriceEntityLoadout(child, catalog, settings, out var childPrice))
return false;
price += childPrice;
}
return true;
}
private static bool TryAddSlotPrices(
IReadOnlyList<AetheriaRuntimeLoadoutItemSlotSnapshot>? slots,
AetheriaRuntimeCatalogSnapshot catalog,
AetheriaRuntimeTradeValueSettings? settings,
ref int price)
{
foreach (var slot in slots ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotSnapshot>())
{
if (!TryAddLoadoutItemPrice(slot?.Item, catalog, settings, ref price))
return false;
}
return true;
}
private static bool TryAddCargoPrices(
IReadOnlyList<AetheriaRuntimeCargoBayLoadoutSnapshot>? bays,
AetheriaRuntimeCatalogSnapshot catalog,
AetheriaRuntimeTradeValueSettings? settings,
ref int price)
{
foreach (var bay in bays ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutSnapshot>())
{
if (!TryAddSlotPrices(bay?.Items, catalog, settings, ref price))
return false;
}
return true;
}
private static bool TryAddLoadoutItemPrice(
AetheriaRuntimeLoadoutItemSnapshot? item,
AetheriaRuntimeCatalogSnapshot catalog,
AetheriaRuntimeTradeValueSettings? settings,
ref int price)
{
if (item == null || string.IsNullOrWhiteSpace(item.ItemKey))
return false;
var typedItem = catalog.FindItem(item.ItemKey);
if (typedItem == null)
return false;
if (typedItem.Stackable)
{
price += typedItem.Price * Math.Max(1, item.Quantity);
return true;
}
price += TradeItemValue(
typedItem,
CraftedItemCommit(item.ItemKey, item.Quality, item.Durability),
settings).Price;
return true;
}
private static AetheriaRuntimeItemRarityTier? SelectTier(
IReadOnlyList<AetheriaRuntimeItemRarityTier> tiers,
double quality)
{
if (tiers == null || tiers.Count == 0)
return null;
var tier = tiers[0];
foreach (var candidate in tiers)
{
if (quality + .001 > candidate.Quality)
tier = candidate;
}
return tier;
}
private static int Upgrades(AetheriaRuntimeItemRarityTier tier, double quality)
{
return (int)((quality - tier.Quality) / .0499);
}
private static string ToHex(AetheriaRuntimeItemRarityTier tier)
{
return $"{ToByte(tier.Red):X2}{ToByte(tier.Green):X2}{ToByte(tier.Blue):X2}";
}
private static int ToByte(double value)
{
var scaled = Clamp01(value) * 255;
return int.Parse(
Math.Round(scaled).ToString(CultureInfo.InvariantCulture),
CultureInfo.InvariantCulture);
}
private static double Clamp01(double value)
{
if (value < 0)
return 0;
return value > 1 ? 1 : value;
}
}
}