2020-12-22 03:13:43 +00:00
/ * This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
2020-04-09 10:33:16 +01:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2026-06-14 04:47:02 +01:00
using GameCult.Aetheria.State.Unity ;
2020-12-10 23:20:38 +00:00
using UniRx ;
2020-04-09 10:33:16 +01:00
using Unity.Mathematics ;
using static Unity . Mathematics . math ;
2020-12-10 23:20:38 +00:00
using int2 = Unity . Mathematics . int2 ;
2020-04-09 10:33:16 +01:00
2020-12-10 23:20:38 +00:00
public abstract class Entity
2020-04-09 10:33:16 +01:00
{
2020-12-10 23:20:38 +00:00
public Zone Zone ;
2021-03-20 04:20:17 +00:00
public Faction Faction ;
2020-12-10 23:20:38 +00:00
public EquippableItem Hull ;
2021-04-18 05:13:20 +01:00
public EquippedItem EquippedHull ;
2020-06-01 03:11:04 +01:00
2021-01-06 14:56:39 +00:00
public float3 Position ;
2020-06-01 03:11:04 +01:00
public float2 Direction = float2 ( 0 , 1 ) ;
public float2 Velocity ;
2020-12-10 23:20:38 +00:00
public float [ , ] Temperature ;
2021-01-17 23:26:53 +00:00
public float [ , ] NewTemperature ;
2021-01-17 02:45:52 +00:00
public bool2 [ , ] HullConductivity ;
2020-12-27 22:18:22 +00:00
public float [ , ] ThermalMass ;
2020-06-05 04:16:33 +01:00
2020-12-19 02:37:13 +00:00
public readonly ReactiveCollection < EquippedItem > Equipment = new ReactiveCollection < EquippedItem > ( ) ;
public readonly ReactiveCollection < EquippedCargoBay > CargoBays = new ReactiveCollection < EquippedCargoBay > ( ) ;
public readonly ReactiveCollection < EquippedDockingBay > DockingBays = new ReactiveCollection < EquippedDockingBay > ( ) ;
2021-02-26 19:01:47 +00:00
public readonly ReactiveCollection < Entity > VisibleEntities = new ReactiveCollection < Entity > ( ) ;
2021-05-11 04:45:42 +01:00
public readonly ReactiveDictionary < Entity , bool > EntityHostility = new ReactiveDictionary < Entity , bool > ( ) ;
public readonly ReactiveCollection < Entity > VisibleEnemies = new ReactiveCollection < Entity > ( ) ;
public readonly ReactiveCollection < Entity > VisibleFriendlies = new ReactiveCollection < Entity > ( ) ;
2020-12-10 23:20:38 +00:00
public Entity Parent ;
public List < Entity > Children = new List < Entity > ( ) ;
2021-01-10 03:59:47 +00:00
public ReactiveProperty < Entity > Target = new ReactiveProperty < Entity > ( ( Entity ) null ) ;
2021-01-05 02:56:17 +00:00
public float3 LookDirection ;
2020-06-01 03:11:04 +01:00
public string Name ;
2021-05-16 05:24:51 +01:00
// public int Population;
2020-05-06 04:56:36 +01:00
2020-12-10 23:20:38 +00:00
public readonly Dictionary < string , float > Messages = new Dictionary < string , float > ( ) ;
public readonly Dictionary < object , float > VisibilitySources = new Dictionary < object , float > ( ) ;
2021-02-26 19:01:47 +00:00
public readonly ReactiveDictionary < Entity , float > EntityInfoGathered = new ReactiveDictionary < Entity , float > ( ) ;
2021-01-08 12:44:10 +00:00
public readonly Dictionary < HardpointData , ( float3 position , float3 direction ) > HardpointTransforms =
2021-01-15 20:07:39 +00:00
new Dictionary < HardpointData , ( float3 position , float3 direction ) > ( ) ;
2020-06-02 07:01:03 +01:00
2021-05-02 20:59:45 +01:00
public ( List < Weapon > weapons , List < EquippedItem > items ) [ ] WeaponGroups ;
2020-12-31 19:53:16 +00:00
public List < IPopulationAssignment > PopulationAssignments = new List < IPopulationAssignment > ( ) ;
2020-12-27 22:18:22 +00:00
2020-12-12 03:42:58 +00:00
public EquippedItem [ , ] GearOccupancy ;
public HardpointData [ , ] Hardpoints ;
2021-01-08 12:44:10 +00:00
public float [ , ] Armor ;
2021-02-01 08:13:14 +00:00
public float [ , ] MaxArmor ;
2020-12-10 23:20:38 +00:00
2020-12-27 22:18:22 +00:00
private EquippedItem [ ] _orderedEquipment ;
2021-02-11 03:14:20 +00:00
private List < Weapon > _weapons = new List < Weapon > ( ) ;
2021-03-02 23:02:19 +00:00
private List < Capacitor > _capacitors = new List < Capacitor > ( ) ;
private List < Reactor > _reactors = new List < Reactor > ( ) ;
2021-03-04 03:58:46 +00:00
private List < Radiator > _heatsinks = new List < Radiator > ( ) ;
2021-04-18 05:13:20 +01:00
private List < ConsumableItemEffect > _activeConsumables = new List < ConsumableItemEffect > ( ) ;
2021-01-05 02:56:17 +00:00
protected bool _active ;
2021-03-04 03:58:46 +00:00
private bool _heatsinksEnabled = true ;
public bool HeatsinksEnabled
{
get = > _heatsinksEnabled ;
set
{
if ( value = = _heatsinksEnabled ) return ;
_heatsinksEnabled = value ;
foreach ( var heatsink in _heatsinks )
2021-03-07 01:10:31 +00:00
heatsink . Item . Enabled . Value = value ;
2021-03-04 03:58:46 +00:00
}
}
2021-03-14 03:56:49 +00:00
public EntitySettings Settings { get ; }
2021-03-07 01:10:31 +00:00
public bool OverrideShutdown { get ; set ; }
2021-04-15 04:25:22 +01:00
public float TractorPower { get ; set ; }
2021-03-04 03:58:46 +00:00
public bool Active
{
get = > _active ;
}
2020-12-27 22:18:22 +00:00
2021-02-11 03:14:20 +00:00
public IEnumerable < Weapon > Weapons
{
get = > _weapons ;
}
2021-03-06 01:04:26 +00:00
public Shield Shield { get ; private set ; }
2021-03-06 02:42:19 +00:00
public Cockpit Cockpit { get ; private set ; }
2021-03-21 01:55:25 +00:00
public Sensor Sensor { get ; private set ; }
2021-03-06 02:42:19 +00:00
public float Heatstroke { get ; private set ; }
2021-04-10 01:47:16 +01:00
public float Hypothermia { get ; private set ; }
2021-03-06 01:04:26 +00:00
2021-02-26 19:01:47 +00:00
public float TargetRange { get ; private set ; }
2021-01-27 00:05:30 +00:00
public float MaxTemp { get ; private set ; }
public float MinTemp { get ; private set ; }
2020-12-10 23:20:38 +00:00
public ItemManager ItemManager { get ; }
public int AssignedPopulation = > PopulationAssignments . Sum ( pa = > pa . AssignedPopulation ) ;
public float Mass { get ; private set ; }
public float Visibility = > VisibilitySources . Values . Sum ( ) ;
2021-04-27 16:44:44 +01:00
public Subject < Entity > IncomingHit = new Subject < Entity > ( ) ;
2021-01-08 12:44:10 +00:00
public Subject < ( int2 pos , float damage ) > ArmorDamage = new Subject < ( int2 , float ) > ( ) ;
public Subject < ( EquippedItem item , float damage ) > ItemDamage = new Subject < ( EquippedItem , float ) > ( ) ;
2021-03-07 01:10:31 +00:00
// public Subject<EquippedItem> ItemOffline = new Subject<EquippedItem>();
// public Subject<EquippedItem> ItemOnline = new Subject<EquippedItem>();
2021-01-08 12:44:10 +00:00
public Subject < float > HullDamage = new Subject < float > ( ) ;
2021-02-26 19:01:47 +00:00
public Subject < Entity > Docked = new Subject < Entity > ( ) ;
2021-03-06 02:42:19 +00:00
public Subject < Unit > HeatstrokeRisk = new Subject < Unit > ( ) ;
2021-03-09 02:26:58 +00:00
public Subject < Unit > HeatstrokeDeath = new Subject < Unit > ( ) ;
2021-04-10 01:47:16 +01:00
public Subject < Unit > HypothermiaRisk = new Subject < Unit > ( ) ;
public Subject < Unit > HypothermiaDeath = new Subject < Unit > ( ) ;
2021-04-30 11:19:25 +01:00
public Subject < Entity > TargetedBy = new Subject < Entity > ( ) ;
public ReactiveProperty < int > TargetedByCount = new ReactiveProperty < int > ( 0 ) ;
2021-05-11 04:45:42 +01:00
public ReactiveProperty < SecurityLevel > CurrentSecurityLevel = new ReactiveProperty < SecurityLevel > ( SecurityLevel . Open ) ;
public ReadOnlyReactiveProperty < bool > PresencePermitted ;
2021-01-10 03:59:47 +00:00
public UniRx . IObservable < EquippedItem > ItemDestroyed ;
public UniRx . IObservable < int2 > HullArmorDepleted ;
public UniRx . IObservable < HardpointData > HardpointArmorDepleted ;
2021-02-26 19:01:47 +00:00
public UniRx . IObservable < Weapon > WeaponDestroyed ;
2021-04-10 01:47:16 +01:00
public UniRx . IObservable < CauseOfDeath > Death ;
2021-01-08 12:44:10 +00:00
2021-02-26 19:01:47 +00:00
private List < IDisposable > _subscriptions = new List < IDisposable > ( ) ;
2021-05-11 04:45:42 +01:00
private Dictionary < Entity , List < IDisposable > > _watchedEntitySubscriptions = new Dictionary < Entity , List < IDisposable > > ( ) ;
2021-02-26 19:01:47 +00:00
public virtual void Activate ( )
2020-07-03 01:33:42 +01:00
{
2021-03-21 01:55:25 +00:00
//ItemManager.Log($"Entity {Name} is activating!");
2021-02-26 19:01:47 +00:00
_active = true ;
2021-03-06 02:42:19 +00:00
Heatstroke = 0 ;
2021-05-14 03:03:28 +01:00
// The entity's presence is permitted when its faction owns the zone, or the current security level at its location is low enough
PresencePermitted = new ReadOnlyReactiveProperty < bool > ( CurrentSecurityLevel . Select ( security = >
{
var factionRelationship = GetFactionRelationship ( Zone . GalaxyZone . Owner ) ;
var presencePermitted = IsPresencePermitted ( factionRelationship , security ) ;
return presencePermitted ;
} ) , initialValue : true ) ;
2021-02-26 19:01:47 +00:00
foreach ( var item in Equipment )
foreach ( var behavior in item . Behaviors )
2020-07-03 01:33:42 +01:00
{
2021-02-26 19:01:47 +00:00
if ( behavior is IInitializableBehavior initializableBehavior )
initializableBehavior . Initialize ( ) ;
2020-07-03 01:33:42 +01:00
}
2021-05-11 04:45:42 +01:00
foreach ( var entity in Zone . Entities )
{
EntityInfoGathered [ entity ] = 0 ;
EntityHostility [ entity ] = IsHostileTo ( entity ) ;
}
_subscriptions . Add ( Zone . Entities . ObserveAdd ( ) . Subscribe ( add = >
{
EntityInfoGathered [ add . Value ] = 0 ;
EntityHostility [ add . Value ] = IsHostileTo ( add . Value ) ;
} ) ) ;
2021-02-26 19:01:47 +00:00
_subscriptions . Add ( Zone . Entities . ObserveRemove ( ) . Subscribe ( remove = >
{
if ( Target . Value = = remove . Value ) Target . Value = null ;
EntityInfoGathered . Remove ( remove . Value ) ;
2021-05-11 04:45:42 +01:00
EntityHostility . Remove ( remove . Value ) ;
2021-02-26 19:01:47 +00:00
VisibleEntities . Remove ( remove . Value ) ;
2021-05-11 04:45:42 +01:00
VisibleEnemies . Remove ( remove . Value ) ;
VisibleFriendlies . Remove ( remove . Value ) ;
2021-02-26 19:01:47 +00:00
} ) ) ;
2021-05-11 04:45:42 +01:00
_subscriptions . Add ( VisibleEnemies . ObserveRemove ( ) . Subscribe ( remove = >
2021-02-26 19:01:47 +00:00
{
if ( Target . Value = = remove . Value ) Target . Value = null ;
} ) ) ;
2021-05-11 04:45:42 +01:00
_subscriptions . Add ( Target . Subscribe ( entity = > entity ? . TargetedBy . OnNext ( this ) ) ) ;
_subscriptions . Add ( TargetedBy . Subscribe ( enemy = >
{
TargetedByCount . Value + + ;
enemy . Target . Where ( t = > t ! = this ) . Take ( 1 ) . Subscribe ( _ = > TargetedByCount . Value - - ) ;
} ) ) ;
//
_subscriptions . Add ( EntityInfoGathered . ObserveReplace ( ) . Subscribe ( replace = >
{
if ( replace . OldValue < ItemManager . GameplaySettings . TargetDetectionInfoThreshold & &
replace . NewValue > ItemManager . GameplaySettings . TargetDetectionInfoThreshold )
{
VisibleEntities . Add ( replace . Key ) ;
if ( EntityHostility [ replace . Key ] )
VisibleEnemies . Add ( replace . Key ) ;
else VisibleFriendlies . Add ( replace . Key ) ;
}
if ( replace . OldValue > ItemManager . GameplaySettings . TargetDetectionInfoThreshold & &
replace . NewValue < ItemManager . GameplaySettings . TargetDetectionInfoThreshold )
{
VisibleEntities . Remove ( replace . Key ) ;
VisibleEnemies . Remove ( replace . Key ) ;
VisibleFriendlies . Remove ( replace . Key ) ;
}
} ) ) ;
// Create subscriptions to events occurring on any visible entity
_subscriptions . Add ( VisibleEntities . ObserveAdd ( )
. Select ( add = > add . Value )
. Subscribe ( entity = >
{
var newVisibleEntitySubscriptions = new List < IDisposable >
{
// Respond to changes in the target's hostility status by updating the contents of VisibleEnemies and VisibleFriendlies
EntityHostility . ObserveReplace ( )
. Where ( replace = > replace . Key = = entity )
. Select ( replace = > replace . NewValue )
. Subscribe ( isHostile = >
{
( isHostile ? VisibleEnemies : VisibleFriendlies ) . Add ( entity ) ;
( isHostile ? VisibleFriendlies : VisibleEnemies ) . Remove ( entity ) ;
} )
} ;
_watchedEntitySubscriptions [ entity ] = newVisibleEntitySubscriptions ;
} ) ) ;
// Cleanup visible entity subscriptions
_subscriptions . Add ( VisibleEntities . ObserveRemove ( ) . Subscribe ( disappearingEntity = >
{
foreach ( var subscription in _watchedEntitySubscriptions [ disappearingEntity . Value ] ) subscription . Dispose ( ) ;
_watchedEntitySubscriptions . Remove ( disappearingEntity . Value ) ;
} ) ) ;
2021-05-02 20:59:45 +01:00
if ( WeaponGroups . All ( wg = > ! wg . items . Any ( ) ) )
GenerateWeaponGroups ( ) ;
2020-07-03 01:33:42 +01:00
}
2020-05-25 03:05:30 +01:00
2021-02-26 19:01:47 +00:00
public virtual void Deactivate ( )
{
2021-03-21 01:55:25 +00:00
//ItemManager.Log($"Entity {Name} is deactivating!");
2021-02-26 19:01:47 +00:00
foreach ( var s in _subscriptions ) s . Dispose ( ) ;
_subscriptions . Clear ( ) ;
2021-05-11 04:45:42 +01:00
foreach ( var ss in _watchedEntitySubscriptions . Values ) foreach ( var s in ss ) s . Dispose ( ) ;
_watchedEntitySubscriptions . Clear ( ) ;
2021-02-26 19:01:47 +00:00
_active = false ;
EntityInfoGathered . Clear ( ) ;
VisibleEntities . Clear ( ) ;
2021-05-11 04:45:42 +01:00
VisibleEnemies . Clear ( ) ;
VisibleFriendlies . Clear ( ) ;
2021-02-26 19:01:47 +00:00
}
2021-01-05 02:56:17 +00:00
2021-03-14 03:56:49 +00:00
public Entity ( ItemManager itemManager , Zone zone , EquippableItem hull , EntitySettings settings )
2020-04-09 10:33:16 +01:00
{
2021-03-14 03:56:49 +00:00
Settings = settings ;
2020-12-10 23:20:38 +00:00
ItemManager = itemManager ;
2020-05-07 03:53:54 +01:00
Zone = zone ;
2020-04-23 03:31:17 +01:00
Hull = hull ;
2026-06-14 06:19:20 +01:00
var typedHull = itemManager . GetRuntimeItem ( hull ) ;
if ( typedHull = = null )
throw new InvalidOperationException ( $"Unable to construct entity: missing typed hull row for {hull?.Data?.ItemId}" ) ;
Name = typedHull . Name ;
2021-01-08 12:44:10 +00:00
MapEntity ( ) ;
2021-05-02 20:59:45 +01:00
WeaponGroups = new ( List < Weapon > weapons , List < EquippedItem > items ) [ itemManager . GameplaySettings . WeaponGroupCount ] ;
for ( int i = 0 ; i < itemManager . GameplaySettings . WeaponGroupCount ; i + + )
WeaponGroups [ i ] = ( new List < Weapon > ( ) , new List < EquippedItem > ( ) ) ;
2021-01-10 03:59:47 +00:00
ItemDestroyed = ItemDamage . Where ( x = > x . item . EquippableItem . Durability < . 01f ) . Select ( x = > x . item ) ;
2021-02-26 19:01:47 +00:00
WeaponDestroyed = ItemDestroyed . Select ( x = > x . Behaviors . FirstOrDefault ( b = > b is Weapon ) as Weapon ) . Where ( x = > x ! = null ) ;
2021-01-10 03:59:47 +00:00
HullArmorDepleted = ArmorDamage . Where ( x = > Armor [ x . pos . x , x . pos . y ] < . 01f ) . Select ( x = > x . pos ) ;
2021-04-10 01:47:16 +01:00
Death = HullDamage . Where ( _ = > Hull . Durability < . 01f ) . Select ( _ = > CauseOfDeath . HullDestroyed )
. Merge ( HeatstrokeDeath . Select ( _ = > CauseOfDeath . Heatstroke ) )
. Merge ( HypothermiaDeath . Select ( _ = > CauseOfDeath . Hypothermia ) )
. Merge ( ItemDestroyed . Where ( i = > i . GetBehavior < Cockpit > ( ) ! = null ) . Select ( _ = > CauseOfDeath . CockpitDestroyed ) ) ;
2021-02-26 19:01:47 +00:00
2021-05-14 03:03:28 +01:00
//CurrentSecurityLevel.Value = SecurityLevel.Open;
2021-05-11 04:45:42 +01:00
}
2021-04-30 11:19:25 +01:00
2021-05-11 04:45:42 +01:00
public bool IsHostileTo ( Entity other , bool recursive = false )
{
if ( Faction = = null )
return ! recursive & & other . Faction ! = null & & other . IsHostileTo ( this , true ) ;
// TODO: Inter-faction hostility
// When the entity faction owns the zone, they are hostile to trespassers or those hostile to them
2021-05-16 05:24:51 +01:00
if ( Faction . ID = = Zone . GalaxyZone . Owner ? . ID )
2021-05-14 03:03:28 +01:00
return recursive ? ! ( other . PresencePermitted ? . Value ? ? true ) : ! ( other . PresencePermitted ? . Value ? ? true ) | | other . IsHostileTo ( this , true ) ;
2021-05-11 04:45:42 +01:00
return ! recursive & & other . IsHostileTo ( this , true ) ;
2021-02-26 19:01:47 +00:00
}
2021-05-11 04:45:42 +01:00
// TODO: Inter-faction relationships
2021-05-12 01:41:18 +01:00
public FactionRelationship GetFactionRelationship ( Faction faction )
{
if ( faction = = null )
return FactionRelationship . Neutral ;
if ( this is Ship { IsPlayerShip : true } )
2021-05-14 03:03:28 +01:00
return Zone . Galaxy . FactionRelationships [ faction ] ;
2021-05-12 01:41:18 +01:00
return faction . ID = = Faction . ID ? FactionRelationship . Beloved : FactionRelationship . Neutral ;
}
2021-05-12 00:15:11 +01:00
public static bool IsPresencePermitted ( FactionRelationship relationship , SecurityLevel securityLevel ) = >
( int ) relationship - ( int ) securityLevel > 0 ;
public static bool IsDockingPermitted ( FactionRelationship relationship , SecurityLevel securityLevel ) = >
( int ) relationship - ( int ) securityLevel > 1 ;
2021-05-11 04:45:42 +01:00
2021-04-18 05:13:20 +01:00
public void ActivateConsumable ( ConsumableItem item )
{
_activeConsumables . Add ( new ConsumableItemEffect ( item , this ) ) ;
}
public ConsumableItemEffect FindActiveConsumable ( ConsumableItemData data )
{
return _activeConsumables . FirstOrDefault ( ac = > ac . Data . ID = = data . ID ) ;
}
public bool CanActivateConsumable ( ConsumableItemData data )
{
return data . Stackable | | FindActiveConsumable ( data ) = = null ;
}
public bool TryActivateConsumable ( ConsumableItemData data )
{
if ( ! CanActivateConsumable ( data ) ) return false ;
var bay = FindItemInCargo ( data . ID ) ;
if ( bay = = null ) return false ;
var item = ( ConsumableItem ) bay . ItemsOfType [ data . ID ] . First ( ) ;
ActivateConsumable ( item ) ;
bay . Remove ( item ) ;
return true ;
}
2021-01-08 12:44:10 +00:00
private void MapEntity ( )
2020-06-05 04:16:33 +01:00
{
2026-06-14 05:08:39 +01:00
var typedHull = ItemManager . GetRuntimeItem ( Hull ) ;
if ( typedHull = = null )
throw new InvalidOperationException ( $"Unable to map entity {Name}: missing typed hull row for {Hull?.Data?.ItemId}" ) ;
var hullShape = GetHullShape ( typedHull ) ;
2021-04-18 05:13:20 +01:00
EquippedHull = new EquippedItem ( ItemManager , Hull , int2 . zero , this ) ;
Equipment . Add ( EquippedHull ) ;
2026-06-14 05:08:39 +01:00
Mass = ItemManager . GetMass ( Hull ) ;
Temperature = new float [ hullShape . Width , hullShape . Height ] ;
NewTemperature = new float [ hullShape . Width , hullShape . Height ] ;
HullConductivity = new bool2 [ hullShape . Width , hullShape . Height ] ;
ThermalMass = new float [ hullShape . Width , hullShape . Height ] ;
Armor = new float [ hullShape . Width , hullShape . Height ] ;
MaxArmor = new float [ hullShape . Width , hullShape . Height ] ;
Hardpoints = new HardpointData [ hullShape . Width , hullShape . Height ] ;
foreach ( var typedHardpoint in typedHull . Hardpoints )
2020-06-05 04:16:33 +01:00
{
2026-06-14 05:08:39 +01:00
var hardpoint = ProjectHardpoint ( typedHardpoint ) ;
2020-12-10 23:20:38 +00:00
foreach ( var hardpointCoord in hardpoint . Shape . Coordinates )
{
var hullCoord = hardpoint . Position + hardpointCoord ;
2020-12-12 03:42:58 +00:00
Hardpoints [ hullCoord . x , hullCoord . y ] = hardpoint ;
2020-12-10 23:20:38 +00:00
}
2020-07-03 01:33:42 +01:00
}
2026-06-14 05:08:39 +01:00
var cellCount = Math . Max ( hullShape . Coordinates . Length , 1 ) ;
foreach ( var v in hullShape . Coordinates )
2021-02-01 08:13:14 +00:00
{
2026-06-14 05:08:39 +01:00
Armor [ v . x , v . y ] = ( float ) typedHull . HullArmor ;
MaxArmor [ v . x , v . y ] = ( float ) typedHull . HullArmor ;
2021-02-01 08:13:14 +00:00
if ( Hardpoints [ v . x , v . y ] ! = null )
{
Armor [ v . x , v . y ] + = Hardpoints [ v . x , v . y ] . Armor ;
MaxArmor [ v . x , v . y ] + = Hardpoints [ v . x , v . y ] . Armor ;
}
Temperature [ v . x , v . y ] = 280 ;
2026-06-14 05:08:39 +01:00
ThermalMass [ v . x , v . y ] = ( float ) ( typedHull . Mass * typedHull . SpecificHeat / cellCount ) ;
2021-02-01 08:13:14 +00:00
}
2026-06-14 05:08:39 +01:00
GearOccupancy = new EquippedItem [ hullShape . Width , hullShape . Height ] ;
2020-05-06 04:56:36 +01:00
}
2021-05-02 20:59:45 +01:00
public void GenerateWeaponGroups ( )
2021-02-26 19:01:47 +00:00
{
foreach ( var group in Weapons
2026-06-13 20:02:41 +01:00
. GroupBy ( w = > w . Item . EquippableItem . Data . ItemId )
2021-04-18 05:13:20 +01:00
. OrderBy ( wg = > wg . Average ( w = > w . Range ) )
2021-02-26 19:01:47 +00:00
. Select ( ( weapons , index ) = > ( weapons , index ) ) )
{
2021-05-02 20:59:45 +01:00
WeaponGroups [ group . index ] . weapons = group . weapons . ToList ( ) ;
WeaponGroups [ group . index ] . items = group . weapons . Select ( w = > w . Item ) . ToList ( ) ;
2021-02-26 19:01:47 +00:00
}
}
2021-02-01 08:13:14 +00:00
public void AddHeat ( int2 position , float heat , bool ignoreThermalMass = false )
2020-05-06 04:56:36 +01:00
{
2021-02-01 08:13:14 +00:00
if ( ignoreThermalMass )
Temperature [ position . x , position . y ] + = heat ;
else
Temperature [ position . x , position . y ] + = heat / ThermalMass [ position . x , position . y ] ;
2020-12-31 19:53:16 +00:00
}
2020-06-11 02:55:11 +01:00
2020-12-31 19:53:16 +00:00
public int CountItemsInCargo ( Guid itemDataID )
{
int sum = 0 ;
foreach ( var x in CargoBays )
2020-07-03 01:33:42 +01:00
{
2020-12-31 19:53:16 +00:00
if ( x . ItemsOfType . ContainsKey ( itemDataID ) )
{
foreach ( var i in x . ItemsOfType [ itemDataID ] ) sum + = i is SimpleCommodity simpleCommodity ? simpleCommodity . Quantity : 1 ;
}
2020-07-03 01:33:42 +01:00
}
2020-06-05 04:16:33 +01:00
2020-12-31 19:53:16 +00:00
return sum ;
2020-12-27 22:18:22 +00:00
}
2020-12-10 23:20:38 +00:00
public EquippedCargoBay FindItemInCargo ( Guid itemDataID )
2020-06-05 04:16:33 +01:00
{
2020-12-19 02:37:13 +00:00
return CargoBays . FirstOrDefault ( c = > c . ItemsOfType . ContainsKey ( itemDataID ) ) ;
2020-06-10 02:45:10 +01:00
}
2021-03-20 04:20:17 +00:00
public Shape UnoccupiedSpace
{
get
{
2026-06-14 05:08:39 +01:00
var typedHull = ItemManager . GetRuntimeItem ( Hull ) ;
var hullShape = GetHullShape ( typedHull ) ;
var hullInterior = GetHullInteriorShape ( typedHull ) ;
var emptyShape = new Shape ( hullShape . Width , hullShape . Height ) ;
foreach ( var v in hullShape . Coordinates )
2021-03-20 04:20:17 +00:00
{
2026-06-14 05:08:39 +01:00
if ( hullInterior [ v ] & & GearOccupancy [ v . x , v . y ] = = null & & Hardpoints [ v . x , v . y ] = = null )
2021-03-20 04:20:17 +00:00
emptyShape [ v ] = true ;
}
return emptyShape ;
}
}
2020-12-10 23:20:38 +00:00
// Attempts to move a given number of items of the given type to the target Entity
// Returns the number of items successfully transferred
public int TryTransferItems ( Entity target , Guid itemDataID , int quantity )
2020-06-10 02:45:10 +01:00
{
2020-12-10 23:20:38 +00:00
int quantityTransferred = 0 ;
while ( quantityTransferred < quantity )
{
2020-12-19 02:37:13 +00:00
EquippedCargoBay originInventory = CargoBays . FirstOrDefault ( c = > c . ItemsOfType . ContainsKey ( itemDataID ) ) ;
2020-12-10 23:20:38 +00:00
if ( originInventory = = null ) break ;
var itemInstance = originInventory . ItemsOfType [ itemDataID ] [ 0 ] ;
if ( itemInstance is SimpleCommodity simpleCommodity )
{
var targetQuantity = min ( simpleCommodity . Quantity , quantity - quantityTransferred ) ;
2020-12-19 02:37:13 +00:00
if ( ! target . CargoBays . Any ( c = > originInventory . TryTransferItem ( c , simpleCommodity , targetQuantity ) ) )
2020-12-10 23:20:38 +00:00
{
quantityTransferred + = targetQuantity - simpleCommodity . Quantity ;
break ;
}
quantityTransferred + = targetQuantity ;
}
else if ( itemInstance is CraftedItemInstance craftedItemInstance )
{
2020-12-19 02:37:13 +00:00
if ( ! target . CargoBays . Any ( c = > originInventory . TryTransferItem ( c , craftedItemInstance ) ) )
2020-12-10 23:20:38 +00:00
break ;
quantityTransferred + + ;
}
}
2020-06-10 02:45:10 +01:00
2020-12-10 23:20:38 +00:00
return quantityTransferred ;
2020-06-05 04:16:33 +01:00
}
2021-03-04 03:58:46 +00:00
public EquippableItem TryUnequip ( EquippedItem item )
2020-06-05 04:16:33 +01:00
{
2021-03-04 03:58:46 +00:00
// Don't allow unequipping when the entity is active
if ( _active ) return null ;
2020-12-10 23:20:38 +00:00
if ( item . EquippableItem = = null )
{
ItemManager . Log ( "Attempted to remove equipped item with no equippable item on it! This should be impossible!" ) ;
return null ;
}
2020-06-05 04:16:33 +01:00
2020-12-10 23:20:38 +00:00
if ( item is EquippedCargoBay cargoBay )
{
if ( cargoBay . Cargo . Count > 0 )
{
ItemManager . Log ( "Attempted to remove cargo bay that is not empty! Please check first before doing this!" ) ;
return null ;
}
2020-06-05 04:16:33 +01:00
2020-12-19 02:37:13 +00:00
CargoBays . Remove ( cargoBay ) ;
2020-12-10 23:20:38 +00:00
}
2020-12-19 02:37:13 +00:00
Equipment . Remove ( item ) ;
2020-12-27 22:18:22 +00:00
_orderedEquipment = Equipment . OrderBy ( x = > x . SortPosition ) . ToArray ( ) ;
2020-12-10 23:20:38 +00:00
2026-06-14 04:56:01 +01:00
var itemShape = GetItemShape ( item . EquippableItem ) ;
var itemCellCount = Math . Max ( itemShape . Coordinates . Length , 1 ) ;
foreach ( var i in GetHullShape ( ) . Coordinates )
2021-01-17 02:45:52 +00:00
if ( GearOccupancy [ i . x , i . y ] = = item )
2020-12-27 22:18:22 +00:00
{
2026-06-14 04:56:01 +01:00
ThermalMass [ i . x , i . y ] - = ItemManager . GetThermalMass ( item . EquippableItem ) / itemCellCount ;
2021-01-17 02:45:52 +00:00
GearOccupancy [ i . x , i . y ] = null ;
2020-12-27 22:18:22 +00:00
}
2026-06-14 04:56:01 +01:00
Mass - = ItemManager . GetMass ( item . EquippableItem ) ;
2021-02-11 03:14:20 +00:00
foreach ( var b in item . Behaviors )
2021-03-02 23:02:19 +00:00
{
2021-02-11 03:14:20 +00:00
if ( b is Weapon weapon )
2021-03-02 23:02:19 +00:00
_weapons . Remove ( weapon ) ;
2021-03-06 01:04:26 +00:00
if ( b is Capacitor capacitor )
2021-03-02 23:02:19 +00:00
_capacitors . Remove ( capacitor ) ;
2021-03-06 01:04:26 +00:00
if ( b is Reactor reactor )
2021-03-02 23:02:19 +00:00
_reactors . Remove ( reactor ) ;
2021-03-06 01:04:26 +00:00
if ( b is Radiator heatsink )
2021-03-04 03:58:46 +00:00
_heatsinks . Remove ( heatsink ) ;
2021-03-06 01:04:26 +00:00
if ( b is Shield )
Shield = null ;
2021-03-06 02:42:19 +00:00
if ( b is Cockpit )
Cockpit = null ;
2021-03-21 01:55:25 +00:00
if ( b is Sensor )
Sensor = null ;
2021-03-02 23:02:19 +00:00
}
2020-06-05 04:16:33 +01:00
2020-12-10 23:20:38 +00:00
return item . EquippableItem ;
2020-06-05 04:16:33 +01:00
}
2026-06-14 04:56:01 +01:00
private Shape GetHullShape ( )
{
return GetHullShape ( ItemManager . GetRuntimeItem ( Hull ) ) ;
}
private static Shape GetHullShape ( AetheriaRuntimeCatalogItem typedHull )
{
return ToShape ( typedHull ? . ShapeWidth ? ? 1 , typedHull ? . ShapeHeight ? ? 1 , typedHull ? . ShapeCells ) ;
}
private static Shape GetHullInteriorShape ( AetheriaRuntimeCatalogItem typedHull )
{
return ToShape ( typedHull ? . InteriorShapeWidth ? ? 1 , typedHull ? . InteriorShapeHeight ? ? 1 , typedHull ? . InteriorShapeCells ) ;
}
private Shape GetItemShape ( EquippableItem item )
{
return GetItemShape ( ItemManager . GetRuntimeItem ( item ) ) ;
}
private static Shape GetItemShape ( AetheriaRuntimeCatalogItem typedItem )
{
return ToShape ( typedItem ? . ShapeWidth ? ? 1 , typedItem ? . ShapeHeight ? ? 1 , typedItem ? . ShapeCells ) ;
}
private static AetheriaRuntimeHardpoint GetHardpointAt ( AetheriaRuntimeCatalogItem typedHull , int2 hullCoord )
{
if ( typedHull = = null ) return null ;
foreach ( var hardpoint in typedHull . Hardpoints )
{
var hardpointShape = ToShape ( hardpoint . ShapeWidth , hardpoint . ShapeHeight , hardpoint . ShapeCells ) ;
var localCoord = hullCoord - new int2 ( hardpoint . PositionX , hardpoint . PositionY ) ;
if ( hardpointShape [ localCoord ] )
return hardpoint ;
}
return null ;
}
private static HardpointType GetHardpointType ( AetheriaRuntimeCatalogItem typedItem , HardpointType fallback )
{
return typedItem ! = null & & Enum . TryParse ( typedItem . HardpointType , out HardpointType hardpointType )
? hardpointType
: fallback ;
}
private static HardpointType GetHardpointType ( AetheriaRuntimeHardpoint hardpoint , HardpointType fallback )
{
return hardpoint ! = null & & Enum . TryParse ( hardpoint . Type , out HardpointType hardpointType )
? hardpointType
: fallback ;
}
private static ItemRotation GetRotation ( AetheriaRuntimeHardpoint hardpoint )
{
return hardpoint ! = null & & Enum . TryParse ( hardpoint . Rotation , out ItemRotation rotation )
? rotation
: ItemRotation . None ;
}
2026-06-14 05:08:39 +01:00
private static HardpointData ProjectHardpoint ( AetheriaRuntimeHardpoint hardpoint )
{
return new HardpointData
{
Type = GetHardpointType ( hardpoint , HardpointType . Hull ) ,
Position = new int2 ( hardpoint . PositionX , hardpoint . PositionY ) ,
Shape = ToShape ( hardpoint . ShapeWidth , hardpoint . ShapeHeight , hardpoint . ShapeCells ) ,
Transform = hardpoint . Transform ,
Rotation = GetRotation ( hardpoint ) ,
Armor = ( float ) hardpoint . Armor
} ;
}
2026-06-14 04:56:01 +01:00
private static bool IsCargoBay ( AetheriaRuntimeCatalogItem typedItem )
{
return typedItem ! = null & &
( string . Equals ( typedItem . Category , "CargoBayData" , StringComparison . Ordinal ) | |
string . Equals ( typedItem . Category , "DockingBayData" , StringComparison . Ordinal ) ) ;
}
private static bool IsDockingBay ( AetheriaRuntimeCatalogItem typedItem )
{
return typedItem ! = null & & string . Equals ( typedItem . Category , "DockingBayData" , StringComparison . Ordinal ) ;
}
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 )
shape [ new int2 ( cell . X , cell . Y ) ] = true ;
return shape ;
}
2020-12-10 23:20:38 +00:00
// Check whether the given item will fit when its origin is placed at the given coordinate
2026-06-14 04:56:01 +01:00
private bool ItemFits ( AetheriaRuntimeCatalogItem typedItem , AetheriaRuntimeCatalogItem typedHull , EquippableItem item , int2 hullCoord )
2020-06-05 04:16:33 +01:00
{
2026-06-14 04:56:01 +01:00
var hullShape = GetHullShape ( typedHull ) ;
var itemShape = GetItemShape ( typedItem ) ;
var itemHardpointType = GetHardpointType ( typedItem , HardpointType . Tool ) ;
if ( itemShape . Coordinates . Length = = 0 ) return false ;
2020-12-20 03:04:52 +00:00
// If the given coordinate isn't even in the ship it obviously won't fit
2026-06-14 04:56:01 +01:00
if ( ! hullShape [ hullCoord ] ) return false ;
2020-12-20 03:04:52 +00:00
2020-12-10 23:20:38 +00:00
// Items without specific hardpoints on the ship can be freely rotated and placed anywhere
2026-06-14 04:56:01 +01:00
if ( itemHardpointType = = HardpointType . Tool )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
var hullInterior = GetHullInteriorShape ( typedHull ) ;
2020-12-10 23:20:38 +00:00
// Check every cell of the item's shape
2026-06-14 04:56:01 +01:00
foreach ( var i in itemShape . Coordinates )
2020-12-10 23:20:38 +00:00
{
// If there is any gear already occupying that space, it won't fit
2021-01-05 02:56:17 +00:00
// If there's a hardpoint there, it won't fit
2020-12-10 23:20:38 +00:00
// Thermal items have their own layer and do not collide with gear
2026-06-14 04:56:01 +01:00
var itemCoord = hullCoord + itemShape . Rotate ( i , item . Rotation ) ;
if ( ! hullInterior [ itemCoord ] | |
GetHardpointAt ( typedHull , itemCoord ) ! = null | |
2021-01-17 02:45:52 +00:00
GearOccupancy [ itemCoord . x , itemCoord . y ] ! = null )
return false ;
2020-12-10 23:20:38 +00:00
}
}
else
{
2026-06-14 04:56:01 +01:00
var hardpoint = GetHardpointAt ( typedHull , hullCoord ) ;
2020-12-10 23:20:38 +00:00
// If there's no hardpoint there, it won't fit
if ( hardpoint = = null ) return false ;
// If the hardpoint type doesn't match the item, it won't fit
2026-06-14 04:56:01 +01:00
if ( GetHardpointType ( hardpoint , HardpointType . Hull ) ! = itemHardpointType ) return false ;
2020-12-10 23:20:38 +00:00
2020-12-20 03:04:52 +00:00
// Items placed in hardpoints are automatically aligned to hardpoint rotation
2026-06-14 04:56:01 +01:00
item . Rotation = GetRotation ( hardpoint ) ;
2020-12-20 03:04:52 +00:00
// Inset the shapes of both item and hardpoint
2026-06-14 04:56:01 +01:00
var itemShapeInset = hullShape . Inset ( itemShape , hullCoord , item . Rotation ) ;
var hardpointShapeInset = hullShape . Inset (
ToShape ( hardpoint . ShapeWidth , hardpoint . ShapeHeight , hardpoint . ShapeCells ) ,
new int2 ( hardpoint . PositionX , hardpoint . PositionY ) ) ;
2020-12-20 03:04:52 +00:00
2021-03-18 00:36:34 +00:00
// Check every cell of the hardpoint shape for existing items
2021-01-08 12:44:10 +00:00
foreach ( var v in hardpointShapeInset . Coordinates )
if ( GearOccupancy [ v . x , v . y ] ! = null )
return false ;
2020-12-10 23:20:38 +00:00
// Check every cell of the item's shape
2020-12-20 03:04:52 +00:00
foreach ( var i in itemShapeInset . Coordinates )
2020-12-10 23:20:38 +00:00
{
// If the hardpoint does not have a matching cell, it wont fit
2020-12-20 03:04:52 +00:00
if ( ! hardpointShapeInset [ i ] ) return false ;
2020-12-10 23:20:38 +00:00
// If there is any gear already occupying that space, it won't fit
2020-12-20 03:04:52 +00:00
if ( GearOccupancy [ i . x , i . y ] ! = null ) return false ;
2020-12-10 23:20:38 +00:00
}
}
return true ;
2020-06-05 04:16:33 +01:00
}
2020-12-10 23:20:38 +00:00
// Check whether the given item will fit when its origin is placed at the given coordinate on the hull
public bool ItemFits ( EquippableItem item , int2 hullCoord )
2020-06-05 04:16:33 +01:00
{
2021-03-04 03:58:46 +00:00
// Don't allow equipping while deployed
if ( _active ) return false ;
2026-06-14 04:56:01 +01:00
var typedItem = ItemManager . GetRuntimeItem ( item ) ;
var typedHull = ItemManager . GetRuntimeItem ( Hull ) ;
return typedItem ! = null & & typedHull ! = null & & ItemFits ( typedItem , typedHull , item , hullCoord ) ;
2020-06-05 04:16:33 +01:00
}
2020-12-10 23:20:38 +00:00
public bool TryFindSpace ( EquippableItem item , out int2 hullCoord )
2020-06-05 04:16:33 +01:00
{
2021-03-04 03:58:46 +00:00
// Don't allow equipping while deployed
if ( _active )
{
hullCoord = int2 . zero ;
return false ;
}
2026-06-14 04:56:01 +01:00
var typedItem = ItemManager . GetRuntimeItem ( item ) ;
var typedHull = ItemManager . GetRuntimeItem ( Hull ) ;
if ( typedItem = = null | | typedHull = = null )
{
hullCoord = int2 . zero ;
return false ;
}
var itemHardpointType = GetHardpointType ( typedItem , HardpointType . Tool ) ;
2020-05-11 01:17:10 +01:00
2020-12-10 23:20:38 +00:00
// Tools and thermal equipment can be installed anywhere on the ship
// Search the whole ship for somewhere the item will fit
2026-06-14 04:56:01 +01:00
if ( itemHardpointType = = HardpointType . Tool )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
foreach ( var hullCoord2 in GetHullInteriorShape ( typedHull ) . Coordinates )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
if ( ItemFits ( typedItem , typedHull , item , hullCoord2 ) )
2020-12-10 23:20:38 +00:00
{
hullCoord = hullCoord2 ;
return true ;
}
}
}
2020-05-06 04:56:36 +01:00
2020-12-10 23:20:38 +00:00
// Everything else has to be equipped onto a hardpoint of the same type
// Search the ship for an empty hardpoint that matches the type and shape of the item
else
2020-06-05 04:16:33 +01:00
{
2026-06-14 04:56:01 +01:00
foreach ( var hardpoint in typedHull . Hardpoints )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
if ( GetHardpointType ( hardpoint , HardpointType . Hull ) = = itemHardpointType )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
var hardpointShape = ToShape ( hardpoint . ShapeWidth , hardpoint . ShapeHeight , hardpoint . ShapeCells ) ;
foreach ( var hardpointCoord in hardpointShape . Coordinates )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
var hullCoord2 = new int2 ( hardpoint . PositionX , hardpoint . PositionY ) + hardpointCoord ;
if ( ItemFits ( typedItem , typedHull , item , hullCoord2 ) )
2020-12-10 23:20:38 +00:00
{
hullCoord = hullCoord2 ;
return true ;
}
}
}
}
2020-06-05 04:16:33 +01:00
}
2020-04-28 01:50:50 +01:00
2020-12-10 23:20:38 +00:00
hullCoord = int2 . zero ;
return false ;
2020-04-28 01:50:50 +01:00
}
2020-12-10 23:20:38 +00:00
// Try to equip the given item anywhere it will fit, returns true when the item was successfully equipped
public bool TryEquip ( EquippableItem item ) = > TryFindSpace ( item , out var hullCoord ) & & TryEquip ( item , hullCoord ) ;
// Try to equip the given item to the given location
public bool TryEquip ( EquippableItem item , int2 hullCoord )
2020-06-02 07:01:03 +01:00
{
2021-03-04 03:58:46 +00:00
// Don't allow equipping while deployed
if ( _active ) return false ;
2026-06-14 04:56:01 +01:00
var typedItem = ItemManager . GetRuntimeItem ( item ) ;
var typedHull = ItemManager . GetRuntimeItem ( Hull ) ;
if ( typedItem = = null | | typedHull = = null ) return false ;
var itemHardpointType = GetHardpointType ( typedItem , HardpointType . Tool ) ;
var itemShape = GetItemShape ( typedItem ) ;
2020-12-10 23:20:38 +00:00
2026-06-14 04:56:01 +01:00
if ( ! ItemFits ( typedItem , typedHull , item , hullCoord ) ) return false ;
2020-06-05 04:16:33 +01:00
2021-01-06 14:56:39 +00:00
EquippedItem equippedItem ;
2026-06-14 04:56:01 +01:00
if ( itemHardpointType = = HardpointType . Tool )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
if ( IsCargoBay ( typedItem ) )
2020-12-10 23:20:38 +00:00
{
2026-06-14 04:56:01 +01:00
if ( IsDockingBay ( typedItem ) )
2020-12-19 02:37:13 +00:00
{
2020-12-27 22:18:22 +00:00
equippedItem = new EquippedDockingBay ( ItemManager , item , hullCoord , this , $"{Name} Docking Bay {DockingBays.Count + 1}" ) ;
2020-12-19 02:37:13 +00:00
DockingBays . Add ( ( EquippedDockingBay ) equippedItem ) ;
}
else
{
2020-12-27 22:18:22 +00:00
equippedItem = new EquippedCargoBay ( ItemManager , item , hullCoord , this , $"{Name} Cargo Bay {CargoBays.Count + 1}" ) ;
2020-12-19 02:37:13 +00:00
CargoBays . Add ( ( EquippedCargoBay ) equippedItem ) ;
}
2020-12-10 23:20:38 +00:00
}
else
{
2020-12-27 22:18:22 +00:00
equippedItem = new EquippedItem ( ItemManager , item , hullCoord , this ) ;
2020-12-19 02:37:13 +00:00
Equipment . Add ( equippedItem ) ;
2020-12-10 23:20:38 +00:00
}
}
2020-06-02 07:01:03 +01:00
else
2020-06-05 04:16:33 +01:00
{
2021-01-06 14:56:39 +00:00
equippedItem = new EquippedItem ( ItemManager , item , hullCoord , this ) ;
2020-12-19 02:37:13 +00:00
Equipment . Add ( equippedItem ) ;
2021-01-06 14:56:39 +00:00
}
2021-03-03 01:53:23 +00:00
foreach ( var b in equippedItem . Behaviors )
{
if ( b is Weapon weapon )
_weapons . Add ( weapon ) ;
if ( b is Capacitor capacitor )
_capacitors . Add ( capacitor ) ;
if ( b is Reactor reactor )
_reactors . Add ( reactor ) ;
2021-03-04 03:58:46 +00:00
if ( b is Radiator heatsink )
_heatsinks . Add ( heatsink ) ;
2021-03-06 01:04:26 +00:00
if ( b is Shield shield )
Shield = shield ;
2021-03-06 02:42:19 +00:00
if ( b is Cockpit cockpit )
Cockpit = cockpit ;
2021-03-21 01:55:25 +00:00
if ( b is Sensor sensor )
Sensor = sensor ;
2021-03-03 01:53:23 +00:00
}
2021-03-07 01:10:31 +00:00
// equippedItem.OnOnline += () => ItemOnline.OnNext(equippedItem);
// equippedItem.OnOffline += () => ItemOffline.OnNext(equippedItem);
2020-12-10 23:20:38 +00:00
2026-06-14 04:56:01 +01:00
foreach ( var i in itemShape . Coordinates )
2021-01-06 14:56:39 +00:00
{
2026-06-14 04:56:01 +01:00
var occupiedCoord = hullCoord + itemShape . Rotate ( i , item . Rotation ) ;
2021-01-06 14:56:39 +00:00
// TODO: Track thermal mass of cargo bay contents as reactive property
2026-06-14 04:56:01 +01:00
ThermalMass [ occupiedCoord . x , occupiedCoord . y ] + = ItemManager . GetThermalMass ( item ) / itemShape . Coordinates . Length ;
2021-01-17 02:45:52 +00:00
GearOccupancy [ occupiedCoord . x , occupiedCoord . y ] = equippedItem ;
2020-06-05 04:16:33 +01:00
}
2021-01-06 14:56:39 +00:00
2026-06-14 04:56:01 +01:00
Mass + = ItemManager . GetMass ( item ) ;
2021-01-06 14:56:39 +00:00
_orderedEquipment = Equipment . OrderBy ( x = > x . SortPosition ) . ToArray ( ) ;
return true ;
2020-12-19 02:37:13 +00:00
}
2020-06-05 04:16:33 +01:00
2020-12-19 02:37:13 +00:00
public EquippedDockingBay TryDock ( Ship ship )
{
2025-07-18 00:21:33 +01:00
//if (!IsDockingPermitted(ship.GetFactionRelationship(Faction), CurrentSecurityLevel.Value)) return null;
2021-05-12 00:15:11 +01:00
2020-12-19 02:37:13 +00:00
var bay = DockingBays . FirstOrDefault ( x = > x . DockedShip = = null ) ;
if ( bay ! = null )
{
bay . DockedShip = ship ;
ship . SetParent ( this ) ;
Zone . Entities . Remove ( ship ) ;
2021-03-21 01:55:25 +00:00
ship . Deactivate ( ) ;
2021-02-26 19:01:47 +00:00
ship . Docked . OnNext ( this ) ;
2020-12-19 02:37:13 +00:00
}
return bay ;
}
public bool TryUndock ( Ship ship )
{
var bay = DockingBays . FirstOrDefault ( x = > x . DockedShip = = ship ) ;
if ( bay = = null )
{
ItemManager . Log ( $"Ship {ship.Name} attempted to undock from {Name}, but it was not docked!" ) ;
return false ;
}
if ( bay . Cargo . Any ( ) )
return false ;
bay . DockedShip = null ;
ship . RemoveParent ( ) ;
Zone . Entities . Add ( ship ) ;
2021-02-26 19:01:47 +00:00
ship . Activate ( ) ;
2020-12-19 02:37:13 +00:00
return true ;
2020-06-05 04:16:33 +01:00
}
2021-03-06 01:04:26 +00:00
public bool CanConsumeEnergy ( float energy )
{
var capEnergy = _capacitors . Sum ( cap = > cap . Charge ) ;
2021-03-07 01:10:31 +00:00
int onlineReactors = _reactors . Count ( reactor = > reactor . Item . Online . Value ) ;
2021-03-06 01:04:26 +00:00
return capEnergy > energy | | onlineReactors > 0 ;
}
2021-03-02 23:02:19 +00:00
public bool TryConsumeEnergy ( float energy )
{
if ( energy < . 01f ) return true ;
int chargedCapacitors ;
do
{
chargedCapacitors = _capacitors . Count ( capacitor = > capacitor . Charge > . 01f ) ;
var chargeToRemove = energy ;
foreach ( var cap in _capacitors )
{
if ( cap . Charge > 0.01f )
{
var chargeRemoved = min ( chargeToRemove / chargedCapacitors , cap . Charge ) ;
cap . AddCharge ( - chargeRemoved ) ;
energy - = chargeRemoved ;
}
}
} while ( chargedCapacitors > 0 & & energy > . 01f ) ;
if ( energy < . 01f ) return true ;
2021-03-07 01:10:31 +00:00
int onlineReactors = _reactors . Count ( reactor = > reactor . Item . Online . Value ) ;
2021-03-02 23:02:19 +00:00
foreach ( var reactor in _reactors )
{
2021-03-07 01:10:31 +00:00
if ( reactor . Item . Online . Value )
2021-03-02 23:02:19 +00:00
{
reactor . ConsumeEnergy ( energy / onlineReactors ) ;
}
}
return onlineReactors > 0 ;
}
2021-01-17 02:45:52 +00:00
private void AddChild ( Entity entity )
2020-06-05 04:16:33 +01:00
{
Mass + = entity . Mass ;
2020-12-10 23:20:38 +00:00
Children . Add ( entity ) ;
2020-06-05 04:16:33 +01:00
}
2020-06-02 07:01:03 +01:00
2021-01-17 02:45:52 +00:00
private void RemoveChild ( Entity entity )
2020-06-05 04:16:33 +01:00
{
Mass - = entity . Mass ;
2020-12-10 23:20:38 +00:00
Children . Remove ( entity ) ;
2020-06-02 07:01:03 +01:00
}
2020-12-01 00:16:11 +00:00
public void SetParent ( Entity parent )
{
2020-12-10 23:20:38 +00:00
Parent = parent ;
2020-12-01 00:16:11 +00:00
parent . AddChild ( this ) ;
}
public void RemoveParent ( )
{
2020-12-10 23:20:38 +00:00
if ( Parent = = null )
2020-12-01 00:16:11 +00:00
return ;
2020-12-10 23:20:38 +00:00
Parent . RemoveChild ( this ) ;
Parent = null ;
2020-12-01 00:16:11 +00:00
}
2020-06-02 07:01:03 +01:00
2021-04-18 05:13:20 +01:00
public T GetBehavior < T > ( ) where T : Behavior
2020-06-02 07:01:03 +01:00
{
2020-12-19 02:37:13 +00:00
foreach ( var equippedItem in Equipment )
2020-12-27 22:18:22 +00:00
if ( equippedItem . Behaviors ! = null )
foreach ( var behavior in equippedItem . Behaviors )
if ( behavior is T b )
return b ;
2020-12-10 23:20:38 +00:00
return null ;
}
2020-06-05 04:16:33 +01:00
2021-04-18 05:13:20 +01:00
public IEnumerable < T > GetBehaviors < T > ( ) where T : Behavior
2020-12-10 23:20:38 +00:00
{
2020-12-19 02:37:13 +00:00
foreach ( var equippedItem in Equipment )
2020-12-27 22:18:22 +00:00
if ( equippedItem . Behaviors ! = null )
foreach ( var behavior in equippedItem . Behaviors )
if ( behavior is T b )
yield return b ;
2020-12-10 23:20:38 +00:00
}
2020-06-05 04:16:33 +01:00
2020-12-10 23:20:38 +00:00
public IEnumerable < T > GetBehaviorData < T > ( ) where T : BehaviorData
{
2020-12-19 02:37:13 +00:00
foreach ( var equippedItem in Equipment )
2020-12-10 23:20:38 +00:00
foreach ( var behavior in equippedItem . Behaviors )
if ( behavior . Data is T b )
yield return b ;
}
2021-01-25 05:53:36 +00:00
// public IEnumerable<(T t, Switch s)> GetSwitch<T>() where T : class, IBehavior
// {
// foreach (var equippedItem in Equipment)
// foreach (var group in equippedItem.BehaviorGroups.Values)
// foreach(var behavior in group.Behaviors)
// if (behavior is T t)
// {
// var s = group.GetExposed<Switch>();
// if (s != null) yield return (t, s);
// }
// }
//
// public IEnumerable<(T behavior, Trigger trigger)> GetTrigger<T>() where T : class, IBehavior
// {
// foreach (var equippedItem in Equipment)
// foreach (var group in equippedItem.BehaviorGroups.Values)
// foreach(var behavior in group.Behaviors)
// if (behavior is T t)
// {
// var s = group.GetExposed<Trigger>();
// if (s != null) yield return (t, s);
// }
// }
//
// public IEnumerable<(T behavior, Axis axis)> GetAxis<T>() where T : class, IBehavior
// {
// foreach (var equippedItem in Equipment)
// foreach (var group in equippedItem.BehaviorGroups.Values)
// foreach(var behavior in group.Behaviors)
// if (behavior is T t)
// {
// var s = group.GetExposed<Axis>();
// if (s != null) yield return (t, s);
// }
// }
2020-12-10 23:20:38 +00:00
public virtual void Update ( float delta )
{
2021-02-26 19:01:47 +00:00
TargetRange = Target . Value = = null ? - 1 : length ( Position - Target . Value . Position ) ;
2021-01-25 05:53:36 +00:00
2021-05-11 04:45:42 +01:00
var localSecurityLevel = Zone . GetSecurityLevel ( Position . xz ) ;
if ( CurrentSecurityLevel . Value ! = localSecurityLevel ) CurrentSecurityLevel . Value = localSecurityLevel ;
2021-01-25 05:53:36 +00:00
foreach ( var v in VisibilitySources . Keys . ToArray ( ) )
{
2021-04-12 04:20:52 +01:00
VisibilitySources [ v ] = AetheriaMath . Decay ( VisibilitySources [ v ] , ItemManager . GameplaySettings . VisibilityDecay , delta ) ;
2021-05-12 00:15:11 +01:00
2021-04-12 04:20:52 +01:00
if ( VisibilitySources [ v ] < 0.1f ) VisibilitySources . Remove ( v ) ;
2021-01-25 05:53:36 +00:00
}
2021-02-26 19:01:47 +00:00
UpdateTemperature ( delta ) ;
2021-04-13 01:50:42 +01:00
foreach ( var item in _orderedEquipment ) item . UpdatePerformance ( ) ;
2021-02-26 19:01:47 +00:00
if ( _active )
{
2021-05-12 00:15:11 +01:00
foreach ( var entity in Zone . Entities )
{
var previousHostility = EntityHostility [ entity ] ;
var newHostility = IsHostileTo ( entity ) ;
if ( newHostility ! = previousHostility )
EntityHostility [ entity ] = newHostility ;
}
2021-03-09 02:26:58 +00:00
if ( Cockpit ! = null )
{
2021-04-18 05:13:20 +01:00
var cockpitTemp = Cockpit . Temperature ;
if ( cockpitTemp > ItemManager . GameplaySettings . HeatstrokeTemperature )
2021-03-09 02:26:58 +00:00
{
var previous = Heatstroke ;
Heatstroke = saturate (
Heatstroke +
2021-04-18 05:13:20 +01:00
pow ( cockpitTemp - ItemManager . GameplaySettings . HeatstrokeTemperature , ItemManager . GameplaySettings . HeatstrokeExponent ) *
2021-03-09 02:26:58 +00:00
ItemManager . GameplaySettings . HeatstrokeMultiplier * delta ) ;
if ( previous < ItemManager . GameplaySettings . SevereHeatstrokeRiskThreshold & & Heatstroke > ItemManager . GameplaySettings . SevereHeatstrokeRiskThreshold )
HeatstrokeRisk . OnNext ( Unit . Default ) ;
2021-04-10 01:47:16 +01:00
if ( Heatstroke > . 99 )
2021-03-09 02:26:58 +00:00
{
HeatstrokeDeath . OnNext ( Unit . Default ) ;
Deactivate ( ) ;
}
}
else
{
Heatstroke = saturate ( Heatstroke - ItemManager . GameplaySettings . HeatstrokeRecoverySpeed * delta ) ;
}
2021-04-18 05:13:20 +01:00
if ( cockpitTemp < ItemManager . GameplaySettings . HypothermiaTemperature )
2021-04-10 01:47:16 +01:00
{
var previous = Hypothermia ;
Hypothermia = saturate (
Hypothermia +
2021-04-18 05:13:20 +01:00
pow ( ItemManager . GameplaySettings . HypothermiaTemperature - cockpitTemp , ItemManager . GameplaySettings . HypothermiaExponent ) *
2021-04-10 01:47:16 +01:00
ItemManager . GameplaySettings . HypothermiaMultiplier * delta ) ;
if ( previous < ItemManager . GameplaySettings . SevereHeatstrokeRiskThreshold & & Heatstroke > ItemManager . GameplaySettings . SevereHeatstrokeRiskThreshold )
HypothermiaRisk . OnNext ( Unit . Default ) ;
if ( Hypothermia > . 99 )
{
HypothermiaDeath . OnNext ( Unit . Default ) ;
Deactivate ( ) ;
}
}
else
{
Hypothermia = saturate ( Hypothermia - ItemManager . GameplaySettings . HypothermiaRecoverySpeed * delta ) ;
}
2021-03-09 02:26:58 +00:00
}
2021-04-18 05:13:20 +01:00
for ( var i = 0 ; i < _activeConsumables . Count ; i + + )
{
_activeConsumables [ i ] . Update ( delta ) ;
if ( _activeConsumables [ i ] . RemainingDuration < 0 ) _activeConsumables . RemoveAt ( i - - ) ;
}
2021-02-26 19:01:47 +00:00
foreach ( var equippedItem in _orderedEquipment )
{
equippedItem . Update ( delta ) ;
}
foreach ( var message in Messages . Keys . ToArray ( ) )
{
Messages [ message ] = Messages [ message ] - delta ;
if ( Messages [ message ] < 0 )
Messages . Remove ( message ) ;
}
}
foreach ( var child in Children )
child . Update ( delta ) ;
if ( Parent ! = null )
{
Position = Parent . Position ;
Velocity = Parent . Velocity ;
}
2026-06-14 05:08:39 +01:00
else Position . y = Zone . GetHeight ( Position . xz ) + ( float ) ( ItemManager . GetRuntimeItem ( Hull ) ? . HullGridOffset ? ? 0 ) ;
2021-02-26 19:01:47 +00:00
}
private void UpdateTemperature ( float delta )
{
2026-06-14 05:08:39 +01:00
var typedHull = ItemManager . GetRuntimeItem ( Hull ) ;
var hullShape = GetHullShape ( typedHull ) ;
var hullInterior = GetHullInteriorShape ( typedHull ) ;
var hullConductivity = ( float ) ( typedHull ? . Conductivity ? ? 1 ) ;
2021-01-25 05:53:36 +00:00
2021-01-27 00:05:30 +00:00
MaxTemp = Single . MinValue ;
MinTemp = Single . MaxValue ;
2026-06-14 05:08:39 +01:00
//float[,] newTemp = new float[hullShape.Width,hullShape.Height];
2020-12-27 22:18:22 +00:00
var radiation = 0f ;
2026-06-14 05:08:39 +01:00
foreach ( var v in hullShape . Coordinates )
2020-12-27 22:18:22 +00:00
{
var temp = Temperature [ v . x , v . y ] ;
2021-01-27 00:05:30 +00:00
var totalTemp = temp / ItemManager . GameplaySettings . HeatConductionMultiplier ;
var totalConductivity = 1f / ItemManager . GameplaySettings . HeatConductionMultiplier ;
2020-12-27 22:18:22 +00:00
2026-06-14 05:08:39 +01:00
if ( hullShape [ int2 ( v . x - 1 , v . y ) ] )
2021-01-27 00:05:30 +00:00
{
var conductivity = ( GearOccupancy [ v . x , v . y ] ? . Conductivity ? ? 1 ) *
( GearOccupancy [ v . x - 1 , v . y ] ? . Conductivity ? ? 1 ) *
2026-06-14 05:08:39 +01:00
( HullConductivity [ v . x - 1 , v . y ] . x ? hullConductivity : 1 / hullConductivity ) *
2021-01-27 00:05:30 +00:00
( ThermalMass [ v . x - 1 , v . y ] / ThermalMass [ v . x , v . y ] ) ;
totalConductivity + = conductivity ;
totalTemp + = Temperature [ v . x - 1 , v . y ] * conductivity ;
}
2021-01-17 02:45:52 +00:00
2026-06-14 05:08:39 +01:00
if ( hullShape [ int2 ( v . x + 1 , v . y ) ] )
2021-01-27 00:05:30 +00:00
{
var conductivity = ( GearOccupancy [ v . x , v . y ] ? . Conductivity ? ? 1 ) *
( GearOccupancy [ v . x + 1 , v . y ] ? . Conductivity ? ? 1 ) *
2026-06-14 05:08:39 +01:00
( HullConductivity [ v . x , v . y ] . x ? hullConductivity : 1 / hullConductivity ) *
2021-01-27 00:05:30 +00:00
( ThermalMass [ v . x + 1 , v . y ] / ThermalMass [ v . x , v . y ] ) ;
totalConductivity + = conductivity ;
totalTemp + = Temperature [ v . x + 1 , v . y ] * conductivity ;
}
2026-06-14 05:08:39 +01:00
if ( hullShape [ int2 ( v . x , v . y - 1 ) ] )
2021-01-27 00:05:30 +00:00
{
var conductivity = ( GearOccupancy [ v . x , v . y ] ? . Conductivity ? ? 1 ) *
( GearOccupancy [ v . x , v . y - 1 ] ? . Conductivity ? ? 1 ) *
2026-06-14 05:08:39 +01:00
( HullConductivity [ v . x , v . y - 1 ] . y ? hullConductivity : 1 / hullConductivity ) *
2021-01-27 00:05:30 +00:00
( ThermalMass [ v . x , v . y - 1 ] / ThermalMass [ v . x , v . y ] ) ;
totalConductivity + = conductivity ;
totalTemp + = Temperature [ v . x , v . y - 1 ] * conductivity ;
}
2020-12-27 22:18:22 +00:00
2026-06-14 05:08:39 +01:00
if ( hullShape [ int2 ( v . x , v . y + 1 ) ] )
2021-01-27 00:05:30 +00:00
{
var conductivity = ( GearOccupancy [ v . x , v . y ] ? . Conductivity ? ? 1 ) *
( GearOccupancy [ v . x , v . y + 1 ] ? . Conductivity ? ? 1 ) *
2026-06-14 05:08:39 +01:00
( HullConductivity [ v . x , v . y ] . y ? hullConductivity : 1 / hullConductivity ) *
2021-01-27 00:05:30 +00:00
( ThermalMass [ v . x , v . y + 1 ] / ThermalMass [ v . x , v . y ] ) ;
totalConductivity + = conductivity ;
totalTemp + = Temperature [ v . x , v . y + 1 ] * conductivity ;
}
2020-12-27 22:18:22 +00:00
2021-01-27 00:05:30 +00:00
NewTemperature [ v . x , v . y ] = totalTemp / totalConductivity ;
var r = 0f ;
2020-12-27 22:18:22 +00:00
// For all cells on the border of the entity, radiate some heat into space, increasing the visibility of the ship
2026-06-14 05:08:39 +01:00
if ( Parent = = null & & ! hullInterior [ v ] )
2020-12-27 22:18:22 +00:00
{
2021-03-01 01:53:56 +00:00
var rad = pow ( NewTemperature [ v . x , v . y ] , ItemManager . GameplaySettings . HeatRadiationExponent ) *
ItemManager . GameplaySettings . HeatRadiationMultiplier ;
NewTemperature [ v . x , v . y ] - = rad * delta ;
r + = rad ;
2020-12-27 22:18:22 +00:00
}
2021-01-27 00:05:30 +00:00
radiation + = r ;
2021-01-17 02:45:52 +00:00
2021-01-31 03:52:32 +00:00
if ( float . IsNaN ( NewTemperature [ v . x , v . y ] ) | | NewTemperature [ v . x , v . y ] < 0 )
2021-01-17 02:45:52 +00:00
ItemManager . Log ( "HOUSTON, WE HAVE A PROBLEM!" ) ;
2021-01-27 00:05:30 +00:00
if ( NewTemperature [ v . x , v . y ] < MinTemp )
MinTemp = NewTemperature [ v . x , v . y ] ;
if ( NewTemperature [ v . x , v . y ] > MaxTemp )
MaxTemp = NewTemperature [ v . x , v . y ] ;
2020-12-27 22:18:22 +00:00
}
VisibilitySources [ this ] = radiation ;
2021-01-17 23:26:53 +00:00
var swap = Temperature ;
Temperature = NewTemperature ;
NewTemperature = swap ;
2020-06-02 07:01:03 +01:00
}
2020-12-10 23:20:38 +00:00
public void SetMessage ( string message )
2020-04-28 01:50:50 +01:00
{
2020-12-10 23:20:38 +00:00
Messages [ message ] = ItemManager . GameplaySettings . MessageDuration ;
2020-06-02 07:01:03 +01:00
}
2020-12-10 23:20:38 +00:00
}
2021-04-18 05:13:20 +01:00
public class ConsumableItemEffect
{
public float RemainingDuration { get ; private set ; }
public Entity Entity { get ; }
public ConsumableItem Item { get ; }
public ConsumableItemData Data { get ; }
public Behavior [ ] Behaviors { get ; }
public ConsumableItemEffect ( ConsumableItem item , Entity entity )
{
Item = item ;
Entity = entity ;
2026-06-13 17:38:05 +01:00
Data = entity . ItemManager . GetData ( item ) as ConsumableItemData ;
2021-04-18 05:13:20 +01:00
RemainingDuration = Data . Duration ;
2026-06-14 05:51:15 +01:00
Behaviors = entity . ItemManager . GetRuntimeBehaviorProjections ( item )
2021-04-18 05:13:20 +01:00
. Select ( bd = > bd . CreateInstance ( this ) )
. ToArray ( ) ;
}
public void Update ( float delta )
{
foreach ( var behavior in Behaviors )
if ( behavior is IAlwaysUpdatedBehavior alwaysUpdatedBehavior ) alwaysUpdatedBehavior . Update ( delta ) ;
foreach ( var behavior in Behaviors )
{
if ( ! behavior . Execute ( delta ) )
break ;
}
RemainingDuration - = delta ;
}
public float Evaluate ( PerformanceStat stat )
{
var effectiveness = Data . Effectiveness . Evaluate ( ( Data . Duration - RemainingDuration ) / Data . Duration ) ;
var quality = pow ( Item . Quality , stat . QualityExponent ) ;
var result = lerp ( stat . Min , stat . Max , effectiveness * quality ) ;
if ( float . IsNaN ( result ) )
return stat . Min ;
return result ;
}
}
2020-12-10 23:20:38 +00:00
public class EquippedItem
{
2021-03-07 01:10:31 +00:00
public int SortPosition ;
2021-01-17 02:45:52 +00:00
public EquippableItem EquippableItem ;
public int2 Position ;
2021-02-26 19:01:47 +00:00
2021-03-07 01:10:31 +00:00
private bool _thermalOnline ;
private bool _durabilityOnline ;
2021-04-30 06:39:57 +01:00
public WwiseMetaSoundBank SoundBank ;
2020-12-10 23:20:38 +00:00
2021-04-18 05:13:20 +01:00
public Behavior [ ] Behaviors { get ; }
public Dictionary < int , BehaviorGroup > BehaviorGroups { get ; }
2021-01-17 02:45:52 +00:00
public float Conductivity { get ; }
2026-06-14 06:39:23 +01:00
public float MaxDurability { get ; }
2026-06-14 06:57:53 +01:00
public float ThermalResilience { get ; }
2021-03-07 01:10:31 +00:00
public float ThermalPerformance { get ; private set ; }
public float ThermalExponent { get ; }
public float DurabilityPerformance { get ; private set ; }
public float DurabilityExponent { get ; }
public float Wear { get ; private set ; }
2020-12-27 22:18:22 +00:00
public Shape InsetShape { get ; }
2021-03-07 01:10:31 +00:00
public Entity Entity { get ; }
public EquippableItemData Data { get ; }
2026-06-14 06:52:29 +01:00
public AetheriaRuntimeCatalogItem RuntimeItem { get ; }
2020-12-27 22:18:22 +00:00
2021-03-07 01:10:31 +00:00
public ReactiveProperty < bool > ThermalOnline { get ; } = new ReactiveProperty < bool > ( false ) ;
public ReactiveProperty < bool > DurabilityOnline { get ; } = new ReactiveProperty < bool > ( false ) ;
public ReadOnlyReactiveProperty < bool > Online { get ; }
public ReactiveProperty < bool > Enabled { get ; } = new ReactiveProperty < bool > ( true ) ;
public ReadOnlyReactiveProperty < bool > Active { get ; }
2021-03-16 16:35:23 +00:00
public ItemManager ItemManager { get ; }
2021-01-10 03:59:47 +00:00
2021-04-30 06:39:57 +01:00
public Subject < uint > AudioEvents { get ; } = new Subject < uint > ( ) ;
public Subject < ( uint id , float v ) > AudioParameters { get ; } = new Subject < ( uint id , float v ) > ( ) ;
public Dictionary < uint , float > AudioParameterValues { get ; } = new Dictionary < uint , float > ( ) ;
2026-06-14 06:52:29 +01:00
private readonly BezierCurve _thermalPerformanceCurve ;
2026-06-14 07:08:06 +01:00
private readonly EquippedItemAudioStatBinding [ ] _audioStats ;
2021-06-04 14:49:00 +02:00
private float oldTemperature ;
2020-12-27 22:18:22 +00:00
public float Temperature
{
get
{
float sum = 0 ;
2021-03-07 01:10:31 +00:00
foreach ( var x in InsetShape . Coordinates ) sum + = Entity . Temperature [ x . x , x . y ] ;
2020-12-27 22:18:22 +00:00
return sum / InsetShape . Coordinates . Length ;
}
}
2021-04-30 06:39:57 +01:00
public void FireAudioEvent ( uint eventId , bool skipVerify = false )
{
if ( SoundBank ! = null & & ( skipVerify | | SoundBank . IncludedEvents . Any ( o = > o . Id = = eventId ) ) )
AudioEvents . OnNext ( eventId ) ;
}
public void FireAudioEvent ( WwiseSoundBinding soundBinding )
{
FireAudioEvent ( soundBinding . PlayEvent ) ;
}
public void PlaySound ( WwiseLoopingSoundBinding soundBinding )
{
FireAudioEvent ( soundBinding . PlayEvent ) ;
}
public void StopSound ( WwiseLoopingSoundBinding soundBinding )
{
FireAudioEvent ( soundBinding . StopEvent ) ;
}
public void FireAudioEvent ( WeaponAudioEvent weaponAudioEvent )
{
if ( SoundBank = = null ) return ;
var eventObject = SoundBank . GetEvent ( weaponAudioEvent ) ;
if ( eventObject = = null )
{
ItemManager . Log ( $"Attempted to trigger {Enum.GetName(typeof(WeaponAudioEvent), weaponAudioEvent)} weapon audio event, but the soundbank doesn't support it!" ) ;
return ;
}
FireAudioEvent ( eventObject . Id , true ) ;
}
public void FireAudioEvent ( ChargedWeaponAudioEvent weaponAudioEvent )
{
if ( SoundBank = = null ) return ;
var eventObject = SoundBank . GetEvent ( weaponAudioEvent ) ;
if ( eventObject = = null )
{
ItemManager . Log ( $"Attempted to trigger {Enum.GetName(typeof(ChargedWeaponAudioEvent), weaponAudioEvent)} weapon audio event, but the soundbank doesn't support it!" ) ;
return ;
}
FireAudioEvent ( eventObject . Id , true ) ;
}
public void SetAudioParameter ( uint id , float v , bool skipVerify = false )
{
if ( SoundBank ! = null & & ( skipVerify | | SoundBank . GameParameters . Any ( o = > o . Id = = id ) ) )
{
AudioParameterValues [ id ] = v ;
AudioParameters . OnNext ( ( id , v ) ) ;
}
}
// public void SetAudioParameter(WwiseParameterBinding binding)
// {
// FireAudioEvent(binding.Parameter);
// }
public void SetAudioParameter ( SpecialAudioParameter p , float v )
{
if ( SoundBank = = null ) return ;
var metaObject = SoundBank . GetParameter ( p ) ;
if ( metaObject = = null )
{
ItemManager . Log ( $"Attempted to set {Enum.GetName(typeof(ChargedWeaponAudioEvent), p)} audio parameter, but the soundbank doesn't support it!" ) ;
return ;
}
SetAudioParameter ( metaObject . Id , v , true ) ;
}
2020-12-10 23:20:38 +00:00
2020-12-27 22:18:22 +00:00
public EquippedItem ( ItemManager itemManager , EquippableItem item , int2 position , Entity entity )
2020-04-09 10:33:16 +01:00
{
2021-03-16 16:35:23 +00:00
ItemManager = itemManager ;
Data = ItemManager . GetData ( item ) ;
2021-03-07 01:10:31 +00:00
Entity = entity ;
2020-12-10 23:20:38 +00:00
EquippableItem = item ;
Position = position ;
2026-06-14 06:52:29 +01:00
RuntimeItem = ItemManager . GetRuntimeItem ( item ) ;
Conductivity = RuntimeItem ! = null ? ( float ) RuntimeItem . Conductivity : Data . Conductivity ;
MaxDurability = RuntimeItem ? . Durability > 0 ? ( float ) RuntimeItem . Durability : Math . Max ( item . Durability , 1f ) ;
2026-06-14 06:57:53 +01:00
ThermalResilience = RuntimeItem ? . ThermalResilience > 0 ? ( float ) RuntimeItem . ThermalResilience : Data . ThermalResilience ;
2026-06-14 06:52:29 +01:00
_thermalPerformanceCurve = CreateThermalPerformanceCurve ( RuntimeItem ) ;
2026-06-14 07:08:06 +01:00
_audioStats = CreateAudioStatBindings ( RuntimeItem , Data ) ;
2021-03-22 05:22:31 +00:00
ThermalExponent = lerp (
ItemManager . GameplaySettings . ThermalQualityMin ,
ItemManager . GameplaySettings . ThermalQualityMax ,
pow ( item . Quality , ItemManager . GameplaySettings . ThermalQualityExponent ) ) ;
DurabilityExponent = lerp (
ItemManager . GameplaySettings . DurabilityQualityMin ,
ItemManager . GameplaySettings . DurabilityQualityMax ,
pow ( item . Quality , ItemManager . GameplaySettings . DurabilityQualityExponent ) ) ;
2026-06-14 05:44:27 +01:00
var hullShape = ItemManager . GetRuntimeShape ( entity . Hull ) ;
var itemShape = ItemManager . GetRuntimeShape ( item ) ;
InsetShape = hullShape . Inset ( itemShape , position , item . Rotation ) ;
2021-06-04 14:49:00 +02:00
if ( Entity . Temperature ! = null ) oldTemperature = Temperature ;
2020-12-31 19:53:16 +00:00
2021-03-07 01:10:31 +00:00
Online = new ReadOnlyReactiveProperty < bool > ( ThermalOnline
. CombineLatest ( DurabilityOnline , ( thermal , durability ) = > thermal & & durability ) . DistinctUntilChanged ( ) ) ;
Active = new ReadOnlyReactiveProperty < bool > ( Enabled
. CombineLatest ( Online , ( enabled , online ) = > enabled & & online ) . DistinctUntilChanged ( ) ) ;
2026-06-14 05:51:15 +01:00
Behaviors = ItemManager . GetRuntimeBehaviorProjections ( item )
2021-04-12 04:20:52 +01:00
. Select ( bd = > bd . CreateInstance ( this ) )
2020-12-31 19:53:16 +00:00
. ToArray ( ) ;
2021-01-25 05:53:36 +00:00
2020-12-31 19:53:16 +00:00
BehaviorGroups = Behaviors
. GroupBy ( b = > b . Data . Group )
2021-01-25 05:53:36 +00:00
. OrderBy ( g = > g . Key )
. ToDictionary ( g = > g . Key , g = > new BehaviorGroup
{
2021-03-07 01:10:31 +00:00
Behaviors = g . ToArray ( )
2021-01-25 05:53:36 +00:00
} ) ;
2020-12-31 19:53:16 +00:00
foreach ( var behavior in Behaviors )
{
if ( behavior is IOrderedBehavior orderedBehavior )
SortPosition = orderedBehavior . Order ;
if ( behavior is IPopulationAssignment populationAssignment )
entity . PopulationAssignments . Add ( populationAssignment ) ;
}
2020-12-27 22:18:22 +00:00
}
2021-03-07 01:10:31 +00:00
public float Evaluate ( PerformanceStat stat )
{
2021-03-22 05:22:31 +00:00
var heat = pow ( ThermalPerformance , ThermalExponent * stat . HeatExponentMultiplier ) ;
var durability = pow ( DurabilityPerformance , DurabilityExponent * stat . DurabilityExponentMultiplier ) ;
var quality = pow ( EquippableItem . Quality , stat . QualityExponent ) ;
2021-03-07 01:10:31 +00:00
var scaleModifier = 1.0f ;
2021-04-21 01:49:07 +01:00
var scaleModifiers = stat . GetScaleModifiers ( Entity ) . Values ;
foreach ( var value in scaleModifiers ) scaleModifier * = value ;
2021-03-07 01:10:31 +00:00
float constantModifier = 0 ;
foreach ( var value in stat . GetConstantModifiers ( Entity ) . Values ) constantModifier + = value ;
var result = lerp ( stat . Min , stat . Max , durability * quality * heat ) * scaleModifier + constantModifier ;
if ( float . IsNaN ( result ) )
return stat . Min ;
return result ;
}
2021-02-01 08:13:14 +00:00
public void AddHeat ( float heat , bool ignoreThermalMass = false )
2020-12-27 22:18:22 +00:00
{
foreach ( var hullCoord in InsetShape . Coordinates )
2021-03-07 01:10:31 +00:00
Entity . AddHeat ( hullCoord , heat / InsetShape . Coordinates . Length , ignoreThermalMass ) ;
2020-04-09 10:33:16 +01:00
}
2021-04-13 01:50:42 +01:00
public void UpdatePerformance ( )
2021-06-04 14:49:00 +02:00
{
2021-03-07 01:10:31 +00:00
var temp = Temperature ;
2026-06-14 06:52:29 +01:00
ThermalPerformance = EvaluateThermalPerformance ( temp ) ;
2021-06-04 14:49:00 +02:00
var deltaTemp = math . abs ( temp - oldTemperature ) ;
2026-06-14 06:39:23 +01:00
DurabilityPerformance = EquippableItem . Durability / MaxDurability ;
2021-03-14 03:56:49 +00:00
var performanceThreshold = Entity . Settings . ShutdownPerformance ;
2021-04-22 02:53:06 +01:00
Wear = ( 1 - pow ( ThermalPerformance ,
2021-03-22 05:22:31 +00:00
( 1 - pow ( EquippableItem . Quality , ItemManager . GameplaySettings . QualityWearExponent ) ) *
2021-06-04 14:49:00 +02:00
ItemManager . GameplaySettings . ThermalWearExponent ) +
deltaTemp * ItemManager . GameplaySettings . DeltaTempWearExponent
2026-06-14 06:57:53 +01:00
) * MaxDurability / ThermalResilience ;
2021-03-07 01:10:31 +00:00
ThermalOnline . Value = ThermalPerformance > performanceThreshold | | Entity . OverrideShutdown & & EquippableItem . OverrideShutdown ;
DurabilityOnline . Value = EquippableItem . Durability > . 01f ;
2021-06-04 14:49:00 +02:00
oldTemperature = temp ;
2021-04-13 01:50:42 +01:00
}
2026-06-14 06:52:29 +01:00
private float EvaluateThermalPerformance ( float temperature )
{
if ( _thermalPerformanceCurve = = null | |
RuntimeItem = = null | |
RuntimeItem . MaximumTemperature < = RuntimeItem . MinimumTemperature )
{
return Data . Performance ( temperature ) ;
}
var t = unlerp ( ( float ) RuntimeItem . MinimumTemperature , ( float ) RuntimeItem . MaximumTemperature , temperature ) ;
return saturate ( _thermalPerformanceCurve . Evaluate ( t ) ) ;
}
private static BezierCurve CreateThermalPerformanceCurve ( AetheriaRuntimeCatalogItem item )
{
if ( item ? . ThermalPerformanceCurveKeys = = null | | item . ThermalPerformanceCurveKeys . Count = = 0 )
{
return null ;
}
return new BezierCurve
{
Keys = item . ThermalPerformanceCurveKeys
. Select ( key = > new float4 ( ( float ) key . Time , ( float ) key . Value , ( float ) key . InTangent , ( float ) key . OutTangent ) )
. ToArray ( )
} ;
}
2021-04-13 01:50:42 +01:00
public void Update ( float delta )
{
2026-06-14 07:08:06 +01:00
foreach ( var audioStat in _audioStats )
2021-04-30 06:39:57 +01:00
{
SetAudioParameter ( audioStat . Parameter , Evaluate ( audioStat . Stat ) ) ;
}
2021-03-07 01:10:31 +00:00
if ( Active . Value )
2020-12-10 23:20:38 +00:00
{
2021-01-25 05:53:36 +00:00
foreach ( var group in BehaviorGroups . Values )
2020-12-10 23:20:38 +00:00
{
2021-01-10 03:59:47 +00:00
foreach ( var behavior in group . Behaviors )
{
if ( ! behavior . Execute ( delta ) )
break ;
}
2020-12-10 23:20:38 +00:00
}
}
2021-04-21 01:49:07 +01:00
foreach ( var behavior in Behaviors )
if ( behavior is IAlwaysUpdatedBehavior alwaysUpdatedBehavior ) alwaysUpdatedBehavior . Update ( delta ) ;
2021-03-02 23:02:19 +00:00
}
2021-04-21 01:49:07 +01:00
public T GetBehavior < T > ( ) where T : class
2021-03-02 23:02:19 +00:00
{
foreach ( var behavior in Behaviors )
if ( behavior is T b )
return b ;
return null ;
2020-05-11 01:17:10 +01:00
}
2026-06-14 07:08:06 +01:00
private static EquippedItemAudioStatBinding [ ] CreateAudioStatBindings (
AetheriaRuntimeCatalogItem item ,
EquippableItemData fallback )
{
if ( item ? . AudioStats ! = null )
{
return item . AudioStats
. Select ( audioStat = > new EquippedItemAudioStatBinding (
audioStat . Parameter ,
CreatePerformanceStat ( audioStat . Stat ) ) )
. ToArray ( ) ;
}
return fallback . AudioStats
. Select ( audioStat = > new EquippedItemAudioStatBinding ( audioStat . Parameter , audioStat . Stat ) )
. ToArray ( ) ;
}
private static PerformanceStat CreatePerformanceStat ( 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 readonly struct EquippedItemAudioStatBinding
{
public EquippedItemAudioStatBinding ( uint parameter , PerformanceStat stat )
{
Parameter = parameter ;
Stat = stat ;
}
public uint Parameter { get ; }
public PerformanceStat Stat { get ; }
}
2020-12-10 23:20:38 +00:00
}
2020-05-11 01:17:10 +01:00
2020-12-10 23:20:38 +00:00
public class EquippedCargoBay : EquippedItem
{
public readonly ReactiveDictionary < ItemInstance , int2 > Cargo = new ReactiveDictionary < ItemInstance , int2 > ( ) ;
public readonly ItemInstance [ , ] Occupancy ;
public readonly Dictionary < Guid , List < ItemInstance > > ItemsOfType = new Dictionary < Guid , List < ItemInstance > > ( ) ;
2020-12-14 02:56:35 +00:00
2026-06-14 04:47:02 +01:00
public Shape InteriorShape { get ; }
2020-12-10 23:20:38 +00:00
public float Mass { get ; private set ; }
public float ThermalMass { get ; private set ; }
2020-12-19 02:37:13 +00:00
public string Name { get ; }
2021-03-20 04:20:17 +00:00
public Shape UnoccupiedSpace
{
get
{
2026-06-14 04:47:02 +01:00
var unoccupied = new Shape ( InteriorShape . Width , InteriorShape . Height ) ;
2021-03-20 04:20:17 +00:00
foreach ( var v in unoccupied . AllCoordinates )
unoccupied [ v ] = Occupancy [ v . x , v . y ] = = null ;
return unoccupied ;
}
}
2020-12-27 22:18:22 +00:00
public EquippedCargoBay ( ItemManager itemManager , EquippableItem item , int2 position , Entity entity , string name ) : base ( itemManager , item , position , entity )
2020-05-11 01:17:10 +01:00
{
2026-06-14 04:47:02 +01:00
var typedCargoBay = ItemManager . GetRuntimeItem ( EquippableItem ) ;
InteriorShape = ToShape (
typedCargoBay ? . InteriorShapeWidth ? ? 1 ,
typedCargoBay ? . InteriorShapeHeight ? ? 1 ,
typedCargoBay ? . InteriorShapeCells ) ;
2020-12-19 02:37:13 +00:00
Name = name ;
2020-12-10 23:20:38 +00:00
2026-06-14 04:47:02 +01:00
Mass = ItemManager . GetMass ( EquippableItem ) ;
ThermalMass = ItemManager . GetThermalMass ( EquippableItem ) ;
2020-12-10 23:20:38 +00:00
2026-06-14 04:47:02 +01:00
Occupancy = new ItemInstance [ InteriorShape . Width , InteriorShape . Height ] ;
2020-05-11 01:17:10 +01:00
}
2020-12-10 23:20:38 +00:00
// Check whether the given item will fit when its origin is placed at the given coordinate
2020-12-20 03:04:52 +00:00
public bool ItemFits ( ItemInstance item , int2 cargoCoord )
2020-05-28 04:50:40 +01:00
{
2026-06-14 04:47:02 +01:00
var itemShape = GetItemShape ( item ) ;
2020-12-10 23:20:38 +00:00
// Check every cell of the item's shape
2026-06-14 04:47:02 +01:00
foreach ( var i in itemShape . Coordinates )
2020-12-10 23:20:38 +00:00
{
// If there is an item already occupying that space, it won't fit
2026-06-14 04:47:02 +01:00
var itemCargoCoord = cargoCoord + itemShape . Rotate ( i , item . Rotation ) ;
if ( ! InteriorShape [ itemCargoCoord ] | | ( Occupancy [ itemCargoCoord . x , itemCargoCoord . y ] ! = null & & Occupancy [ itemCargoCoord . x , itemCargoCoord . y ] ! = item ) ) return false ;
2020-12-10 23:20:38 +00:00
}
return true ;
2020-05-28 04:50:40 +01:00
}
2021-03-04 03:58:46 +00:00
public bool TryFindSpace ( ItemInstance item )
{
if ( item is SimpleCommodity simpleCommodity )
return TryFindSpace ( simpleCommodity , out _ ) ;
if ( item is CraftedItemInstance craftedItem )
return TryFindSpace ( craftedItem , out _ ) ;
return false ;
}
2020-05-28 04:50:40 +01:00
2020-12-10 23:20:38 +00:00
// Tries to find a place to put the given items in the inventory
// Will attempt to fill existing item stacks first
// Returns true only when ALL of the items have places to go
public bool TryFindSpace ( SimpleCommodity item , out List < int2 > positions )
2020-05-28 04:50:40 +01:00
{
2020-12-10 23:20:38 +00:00
positions = new List < int2 > ( ) ;
2026-06-14 04:47:02 +01:00
var maxStack = GetMaxStack ( item ) ;
2020-12-10 23:20:38 +00:00
var remainingQuantity = item . Quantity ;
// For simple commodities, search for existing item stacks to add to
foreach ( var cargoItem in Cargo . Keys )
{
if ( item . Data ! = cargoItem . Data ) continue ;
var cargoCommodity = ( SimpleCommodity ) cargoItem ;
2026-06-14 04:47:02 +01:00
if ( cargoCommodity . Quantity > = maxStack ) continue ;
2020-12-10 23:20:38 +00:00
// Subtract remaining space in existing stack from remaining quantity
2026-06-14 04:47:02 +01:00
remainingQuantity - = min ( maxStack - cargoCommodity . Quantity , remainingQuantity ) ;
2020-12-10 23:20:38 +00:00
positions . Add ( Cargo [ cargoItem ] ) ;
// If we've moved all of the items into existing stacks, no need to search for empty space!
if ( remainingQuantity = = 0 ) return true ;
}
2021-03-20 04:20:17 +00:00
// TODO: Try alternate item rotations / use Shape.FitsWithin
2020-12-10 23:20:38 +00:00
// Search all the space in the cargo bay for an empty space where the item fits
2026-06-14 04:47:02 +01:00
foreach ( var cargoCoord in InteriorShape . Coordinates )
2020-12-10 23:20:38 +00:00
{
if ( ItemFits ( item , cargoCoord ) )
{
positions . Add ( cargoCoord ) ;
return true ;
}
}
return false ;
2020-05-28 04:50:40 +01:00
}
2021-03-04 03:58:46 +00:00
// Searches the cargo bay for a position where the item will fit, returns true when found
public bool TryFindSpace ( CraftedItemInstance item , out int2 position )
{
// Search all the space in the cargo bay for an empty space where the item fits
2026-06-14 04:47:02 +01:00
foreach ( var cargoCoord in InteriorShape . Coordinates )
2021-03-04 03:58:46 +00:00
{
if ( ItemFits ( item , cargoCoord ) )
{
position = cargoCoord ;
return true ;
}
}
position = int2 . zero ;
return false ;
}
2021-01-27 16:14:41 +00:00
public bool TryStore ( ItemInstance item )
{
if ( item is SimpleCommodity simpleCommodity )
return TryStore ( simpleCommodity ) ;
if ( item is CraftedItemInstance craftedItem )
return TryStore ( craftedItem ) ;
return false ;
}
2020-05-28 04:50:40 +01:00
2020-12-20 03:04:52 +00:00
public bool TryStore ( ItemInstance item , int2 cargoCoord )
{
if ( item is SimpleCommodity simpleCommodity )
return TryStore ( simpleCommodity , cargoCoord ) ;
if ( item is CraftedItemInstance craftedItem )
return TryStore ( craftedItem , cargoCoord ) ;
return false ;
}
2020-12-10 23:20:38 +00:00
// Attempts to store all of the given item anywhere in the inventory
// Will attempt to fill existing item stacks first
// Returns true only when ALL of the items are successfully stored
public bool TryStore ( SimpleCommodity item )
2020-04-09 10:33:16 +01:00
{
2020-12-10 23:20:38 +00:00
TryFindSpace ( item , out var positions ) ;
foreach ( var position in positions )
2020-05-26 01:12:06 +01:00
{
2020-12-10 23:20:38 +00:00
if ( TryStore ( item , position ) ) return true ;
2020-05-26 01:12:06 +01:00
}
2020-12-10 23:20:38 +00:00
return false ;
2020-04-09 10:33:16 +01:00
}
2020-12-10 23:20:38 +00:00
// Try to store the given commodity at the given position
// If there's a stack at the given position it will be added to
// Returns true only when ALL of the items are successfully stored
public bool TryStore ( SimpleCommodity item , int2 cargoCoord )
2020-04-09 10:33:16 +01:00
{
2026-06-14 04:47:02 +01:00
var itemShape = GetItemShape ( item ) ;
var maxStack = GetMaxStack ( item ) ;
2020-12-10 23:20:38 +00:00
if ( ItemFits ( item , cargoCoord ) )
2020-06-05 04:16:33 +01:00
{
2026-06-14 04:47:02 +01:00
foreach ( var p in itemShape . Coordinates )
2020-06-12 01:57:15 +01:00
{
2026-06-14 04:47:02 +01:00
var pos = cargoCoord + itemShape . Rotate ( p , item . Rotation ) ;
2020-12-10 23:20:38 +00:00
Occupancy [ pos . x , pos . y ] = item ;
}
Cargo [ item ] = cargoCoord ;
2020-06-05 04:16:33 +01:00
2026-06-13 20:02:41 +01:00
if ( ! ItemsOfType . ContainsKey ( item . Data . ItemId ) )
ItemsOfType [ item . Data . ItemId ] = new List < ItemInstance > ( ) ;
ItemsOfType [ item . Data . ItemId ] . Add ( item ) ;
2020-12-10 23:20:38 +00:00
}
else if ( Occupancy [ cargoCoord . x , cargoCoord . y ] is SimpleCommodity cargoCommodity & & cargoCommodity . Data = = item . Data )
{
2026-06-14 04:47:02 +01:00
if ( cargoCommodity . Quantity + item . Quantity < = maxStack )
2020-12-10 23:20:38 +00:00
{
cargoCommodity . Quantity + = item . Quantity ;
2020-06-12 01:57:15 +01:00
}
2020-12-10 23:20:38 +00:00
else
2020-06-12 01:57:15 +01:00
{
2026-06-14 04:47:02 +01:00
var quantityTransferred = maxStack - cargoCommodity . Quantity ;
2020-12-10 23:20:38 +00:00
item . Quantity - = quantityTransferred ;
2026-06-14 04:47:02 +01:00
cargoCommodity . Quantity = maxStack ;
2020-12-10 23:20:38 +00:00
2026-06-14 04:47:02 +01:00
Mass + = GetUnitMass ( item ) * quantityTransferred ;
ThermalMass + = GetUnitThermalMass ( item ) * quantityTransferred ;
2020-12-10 23:20:38 +00:00
return false ;
2020-06-12 01:57:15 +01:00
}
2020-12-10 23:20:38 +00:00
}
else return false ;
2021-03-16 16:35:23 +00:00
Mass + = ItemManager . GetMass ( item ) ;
ThermalMass + = ItemManager . GetThermalMass ( item ) ;
2020-12-10 23:20:38 +00:00
return true ;
}
2020-06-12 01:57:15 +01:00
2020-12-10 23:20:38 +00:00
// Try to store the given item anywhere it will fit, returns true when the item was successfully stored
public bool TryStore ( CraftedItemInstance item ) = > TryFindSpace ( item , out var position ) & & TryStore ( item , position ) ;
// Try to store the given item at the given position, returns true when the item was successfully stored
public bool TryStore ( CraftedItemInstance item , int2 cargoCoord )
{
if ( ! ItemFits ( item , cargoCoord ) ) return false ;
2026-06-14 04:47:02 +01:00
var itemShape = GetItemShape ( item ) ;
foreach ( var p in itemShape . Coordinates )
2020-05-04 00:07:36 +01:00
{
2026-06-14 04:47:02 +01:00
var pos = cargoCoord + itemShape . Rotate ( p , item . Rotation ) ;
2020-12-10 23:20:38 +00:00
Occupancy [ pos . x , pos . y ] = item ;
2020-05-04 00:07:36 +01:00
}
2020-12-10 23:20:38 +00:00
Cargo [ item ] = cargoCoord ;
2026-06-13 20:02:41 +01:00
if ( ! ItemsOfType . ContainsKey ( item . Data . ItemId ) )
ItemsOfType [ item . Data . ItemId ] = new List < ItemInstance > ( ) ;
ItemsOfType [ item . Data . ItemId ] . Add ( item ) ;
2020-12-10 23:20:38 +00:00
2021-03-16 16:35:23 +00:00
Mass + = ItemManager . GetMass ( item ) ;
ThermalMass + = ItemManager . GetThermalMass ( item ) ;
2020-12-10 23:20:38 +00:00
return true ;
}
2020-05-11 01:17:10 +01:00
2020-12-10 23:20:38 +00:00
public SimpleCommodity Remove ( SimpleCommodity item , int quantity )
{
if ( ! Cargo . ContainsKey ( item ) )
2020-06-06 03:21:03 +01:00
{
2021-03-16 16:35:23 +00:00
ItemManager . Log ( "Attempted to remove item from a cargo bay that it wasn't even in! Something went wrong here!" ) ;
2020-12-10 23:20:38 +00:00
return null ;
2020-06-06 03:21:03 +01:00
}
2020-12-10 23:20:38 +00:00
if ( quantity > = item . Quantity )
2020-06-06 03:21:03 +01:00
{
2026-06-14 04:47:02 +01:00
foreach ( var v in InteriorShape . Coordinates )
2020-12-20 03:04:52 +00:00
if ( Occupancy [ v . x , v . y ] = = item )
Occupancy [ v . x , v . y ] = null ;
2020-12-10 23:20:38 +00:00
Cargo . Remove ( item ) ;
2026-06-13 20:02:41 +01:00
ItemsOfType [ item . Data . ItemId ] . Remove ( item ) ;
if ( ! ItemsOfType [ item . Data . ItemId ] . Any ( ) )
ItemsOfType . Remove ( item . Data . ItemId ) ;
2020-12-10 23:20:38 +00:00
2021-03-16 16:35:23 +00:00
Mass - = ItemManager . GetMass ( item ) ;
ThermalMass - = ItemManager . GetThermalMass ( item ) ;
2020-12-10 23:20:38 +00:00
return item ;
2020-06-06 03:21:03 +01:00
}
2020-04-29 23:36:00 +01:00
2020-12-10 23:20:38 +00:00
item . Quantity - = quantity ;
2026-06-14 04:47:02 +01:00
Mass - = GetUnitMass ( item ) * quantity ;
ThermalMass - = GetUnitThermalMass ( item ) * quantity ;
2020-12-10 23:20:38 +00:00
return new SimpleCommodity { Data = item . Data , Quantity = quantity , Rotation = item . Rotation } ;
2020-05-29 23:06:35 +01:00
}
2020-12-10 23:20:38 +00:00
public void Remove ( CraftedItemInstance item )
2020-04-29 23:36:00 +01:00
{
2020-12-10 23:20:38 +00:00
if ( ! Cargo . ContainsKey ( item ) )
{
2021-03-16 16:35:23 +00:00
ItemManager . Log ( "Attempted to remove item from a cargo bay that it wasn't even in! Something went wrong here!" ) ;
2020-12-10 23:20:38 +00:00
return ;
}
2026-06-14 04:47:02 +01:00
foreach ( var v in InteriorShape . Coordinates )
2020-12-20 03:04:52 +00:00
if ( Occupancy [ v . x , v . y ] = = item )
Occupancy [ v . x , v . y ] = null ;
2020-12-10 23:20:38 +00:00
Cargo . Remove ( item ) ;
2026-06-13 20:02:41 +01:00
ItemsOfType [ item . Data . ItemId ] . Remove ( item ) ;
if ( ! ItemsOfType [ item . Data . ItemId ] . Any ( ) )
ItemsOfType . Remove ( item . Data . ItemId ) ;
2020-12-10 23:20:38 +00:00
2021-03-16 16:35:23 +00:00
Mass - = ItemManager . GetMass ( item ) ;
ThermalMass - = ItemManager . GetThermalMass ( item ) ;
2020-04-29 23:36:00 +01:00
}
2020-12-20 03:04:52 +00:00
public void Remove ( ItemInstance item )
{
if ( item is SimpleCommodity simpleCommodity )
Remove ( simpleCommodity , simpleCommodity . Quantity ) ;
if ( item is CraftedItemInstance craftedItem )
Remove ( craftedItem ) ;
}
2026-06-14 04:47:02 +01:00
private Shape GetItemShape ( ItemInstance item )
{
var typedItem = ItemManager . GetRuntimeItem ( item ) ;
return ToShape ( typedItem ? . ShapeWidth ? ? 1 , typedItem ? . ShapeHeight ? ? 1 , typedItem ? . ShapeCells ) ;
}
private int GetMaxStack ( SimpleCommodity item )
{
var typedItem = ItemManager . GetRuntimeItem ( item ) ;
return typedItem ! = null & & typedItem . MaxStack > 0 ? typedItem . MaxStack : 1 ;
}
private float GetUnitMass ( ItemInstance item )
{
var typedItem = ItemManager . GetRuntimeItem ( item ) ;
return typedItem ! = null ? ( float ) typedItem . Mass : 0f ;
}
private float GetUnitThermalMass ( ItemInstance item )
{
var typedItem = ItemManager . GetRuntimeItem ( item ) ;
return typedItem ! = null ? ( float ) ( typedItem . Mass * typedItem . SpecificHeat ) : 0f ;
}
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 )
shape [ new int2 ( cell . X , cell . Y ) ] = true ;
return shape ;
}
2020-12-10 23:20:38 +00:00
public bool TryTransferItem ( EquippedCargoBay target , SimpleCommodity item , int quantity )
2020-04-29 23:36:00 +01:00
{
2020-12-10 23:20:38 +00:00
if ( ! Cargo . ContainsKey ( item ) )
{
2021-03-16 16:35:23 +00:00
ItemManager . Log ( "Attempted to remove item from a cargo bay that it wasn't even in! Something went wrong here!" ) ;
2020-12-10 23:20:38 +00:00
return false ;
}
2020-04-30 02:54:31 +01:00
2020-12-10 23:20:38 +00:00
var oldPos = Cargo [ item ] ;
var newItem = Remove ( item , quantity ) ;
if ( target . TryStore ( item ) ) return true ;
// Failed to transfer full quantity, move the remaining items back to their old slot
TryStore ( newItem , oldPos ) ;
return false ;
}
public bool TryTransferItem ( EquippedCargoBay target , CraftedItemInstance item )
{
if ( ! Cargo . ContainsKey ( item ) )
{
2021-03-16 16:35:23 +00:00
ItemManager . Log ( "Attempted to remove item from a cargo bay that it wasn't even in! Something went wrong here!" ) ;
2020-12-10 23:20:38 +00:00
return false ;
}
if ( ! target . TryStore ( item ) ) return false ;
Remove ( item ) ;
return true ;
2020-04-29 23:36:00 +01:00
}
2020-06-02 07:01:03 +01:00
}
2020-12-19 02:37:13 +00:00
public class EquippedDockingBay : EquippedCargoBay
{
public Ship DockedShip ;
2026-06-14 05:39:15 +01:00
public int2 MaxSize { get ; }
2020-12-27 22:18:22 +00:00
public EquippedDockingBay ( ItemManager itemManager , EquippableItem item , int2 position , Entity entity , string name ) : base ( itemManager , item , position , entity , name )
2020-12-19 02:37:13 +00:00
{
2026-06-14 05:39:15 +01:00
var typedDockingBay = ItemManager . GetRuntimeItem ( EquippableItem ) ;
MaxSize = typedDockingBay ! = null
? new int2 ( typedDockingBay . DockingMaxSizeX , typedDockingBay . DockingMaxSizeY )
: int2 . zero ;
2020-12-19 02:37:13 +00:00
}
}
2020-06-05 04:16:33 +01:00
public class BehaviorGroup
2020-06-02 07:01:03 +01:00
{
2021-04-18 05:13:20 +01:00
public Behavior [ ] Behaviors ;
2021-01-25 05:53:36 +00:00
2021-04-18 05:13:20 +01:00
public T GetBehavior < T > ( ) where T : Behavior
2021-01-25 05:53:36 +00:00
{
foreach ( var b in Behaviors )
{
if ( ! ( b is T s ) ) continue ;
return s ;
}
return null ;
}
2021-04-18 05:13:20 +01:00
public T GetExposed < T > ( ) where T : Behavior , IInteractiveBehavior
2021-01-25 05:53:36 +00:00
{
foreach ( var b in Behaviors )
{
if ( ! ( b is T s ) | | ! ( ( IInteractiveBehavior ) b ) . Exposed ) continue ;
return s ;
}
return null ;
}
2020-06-05 04:16:33 +01:00
//public IAnalogBehavior Axis;
2026-06-13 17:38:05 +01:00
}