2026-06-13 17:22:03 +01:00
/ * This Source Code Form is subject to the terms of the Mozilla Public
2020-12-22 03:13:43 +00:00
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at https : //mozilla.org/MPL/2.0/. */
using System ;
2020-03-22 19:05:26 +00:00
using System.Collections.Generic ;
using System.Linq ;
2026-06-14 04:35:51 +01:00
using GameCult.Aetheria.State.Unity ;
2026-06-21 09:37:54 +01:00
using Random = CultMath . Random ;
2020-05-10 00:16:33 +01:00
using JM.LinqFaster ;
2020-12-01 00:16:11 +00:00
using UniRx ;
2020-03-22 19:05:26 +00:00
2020-12-10 23:20:38 +00:00
public class ItemManager
2020-03-22 19:05:26 +00:00
{
2020-04-30 02:54:31 +01:00
public Random Random = new Random ( ( uint ) ( DateTime . Now . Ticks % uint . MaxValue ) ) ;
2020-12-20 03:04:52 +00:00
2026-06-16 13:11:38 +01:00
private Action < string > _logger ;
2026-06-16 19:14:35 +01:00
private readonly AetheriaRuntimeCatalogSnapshot _runtimeCatalog ;
2026-06-14 10:54:17 +01:00
2020-12-10 23:20:38 +00:00
public GameplaySettings GameplaySettings { get ; }
2020-05-10 00:16:33 +01:00
2026-06-16 19:14:35 +01:00
public ItemManager ( AetheriaRuntimeCatalogSnapshot runtimeCatalog , GameplaySettings settings , Action < string > logger )
2020-03-27 01:53:03 +00:00
{
2026-06-16 19:14:35 +01:00
_runtimeCatalog = runtimeCatalog ? ? throw new ArgumentNullException ( nameof ( runtimeCatalog ) ) ;
2020-12-20 03:04:52 +00:00
GameplaySettings = settings ;
2020-04-09 07:48:45 +01:00
_logger = logger ;
2020-04-16 03:08:18 +01:00
}
2026-06-14 04:35:51 +01:00
public AetheriaRuntimeCatalogItem GetRuntimeItem ( ItemInstance item )
{
2026-06-14 22:58:02 +01:00
return item = = null
? null
: ! string . IsNullOrWhiteSpace ( item . ItemKey )
2026-06-16 19:14:35 +01:00
? _runtimeCatalog . FindItem ( item . ItemKey )
2026-06-15 01:31:16 +01:00
: null ;
2026-06-14 04:35:51 +01:00
}
2026-06-14 10:29:17 +01:00
public Behavior [ ] CreateRuntimeBehaviors ( EquippedItem item )
{
2026-06-15 18:51:59 +01:00
return GetRuntimeItem ( item ? . EquippableItem ) ? . BehaviorPayloads
. Select ( payload = > CreateRuntimeBehavior ( payload , item ) )
. Where ( behavior = > behavior ! = null )
. ToArray ( ) ? ? Array . Empty < Behavior > ( ) ;
2026-06-14 10:29:17 +01:00
}
public Behavior [ ] CreateRuntimeBehaviors ( ConsumableItemEffect effect )
{
2026-06-15 18:51:59 +01:00
return GetRuntimeItem ( effect ? . Item ) ? . BehaviorPayloads
. Select ( payload = > CreateRuntimeBehavior ( payload , effect ) )
. Where ( behavior = > behavior ! = null )
. ToArray ( ) ? ? Array . Empty < Behavior > ( ) ;
2026-06-14 10:29:17 +01:00
}
2026-06-15 18:51:59 +01:00
private static Behavior CreateRuntimeBehavior ( AetheriaRuntimeBehaviorPayload payload , EquippedItem item )
2026-06-14 10:29:17 +01:00
{
2026-06-15 20:12:34 +01:00
return CreateDirectRuntimeBehavior ( payload , item ) ;
2026-06-14 10:54:17 +01:00
}
2026-06-15 18:51:59 +01:00
private static Behavior CreateRuntimeBehavior ( AetheriaRuntimeBehaviorPayload payload , ConsumableItemEffect effect )
2026-06-14 10:54:17 +01:00
{
2026-06-15 20:12:34 +01:00
return CreateDirectRuntimeBehavior ( payload , effect ) ;
2026-06-15 18:51:59 +01:00
}
private static Behavior CreateDirectRuntimeBehavior ( AetheriaRuntimeBehaviorPayload payload , EquippedItem item )
{
var definition = new RuntimeBehaviorDefinition ( payload ) ;
switch ( payload . Kind )
{
case "Cockpit" : return new Cockpit ( definition , item ) ;
2026-06-15 19:54:54 +01:00
case "AetherDrive" : return new AetherDrive ( definition , item ) ;
2026-06-15 20:12:34 +01:00
case "AutoWeapon" : return new AutoWeapon ( definition , item ) ;
2026-06-15 19:20:06 +01:00
case "Capacitor" : return new Capacitor ( definition , item ) ;
2026-06-15 20:12:34 +01:00
case "ChargedWeapon" : return new ChargedWeapon ( definition , item ) ;
2026-06-15 19:06:13 +01:00
case "Cooldown" : return new Cooldown ( definition , item ) ;
2026-06-15 20:12:34 +01:00
case "ConstantWeapon" : return new ConstantWeapon ( definition , item ) ;
2026-06-15 19:06:13 +01:00
case "EnergyDraw" : return new EnergyDraw ( definition , item ) ;
2026-06-15 20:12:34 +01:00
case "GuidedWeapon" : return new InstantWeapon ( definition , item ) ;
2026-06-15 19:06:13 +01:00
case "Heat" : return new Heat ( definition , item ) ;
2026-06-15 18:51:59 +01:00
case "HeatStorage" : return new HeatStorage ( definition , item ) ;
2026-06-15 20:12:34 +01:00
case "InstantWeapon" : return new InstantWeapon ( definition , item ) ;
2026-06-15 19:06:13 +01:00
case "ItemUsage" : return new ItemUsage ( definition , item ) ;
2026-06-15 20:12:34 +01:00
case "Launcher" : return new LockWeapon ( definition , item ) ;
case "LockWeapon" : return new LockWeapon ( definition , item ) ;
2026-06-15 19:20:06 +01:00
case "MiningTool" : return new MiningTool ( definition , item ) ;
2026-06-15 19:29:34 +01:00
case "Radiator" : return new Radiator ( definition , item ) ;
case "Reactor" : return new Reactor ( definition , item ) ;
2026-06-15 19:06:13 +01:00
case "Reflector" : return new Reflector ( definition , item ) ;
2026-06-15 19:20:06 +01:00
case "ResourceScanner" : return new ResourceScanner ( definition , item ) ;
2026-06-15 19:29:34 +01:00
case "Sensor" : return new Sensor ( definition , item ) ;
2026-06-15 19:20:06 +01:00
case "Shield" : return new Shield ( definition , item ) ;
2026-06-15 19:29:34 +01:00
case "StatModifier" : return new StatModifier ( definition , item ) ;
2026-06-15 18:51:59 +01:00
case "Switch" : return new Switch ( definition , item ) ;
2026-06-15 19:20:06 +01:00
case "Thermotoggle" : return new Thermotoggle ( definition , item ) ;
case "Thruster" : return new Thruster ( definition , item ) ;
2026-06-15 18:51:59 +01:00
case "Trigger" : return new Trigger ( definition , item ) ;
case "TurretController" : return new TurretController ( definition , item ) ;
2026-06-15 19:06:13 +01:00
case "VelocityConversion" : return new VelocityConversion ( definition , item ) ;
case "VelocityLimit" : return new VelocityLimit ( definition , item ) ;
case "Visibility" : return new Visibility ( definition , item ) ;
case "Wear" : return new Wear ( definition , item ) ;
2026-06-15 18:51:59 +01:00
default : return null ;
}
}
private static Behavior CreateDirectRuntimeBehavior ( AetheriaRuntimeBehaviorPayload payload , ConsumableItemEffect effect )
{
var definition = new RuntimeBehaviorDefinition ( payload ) ;
switch ( payload . Kind )
{
case "Cockpit" : return new Cockpit ( definition , effect ) ;
2026-06-15 19:54:54 +01:00
case "AetherDrive" : return new AetherDrive ( definition , effect ) ;
2026-06-15 20:12:34 +01:00
case "AutoWeapon" : return new InstantWeapon ( definition , effect ) ;
2026-06-15 19:20:06 +01:00
case "Capacitor" : return new Capacitor ( definition , effect ) ;
2026-06-15 20:12:34 +01:00
case "ChargedWeapon" : return new ChargedWeapon ( definition , effect ) ;
2026-06-15 19:06:13 +01:00
case "Cooldown" : return new Cooldown ( definition , effect ) ;
2026-06-15 20:12:34 +01:00
case "ConstantWeapon" : return new ConstantWeapon ( definition , effect ) ;
2026-06-15 19:06:13 +01:00
case "EnergyDraw" : return new EnergyDraw ( definition , effect ) ;
2026-06-15 20:12:34 +01:00
case "GuidedWeapon" : return new InstantWeapon ( definition , effect ) ;
2026-06-15 19:06:13 +01:00
case "Heat" : return new Heat ( definition , effect ) ;
2026-06-15 18:51:59 +01:00
case "HeatStorage" : return new HeatStorage ( definition , effect ) ;
2026-06-15 20:12:34 +01:00
case "InstantWeapon" : return new InstantWeapon ( definition , effect ) ;
2026-06-15 19:06:13 +01:00
case "ItemUsage" : return new ItemUsage ( definition , effect ) ;
2026-06-15 20:12:34 +01:00
case "Launcher" : return new LockWeapon ( definition , effect ) ;
case "LockWeapon" : return new LockWeapon ( definition , effect ) ;
2026-06-15 19:20:06 +01:00
case "MiningTool" : return new MiningTool ( definition , effect ) ;
2026-06-15 19:29:34 +01:00
case "Radiator" : return new Radiator ( definition , effect ) ;
case "Reactor" : return new Reactor ( definition , effect ) ;
2026-06-15 19:06:13 +01:00
case "Reflector" : return new Reflector ( definition , effect ) ;
2026-06-15 19:20:06 +01:00
case "ResourceScanner" : return new ResourceScanner ( definition , effect ) ;
2026-06-15 19:29:34 +01:00
case "Sensor" : return new Sensor ( definition , effect ) ;
2026-06-15 19:20:06 +01:00
case "Shield" : return new Shield ( definition , effect ) ;
2026-06-15 19:29:34 +01:00
case "StatModifier" : return new StatModifier ( definition , effect ) ;
2026-06-15 18:51:59 +01:00
case "Switch" : return new Switch ( definition , effect ) ;
2026-06-15 19:20:06 +01:00
case "Thermotoggle" : return new Thermotoggle ( definition , effect ) ;
case "Thruster" : return new Thruster ( definition , effect ) ;
2026-06-15 18:51:59 +01:00
case "Trigger" : return new Trigger ( definition , effect ) ;
case "TurretController" : return new TurretController ( definition , effect ) ;
2026-06-15 19:06:13 +01:00
case "VelocityConversion" : return new VelocityConversion ( definition , effect ) ;
case "VelocityLimit" : return new VelocityLimit ( definition , effect ) ;
case "Visibility" : return new Visibility ( definition , effect ) ;
case "Wear" : return new Wear ( definition , effect ) ;
2026-06-15 18:51:59 +01:00
default : return null ;
}
2026-06-14 10:54:17 +01:00
}
2026-06-14 05:15:18 +01:00
public Shape GetRuntimeShape ( ItemInstance item )
{
var typedItem = GetRuntimeItem ( item ) ;
return ToShape ( typedItem ? . ShapeWidth ? ? 1 , typedItem ? . ShapeHeight ? ? 1 , typedItem ? . ShapeCells ) ;
}
private static Shape ToShape ( int width , int height , IReadOnlyList < AetheriaRuntimeShapeCell > cells )
{
var shape = new Shape ( Math . Max ( width , 1 ) , Math . Max ( height , 1 ) ) ;
if ( cells = = null ) return shape ;
foreach ( var cell in cells )
2026-06-21 09:37:54 +01:00
shape . SetCell ( cell . X , cell . Y , true ) ;
2026-06-14 05:15:18 +01:00
return shape ;
}
2026-06-14 22:58:02 +01:00
public AetheriaRuntimeItemReference CreateReference ( AetheriaRuntimeCatalogItem item )
2026-06-14 07:27:23 +01:00
{
2026-06-14 22:58:02 +01:00
return new AetheriaRuntimeItemReference ( ToItemKey ( item ) ) ;
}
private static string ToItemKey ( AetheriaRuntimeCatalogItem item )
{
2026-06-15 01:31:16 +01:00
return item ? . ItemKey ? ? "" ;
2026-06-14 07:27:23 +01:00
}
2020-04-16 03:08:18 +01:00
public void Log ( string s )
{
_logger ( s ) ;
}
2020-03-22 19:05:26 +00:00
public float GetMass ( ItemInstance item )
{
2026-06-14 04:35:51 +01:00
var typedItem = GetRuntimeItem ( item ) ;
if ( typedItem = = null )
{
return 0 ;
}
2021-04-18 05:13:20 +01:00
return item switch
2020-03-22 19:05:26 +00:00
{
2026-06-14 04:35:51 +01:00
CraftedItemInstance _ = > ( float ) typedItem . Mass ,
SimpleCommodity commodity = > ( float ) typedItem . Mass * commodity . Quantity ,
2021-04-18 05:13:20 +01:00
_ = > 0
} ;
2020-03-22 19:05:26 +00:00
}
2020-06-05 04:16:33 +01:00
public float GetThermalMass ( ItemInstance item )
2020-03-22 19:05:26 +00:00
{
2026-06-14 04:35:51 +01:00
var typedItem = GetRuntimeItem ( item ) ;
if ( typedItem = = null )
{
return 0 ;
}
2021-04-18 05:13:20 +01:00
return item switch
2020-03-22 19:05:26 +00:00
{
2026-06-14 04:35:51 +01:00
CraftedItemInstance _ = > ( float ) ( typedItem . Mass * typedItem . SpecificHeat ) ,
SimpleCommodity commodity = > ( float ) ( typedItem . Mass * typedItem . SpecificHeat * commodity . Quantity ) ,
2021-04-18 05:13:20 +01:00
_ = > 0
} ;
2020-03-22 19:05:26 +00:00
}
// Returns stat when not equipped
2020-12-10 23:20:38 +00:00
public float Evaluate ( PerformanceStat stat , EquippableItem item )
2020-03-22 19:05:26 +00:00
{
2026-06-14 06:23:45 +01:00
var typedItem = GetRuntimeItem ( item ) ;
var maxDurability = ( float ) ( typedItem ? . Durability ? ? 0 ) ;
if ( maxDurability < = 0 )
{
maxDurability = Math . Max ( item . Durability , 1f ) ;
}
2026-06-21 09:37:54 +01:00
var durabilityRatio = item . Durability / maxDurability ;
var durabilityExponent = Lerp (
2021-03-22 05:22:31 +00:00
GameplaySettings . DurabilityQualityMin ,
GameplaySettings . DurabilityQualityMax ,
2026-06-21 09:37:54 +01:00
MathF . Pow ( item . Quality , GameplaySettings . DurabilityQualityExponent ) ) ;
var result = stat . EvaluateRecipeOrLegacy (
new StatEvaluationContext (
quality : item . Quality ,
durability : durabilityRatio ,
heat : 1 ) ,
durabilityExponent ,
thermalExponent : 0 ,
includeHeat : false ) ;
2021-03-27 10:03:24 +00:00
if ( float . IsNaN ( result ) )
2026-06-15 02:57:50 +01:00
throw new InvalidOperationException ( $"Performance Stat on {typedItem?.Name ?? item.ItemKey} evaluating as NaN: input data is invalid! Durability: {item.Durability} / {maxDurability}" ) ;
2020-03-22 19:05:26 +00:00
return result ;
2021-03-22 05:22:31 +00:00
2020-03-22 19:05:26 +00:00
}
2020-05-25 03:05:30 +01:00
2026-06-21 09:37:54 +01:00
private static float Lerp ( float from , float to , float t )
{
return from + ( to - from ) * t ;
}
2021-03-20 04:20:17 +00:00
public int GetPrice ( CraftedItemInstance item )
{
2026-06-14 04:35:51 +01:00
var typedItem = GetRuntimeItem ( item ) ;
return typedItem = = null
? 0
: ( int ) ( GameplaySettings . QualityPriceModifier . Evaluate ( item . Quality ) * typedItem . Price ) ;
2021-03-20 04:20:17 +00:00
}
2026-06-14 05:56:41 +01:00
public int GetPrice ( ItemInstance item )
{
var typedItem = GetRuntimeItem ( item ) ;
if ( typedItem = = null )
{
return 0 ;
}
return item switch
{
CraftedItemInstance crafted = > ( int ) ( GameplaySettings . QualityPriceModifier . Evaluate ( crafted . Quality ) * typedItem . Price ) ,
SimpleCommodity commodity = > typedItem . Price * commodity . Quantity ,
_ = > typedItem . Price
} ;
}
2026-06-14 07:41:32 +01:00
public SimpleCommodity CreateSimpleCommodityInstance ( AetheriaRuntimeCatalogItem item , int count )
2020-03-22 19:05:26 +00:00
{
2026-06-15 01:31:16 +01:00
if ( ! string . IsNullOrWhiteSpace ( item ? . ItemKey ) )
2020-06-02 07:01:03 +01:00
{
2026-06-14 07:41:32 +01:00
return new SimpleCommodity
2020-03-22 19:05:26 +00:00
{
2026-06-14 22:58:02 +01:00
Reference = CreateReference ( item ) ,
2020-05-25 03:05:30 +01:00
Quantity = count
2020-03-22 19:05:26 +00:00
} ;
2020-06-02 07:01:03 +01:00
}
2026-06-14 07:41:32 +01:00
_logger ( "Attempted to create simple commodity instance using missing typed item data" ) ;
2020-04-09 07:48:45 +01:00
return null ;
2020-03-22 19:05:26 +00:00
}
2021-01-19 01:22:56 +00:00
public ItemInstance Instantiate ( ItemInstance item )
{
2026-06-14 07:41:32 +01:00
var typedItem = GetRuntimeItem ( item ) ;
if ( typedItem = = null )
2021-01-27 00:05:30 +00:00
{
2026-06-15 02:57:50 +01:00
_logger ( $"Attempted to instantiate missing item key {item?.ItemKey}" ) ;
2026-06-14 07:41:32 +01:00
return null ;
2021-01-27 00:05:30 +00:00
}
2026-06-14 07:41:32 +01:00
ItemInstance instance = item switch
2021-01-27 00:05:30 +00:00
{
2026-06-14 07:41:32 +01:00
SimpleCommodity simple = > CreateSimpleCommodityInstance ( typedItem , simple . Quantity ) ,
CraftedItemInstance = > CreateCraftedInstance ( typedItem ) ,
_ = > null
} ;
if ( instance ! = null )
instance . Rotation = item . Rotation ;
return instance ;
2021-01-19 01:22:56 +00:00
}
2021-03-27 10:03:24 +00:00
2026-06-14 07:41:32 +01:00
public CraftedItemInstance CreateCraftedInstance ( AetheriaRuntimeCatalogItem item , float quality )
2021-03-27 10:03:24 +00:00
{
2026-06-14 07:41:32 +01:00
if ( IsEquippable ( item ) )
return CreateEquippableInstance ( item , quality ) ;
2026-06-15 01:31:16 +01:00
if ( string . IsNullOrWhiteSpace ( item ? . ItemKey ) )
2021-03-27 10:03:24 +00:00
{
2026-06-14 07:41:32 +01:00
throw new NullReferenceException ( "Attempted to create crafted item instance using missing typed item data!" ) ;
}
2026-06-14 09:38:55 +01:00
if ( string . Equals ( item . Category , AetheriaRuntimeItemCategories . Consumable , StringComparison . Ordinal ) )
2026-06-14 07:41:32 +01:00
{
return new ConsumableItem
2021-03-27 10:03:24 +00:00
{
2026-06-14 22:58:02 +01:00
Reference = CreateReference ( item ) ,
2026-06-14 07:41:32 +01:00
Quality = quality
2021-03-27 10:03:24 +00:00
} ;
}
2026-06-14 07:41:32 +01:00
return new CompoundCommodity
2021-03-27 10:03:24 +00:00
{
2026-06-14 22:58:02 +01:00
Reference = CreateReference ( item ) ,
2021-03-27 10:03:24 +00:00
Quality = quality
} ;
2026-06-14 07:41:32 +01:00
}
public CraftedItemInstance CreateCraftedInstance ( AetheriaRuntimeCatalogItem item )
{
return CreateCraftedInstance ( item , SelectCraftedQuality ( ) ) ;
2021-03-27 10:03:24 +00:00
}
2026-06-14 06:35:34 +01:00
2026-06-14 07:27:23 +01:00
public EquippableItem CreateEquippableInstance ( AetheriaRuntimeCatalogItem item , float quality )
{
2026-06-15 01:31:16 +01:00
if ( string . IsNullOrWhiteSpace ( item ? . ItemKey ) )
2026-06-14 07:27:23 +01:00
{
throw new NullReferenceException ( "Attempted to create equippable item instance using missing typed item data!" ) ;
}
return new EquippableItem
{
2026-06-14 22:58:02 +01:00
Reference = CreateReference ( item ) ,
2026-06-14 07:27:23 +01:00
Quality = quality ,
Durability = item . Durability > 0 ? ( float ) item . Durability : 1f
} ;
}
public EquippableItem CreateEquippableInstance ( AetheriaRuntimeCatalogItem item )
{
return CreateEquippableInstance ( item , SelectCraftedQuality ( ) ) ;
}
private float SelectCraftedQuality ( )
{
2021-03-08 02:57:38 +00:00
var quality = Random . NextFloat ( ) ;
var tier = GameplaySettings . Tiers [ 0 ] ;
foreach ( var t in GameplaySettings . Tiers )
{
if ( t . Rarity > quality )
tier = t ;
}
2020-03-22 19:05:26 +00:00
2026-06-14 07:27:23 +01:00
return tier . Quality ;
}
2026-06-14 07:41:32 +01:00
private static bool IsEquippable ( AetheriaRuntimeCatalogItem item )
{
return ! string . IsNullOrWhiteSpace ( item ? . HardpointType ) | |
2026-06-14 09:38:55 +01:00
string . Equals ( item ? . Category , AetheriaRuntimeItemCategories . Hull , StringComparison . Ordinal ) ;
2026-06-14 07:41:32 +01:00
}
2021-03-08 02:57:38 +00:00
public ( RarityTier tier , int upgrades ) GetTier ( CraftedItemInstance item )
{
var tier = GameplaySettings . Tiers [ 0 ] ;
foreach ( var t in GameplaySettings . Tiers )
if ( item . Quality + . 001f > t . Quality )
tier = t ;
int upgrades = ( int ) ( ( item . Quality - tier . Quality ) / . 0499f ) ;
return ( tier , upgrades ) ;
}
2026-06-13 07:11:16 +01:00
}