Sync daemon authority migration

This commit is contained in:
Meta Vens 2026-06-21 09:37:54 +01:00
commit 39a0dc8737
295 changed files with 25121 additions and 7360 deletions

View file

@ -7,10 +7,36 @@ using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Unity;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using bool2 = CultMath.bool2;
using cfloat2 = CultMath.float2;
using cfloat3 = CultMath.float3;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using int2 = Unity.Mathematics.int2;
internal static class AetheriaScalar
{
public static float Clamp(float value, float min, float max)
{
return value < min ? min : value > max ? max : value;
}
public static float Lerp(float from, float to, float t)
{
return from + (to - from) * t;
}
public static float Unlerp(float from, float to, float value)
{
return Math.Abs(to - from) <= 0.000001f ? 0 : (value - from) / (to - from);
}
public static float Saturate(float value)
{
return Clamp(value, 0, 1);
}
}
public abstract class Entity
{
public Zone Zone;
@ -19,8 +45,38 @@ public abstract class Entity
public EquippedItem EquippedHull;
public float3 Position;
public float2 Direction = float2(0,1);
public float2 Direction = new float2(0,1);
public float2 Velocity;
public cfloat3 CultPosition
{
get => AetheriaMath.ToCult(Position);
set => Position = AetheriaMath.ToUnity(value);
}
public cfloat2 CultPositionXZ
{
get => AetheriaMath.ToCultXZ(Position);
set => Position.xz = AetheriaMath.ToUnity(value);
}
public float CultPositionY
{
get => Position.y;
set => Position.y = value;
}
public cfloat2 CultDirection
{
get => AetheriaMath.ToCult(Direction);
set => Direction = AetheriaMath.ToUnity(value);
}
public cfloat2 CultVelocity
{
get => AetheriaMath.ToCult(Velocity);
set => Velocity = AetheriaMath.ToUnity(value);
}
public float[,] Temperature;
public float[,] NewTemperature;
@ -40,6 +96,18 @@ public abstract class Entity
public ReactiveProperty<Entity> Target = new ReactiveProperty<Entity>((Entity)null);
public float3 LookDirection;
public cfloat3 CultLookDirection
{
get => AetheriaMath.ToCult(LookDirection);
set => LookDirection = AetheriaMath.ToUnity(value);
}
public cfloat2 CultLookDirectionXZ
{
get => AetheriaMath.ToCultXZ(LookDirection);
set => LookDirection = AetheriaMath.ToUnityXZ(value);
}
public string Name;
@ -47,9 +115,9 @@ public abstract class Entity
public readonly Dictionary<string, float> Messages = new Dictionary<string, float>();
public readonly Dictionary<object, float> VisibilitySources = new Dictionary<object, float>();
public readonly ReactiveDictionary<Entity, float> EntityInfoGathered = new ReactiveDictionary<Entity, float>();
public readonly Dictionary<HardpointData, (float3 position, float3 direction)> HardpointTransforms =
new Dictionary<HardpointData, (float3 position, float3 direction)>();
public readonly ReactiveDictionary<Entity, float> EntityInfoGathered = new ReactiveDictionary<Entity, float>();
public readonly Dictionary<HardpointData, (cfloat3 position, cfloat3 direction)> HardpointTransforms =
new Dictionary<HardpointData, (cfloat3 position, cfloat3 direction)>();
public (List<Weapon> weapons, List<EquippedItem> items)[] WeaponGroups;
@ -95,6 +163,11 @@ public abstract class Entity
{
get => _active;
}
public void RestoreActiveState(bool active)
{
_active = active;
}
public IEnumerable<Weapon> Weapons
{
@ -317,8 +390,8 @@ public abstract class Entity
public void RestoreThermalExposure(float heatstroke, float hypothermia)
{
Heatstroke = saturate(heatstroke);
Hypothermia = saturate(hypothermia);
Heatstroke = AetheriaScalar.Saturate(heatstroke);
Hypothermia = AetheriaScalar.Saturate(hypothermia);
}
public void RestoreActiveConsumable(ConsumableItem item, float remainingDuration, float duration)
@ -436,7 +509,7 @@ public abstract class Entity
throw new InvalidOperationException($"Unable to map entity {Name}: missing typed hull row for {Hull?.ItemKey}");
var hullShape = GetHullShape(typedHull);
EquippedHull = new EquippedItem(ItemManager, Hull, int2.zero, this);
EquippedHull = new EquippedItem(ItemManager, Hull, new int2(0, 0), this);
Equipment.Add(EquippedHull);
Mass = ItemManager.GetMass(Hull);
Temperature = new float[hullShape.Width, hullShape.Height];
@ -546,7 +619,7 @@ public abstract class Entity
if (itemInstance is SimpleCommodity simpleCommodity)
{
var targetQuantity = min(simpleCommodity.Quantity, quantity - quantityTransferred);
var targetQuantity = Math.Min(simpleCommodity.Quantity, quantity - quantityTransferred);
if (!target.CargoBays.Any(c => originInventory.TryTransferItem(c, simpleCommodity, targetQuantity)))
{
quantityTransferred += targetQuantity - simpleCommodity.Quantity;
@ -714,7 +787,7 @@ public abstract class Entity
if (cells == null) return shape;
foreach (var cell in cells)
shape[new int2(cell.X, cell.Y)] = true;
shape.SetCell(cell.X, cell.Y, true);
return shape;
}
@ -801,14 +874,14 @@ public abstract class Entity
// Don't allow equipping while deployed
if (_active)
{
hullCoord = int2.zero;
hullCoord = new int2(0, 0);
return false;
}
var typedItem = ItemManager.GetRuntimeItem(item);
var typedHull = ItemManager.GetRuntimeItem(Hull);
if (typedItem == null || typedHull == null)
{
hullCoord = int2.zero;
hullCoord = new int2(0, 0);
return false;
}
@ -850,7 +923,7 @@ public abstract class Entity
}
}
hullCoord = int2.zero;
hullCoord = new int2(0, 0);
return false;
}
@ -989,7 +1062,7 @@ public abstract class Entity
{
if(cap.Charge > 0.01f)
{
var chargeRemoved = min(chargeToRemove / chargedCapacitors, cap.Charge);
var chargeRemoved = Math.Min(chargeToRemove / chargedCapacitors, cap.Charge);
cap.AddCharge(-chargeRemoved);
energy -= chargeRemoved;
}
@ -1058,9 +1131,9 @@ public abstract class Entity
public virtual void Update(float delta)
{
TargetRange = Target.Value == null ? -1 : length(Position - Target.Value.Position);
TargetRange = Target.Value == null ? -1 : CultMath.math.length(CultPosition - Target.Value.CultPosition);
var localSecurityLevel = Zone.GetSecurityLevel(Position.xz);
var localSecurityLevel = Zone.GetSecurityLevel(CultPositionXZ);
if (CurrentSecurityLevel.Value != localSecurityLevel) CurrentSecurityLevel.Value = localSecurityLevel;
foreach (var v in VisibilitySources.Keys.ToArray())
@ -1090,9 +1163,9 @@ public abstract class Entity
if (cockpitTemp > ItemManager.GameplaySettings.HeatstrokeTemperature)
{
var previous = Heatstroke;
Heatstroke = saturate(
Heatstroke = AetheriaScalar.Saturate(
Heatstroke +
pow(cockpitTemp - ItemManager.GameplaySettings.HeatstrokeTemperature, ItemManager.GameplaySettings.HeatstrokeExponent) *
MathF.Pow(cockpitTemp - ItemManager.GameplaySettings.HeatstrokeTemperature, ItemManager.GameplaySettings.HeatstrokeExponent) *
ItemManager.GameplaySettings.HeatstrokeMultiplier * delta);
if(previous < ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold && Heatstroke > ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold)
HeatstrokeRisk.OnNext(Unit.Default);
@ -1104,15 +1177,15 @@ public abstract class Entity
}
else
{
Heatstroke = saturate(Heatstroke - ItemManager.GameplaySettings.HeatstrokeRecoverySpeed * delta);
Heatstroke = AetheriaScalar.Saturate(Heatstroke - ItemManager.GameplaySettings.HeatstrokeRecoverySpeed * delta);
}
if (cockpitTemp < ItemManager.GameplaySettings.HypothermiaTemperature)
{
var previous = Hypothermia;
Hypothermia = saturate(
Hypothermia = AetheriaScalar.Saturate(
Hypothermia +
pow(ItemManager.GameplaySettings.HypothermiaTemperature - cockpitTemp, ItemManager.GameplaySettings.HypothermiaExponent) *
MathF.Pow(ItemManager.GameplaySettings.HypothermiaTemperature - cockpitTemp, ItemManager.GameplaySettings.HypothermiaExponent) *
ItemManager.GameplaySettings.HypothermiaMultiplier * delta);
if(previous < ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold && Heatstroke > ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold)
HypothermiaRisk.OnNext(Unit.Default);
@ -1124,7 +1197,7 @@ public abstract class Entity
}
else
{
Hypothermia = saturate(Hypothermia - ItemManager.GameplaySettings.HypothermiaRecoverySpeed * delta);
Hypothermia = AetheriaScalar.Saturate(Hypothermia - ItemManager.GameplaySettings.HypothermiaRecoverySpeed * delta);
}
}
@ -1151,10 +1224,10 @@ public abstract class Entity
if (Parent != null)
{
Position = Parent.Position;
Velocity = Parent.Velocity;
CultPosition = Parent.CultPosition;
CultVelocity = Parent.CultVelocity;
}
else Position.y = Zone.GetHeight(Position.xz) + (float)(ItemManager.GetRuntimeItem(Hull)?.HullGridOffset ?? 0);
else CultPositionY = Zone.GetHeight(CultPositionXZ) + (float)(ItemManager.GetRuntimeItem(Hull)?.HullGridOffset ?? 0);
}
private void UpdateTemperature(float delta)
@ -1175,7 +1248,7 @@ public abstract class Entity
var totalTemp = temp / ItemManager.GameplaySettings.HeatConductionMultiplier;
var totalConductivity = 1f / ItemManager.GameplaySettings.HeatConductionMultiplier;
if (hullShape[int2(v.x - 1, v.y)])
if (hullShape.GetCell(v.x - 1, v.y))
{
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
(GearOccupancy[v.x - 1, v.y]?.Conductivity ?? 1) *
@ -1185,7 +1258,7 @@ public abstract class Entity
totalTemp += Temperature[v.x - 1, v.y] * conductivity;
}
if (hullShape[int2(v.x + 1, v.y)])
if (hullShape.GetCell(v.x + 1, v.y))
{
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
(GearOccupancy[v.x + 1, v.y]?.Conductivity ?? 1) *
@ -1196,7 +1269,7 @@ public abstract class Entity
}
if (hullShape[int2(v.x, v.y - 1)])
if (hullShape.GetCell(v.x, v.y - 1))
{
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
(GearOccupancy[v.x, v.y - 1]?.Conductivity ?? 1) *
@ -1207,7 +1280,7 @@ public abstract class Entity
}
if (hullShape[int2(v.x, v.y + 1)])
if (hullShape.GetCell(v.x, v.y + 1))
{
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
(GearOccupancy[v.x, v.y + 1]?.Conductivity ?? 1) *
@ -1223,7 +1296,7 @@ public abstract class Entity
// For all cells on the border of the entity, radiate some heat into space, increasing the visibility of the ship
if (Parent==null && !hullInterior[v])
{
var rad = pow(NewTemperature[v.x, v.y], ItemManager.GameplaySettings.HeatRadiationExponent) *
var rad = MathF.Pow(NewTemperature[v.x, v.y], ItemManager.GameplaySettings.HeatRadiationExponent) *
ItemManager.GameplaySettings.HeatRadiationMultiplier;
NewTemperature[v.x, v.y] -= rad * delta;
r += rad;
@ -1278,7 +1351,7 @@ public class ConsumableItemEffect
_duration = duration.GetValueOrDefault(RuntimeItem?.Duration > 0 ? (float)RuntimeItem.Duration : 0);
_effectiveness = CreateEffectivenessCurve(RuntimeItem);
RemainingDuration = remainingDuration.HasValue
? clamp(remainingDuration.Value, 0, Duration)
? AetheriaScalar.Clamp(remainingDuration.Value, 0, Duration)
: _duration;
Behaviors = entity.ItemManager.CreateRuntimeBehaviors(this);
@ -1300,37 +1373,101 @@ public class ConsumableItemEffect
public float Evaluate(PerformanceStat stat)
{
if (stat == null)
return 0;
var duration = Math.Max(_duration, float.Epsilon);
var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration);
var quality = pow(Item.Quality, stat.QualityExponent);
var result = lerp(stat.Min, stat.Max, effectiveness * quality);
var result = stat.EvaluateConsumableBaseline(new StatEvaluationContext(
quality: Item.Quality,
durability: effectiveness,
heat: 1));
if (float.IsNaN(result))
return stat.Min;
return result;
}
public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value)
{
if (stat == null)
return 0;
if ((GetDependencyMask(stat) & condition) == 0)
return Evaluate(stat);
var duration = Math.Max(_duration, float.Epsilon);
var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration);
var context = new StatEvaluationContext(
quality: Item.Quality,
durability: effectiveness,
heat: 1).WithCondition(condition, value);
var result = stat.EvaluateRecipeOrLegacy(
context,
durabilityExponent: 1,
thermalExponent: 0,
includeHeat: false);
if (float.IsNaN(result))
return stat.Min;
return result;
}
public float Evaluate(
PerformanceStat stat,
StatConditionMask firstCondition,
float firstValue,
StatConditionMask secondCondition,
float secondValue)
{
if (stat == null)
return 0;
var dependencyMask = GetDependencyMask(stat);
if ((dependencyMask & (firstCondition | secondCondition)) == 0)
return Evaluate(stat);
var duration = Math.Max(_duration, float.Epsilon);
var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration);
var context = new StatEvaluationContext(
quality: Item.Quality,
durability: effectiveness,
heat: 1)
.WithCondition(firstCondition, firstValue)
.WithCondition(secondCondition, secondValue);
var result = stat.EvaluateRecipeOrLegacy(
context,
durabilityExponent: 1,
thermalExponent: 0,
includeHeat: false);
if (float.IsNaN(result))
return stat.Min;
return result;
}
private static StatConditionMask GetDependencyMask(PerformanceStat stat)
{
return stat?.GetDependencyMask(includeHeat: false) ?? StatConditionMask.None;
}
private static BezierCurve CreateEffectivenessCurve(AetheriaRuntimeCatalogItem item)
{
if (item?.EffectivenessCurveKeys != null && item.EffectivenessCurveKeys.Count > 0)
{
return new BezierCurve
{
Keys = item.EffectivenessCurveKeys
.Select(key => new float4(
return BezierCurve.FromKeys(
item.EffectivenessCurveKeys
.Select(key => (
(float)key.Time,
(float)key.Value,
(float)key.InTangent,
(float)key.OutTangent))
.ToArray()
};
);
}
return new BezierCurve
{
Keys = new[] { new float4(0, 1, 0, 0), new float4(1, 1, 0, 0) }
};
return BezierCurve.FromKeys(new[] { (0f, 1f, 0f, 0f), (1f, 1f, 0f, 0f) });
}
}
@ -1371,6 +1508,26 @@ public class EquippedItem
private readonly BezierCurve _thermalPerformanceCurve;
private readonly EquippedItemAudioStatBinding[] _audioStats;
private readonly Dictionary<PerformanceStat, EquippedItemBaseStatCacheEntry> _baseStatCache =
new Dictionary<PerformanceStat, EquippedItemBaseStatCacheEntry>();
private readonly Dictionary<PerformanceStat, EquippedItemStatCacheEntry> _statCache =
new Dictionary<PerformanceStat, EquippedItemStatCacheEntry>();
private int _qualityVersion;
private int _durabilityVersion;
private int _thermalVersion;
private int _chargeVersion;
private int _ammoVersion;
private int _rangeVersion;
private int _integrityVersion;
private int _pilotSkillVersion;
private int _environmentVersion;
private float _lastQuality;
private float _statCharge;
private float _statAmmo;
private float _statRange;
private float _statIntegrity;
private float _statPilotSkill;
private float _statEnvironment;
private float oldTemperature;
public float Temperature
{
@ -1465,14 +1622,15 @@ public class EquippedItem
ThermalResilience = RuntimeItem?.ThermalResilience > 0 ? (float)RuntimeItem.ThermalResilience : 1;
_thermalPerformanceCurve = CreateThermalPerformanceCurve(RuntimeItem);
_audioStats = CreateAudioStatBindings(RuntimeItem);
ThermalExponent = lerp(
ThermalExponent = AetheriaScalar.Lerp(
ItemManager.GameplaySettings.ThermalQualityMin,
ItemManager.GameplaySettings.ThermalQualityMax,
pow(item.Quality, ItemManager.GameplaySettings.ThermalQualityExponent));
DurabilityExponent = lerp(
MathF.Pow(item.Quality, ItemManager.GameplaySettings.ThermalQualityExponent));
DurabilityExponent = AetheriaScalar.Lerp(
ItemManager.GameplaySettings.DurabilityQualityMin,
ItemManager.GameplaySettings.DurabilityQualityMax,
pow(item.Quality, ItemManager.GameplaySettings.DurabilityQualityExponent));
MathF.Pow(item.Quality, ItemManager.GameplaySettings.DurabilityQualityExponent));
_lastQuality = item.Quality;
var hullShape = ItemManager.GetRuntimeShape(entity.Hull);
var itemShape = ItemManager.GetRuntimeShape(item);
InsetShape = hullShape.Inset(itemShape, position, item.Rotation);
@ -1503,25 +1661,183 @@ public class EquippedItem
}
}
internal void RegisterPerformanceStat(string behaviorKind, string statName, PerformanceStat stat)
{
if (stat == null || string.IsNullOrWhiteSpace(statName))
return;
if (!_statCache.TryGetValue(stat, out var entry))
{
_statCache[stat] = new EquippedItemStatCacheEntry(stat, GetDependencyMask(stat));
}
else
{
entry.Stat = stat;
entry.DependencyMask = GetDependencyMask(stat);
entry.Dirty = true;
}
}
public float Evaluate(string behaviorKind, string statName, PerformanceStat stat)
{
RegisterPerformanceStat(behaviorKind, statName, stat);
return Evaluate(stat);
}
public float Evaluate(PerformanceStat stat)
{
var heat = pow(ThermalPerformance, ThermalExponent * stat.HeatExponentMultiplier);
var durability = pow(DurabilityPerformance, DurabilityExponent * stat.DurabilityExponentMultiplier);
var quality = pow(EquippableItem.Quality, stat.QualityExponent);
if (stat == null)
{
return 0;
}
var scaleModifier = 1.0f;
var scaleModifiers = stat.GetScaleModifiers(Entity).Values;
foreach (var value in scaleModifiers) scaleModifier *= value;
if (_statCache.TryGetValue(stat, out var entry))
{
return EvaluateCachedStat(entry);
}
float constantModifier = 0;
foreach (var value in stat.GetConstantModifiers(Entity).Values) constantModifier += value;
return EvaluateUncached(stat);
}
var result = lerp(stat.Min, stat.Max, durability * quality * heat) * scaleModifier + constantModifier;
public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value)
{
if (stat == null)
return 0;
var dependencyMask = GetDependencyMask(stat);
if ((dependencyMask & condition) == 0)
return Evaluate(stat);
return EvaluateWithContext(stat, CreateStatEvaluationContext().WithCondition(condition, value));
}
public float Evaluate(
PerformanceStat stat,
StatConditionMask firstCondition,
float firstValue,
StatConditionMask secondCondition,
float secondValue)
{
if (stat == null)
return 0;
var dependencyMask = GetDependencyMask(stat);
if ((dependencyMask & (firstCondition | secondCondition)) == 0)
return Evaluate(stat);
var context = CreateStatEvaluationContext()
.WithCondition(firstCondition, firstValue)
.WithCondition(secondCondition, secondValue);
return EvaluateWithContext(stat, context);
}
private float EvaluateCachedStat(EquippedItemStatCacheEntry entry)
{
var stamp = BuildCacheStamp(entry.DependencyMask);
if (!entry.Dirty && entry.InputStamp == stamp)
{
return entry.Value;
}
var value = EvaluateUncached(entry.Stat);
entry.Value = value;
entry.InputStamp = stamp;
entry.Dirty = false;
return value;
}
private float EvaluateUncached(PerformanceStat stat)
{
var dependencyMask = GetDependencyMask(stat);
var stamp = BuildCacheStamp(dependencyMask);
if (!_baseStatCache.TryGetValue(stat, out var baseEntry) || baseEntry.InputStamp != stamp)
{
float baseValue;
baseValue = stat.EvaluateRecipeOrLegacy(
CreateStatEvaluationContext(),
DurabilityExponent,
ThermalExponent,
includeHeat: true);
_baseStatCache[stat] = new EquippedItemBaseStatCacheEntry(baseValue, stamp);
}
var cachedBaseValue = _baseStatCache[stat].Value;
var result = stat.ApplyEntityModifiers(Entity, cachedBaseValue);
if (float.IsNaN(result))
return stat.Min;
return result;
}
private float EvaluateWithContext(PerformanceStat stat, in StatEvaluationContext context)
{
var baseValue = stat.EvaluateRecipeOrLegacy(
context,
DurabilityExponent,
ThermalExponent,
includeHeat: true);
var result = stat.ApplyEntityModifiers(Entity, baseValue);
return float.IsNaN(result) ? stat.Min : result;
}
internal void SetStatModifier(
PerformanceStat stat,
Behavior source,
StatModifierType type,
float value)
{
if (stat == null || source == null)
return;
var modifiers = type == StatModifierType.Constant
? stat.GetConstantModifiers(Entity)
: stat.GetScaleModifiers(Entity);
if (modifiers.TryGetValue(source, out var current) && MathF.Abs(current - value) < 0.00001f)
return;
modifiers[source] = value;
InvalidateStat(stat);
}
internal void RemoveStatModifier(PerformanceStat stat, Behavior source, StatModifierType type)
{
if (stat == null || source == null)
return;
var hasModifiers = type == StatModifierType.Constant
? stat.TryGetConstantModifiers(Entity, out var modifiers)
: stat.TryGetScaleModifiers(Entity, out modifiers);
if (hasModifiers && modifiers.Remove(source))
InvalidateStat(stat);
}
public void SetStatConditionValue(StatConditionMask condition, float value)
{
switch (condition)
{
case StatConditionMask.Charge:
SetTrackedStatCondition(ref _statCharge, ref _chargeVersion, value);
break;
case StatConditionMask.Ammo:
SetTrackedStatCondition(ref _statAmmo, ref _ammoVersion, value);
break;
case StatConditionMask.Range:
SetTrackedStatCondition(ref _statRange, ref _rangeVersion, value);
break;
case StatConditionMask.Integrity:
SetTrackedStatCondition(ref _statIntegrity, ref _integrityVersion, value);
break;
case StatConditionMask.PilotSkill:
SetTrackedStatCondition(ref _statPilotSkill, ref _pilotSkillVersion, value);
break;
case StatConditionMask.Environment:
SetTrackedStatCondition(ref _statEnvironment, ref _environmentVersion, value);
break;
}
}
public void AddHeat(float heat, bool ignoreThermalMass = false)
{
foreach(var hullCoord in InsetShape.Coordinates)
@ -1531,12 +1847,28 @@ public class EquippedItem
public void UpdatePerformance()
{
var temp = Temperature;
ThermalPerformance = EvaluateThermalPerformance(temp);
var deltaTemp = math.abs(temp - oldTemperature);
DurabilityPerformance = EquippableItem.Durability / MaxDurability;
var nextThermalPerformance = EvaluateThermalPerformance(temp);
var deltaTemp = MathF.Abs(temp - oldTemperature);
var nextDurabilityPerformance = EquippableItem.Durability / MaxDurability;
var nextQuality = EquippableItem.Quality;
if (MathF.Abs(nextThermalPerformance - ThermalPerformance) > 0.00001f)
{
_thermalVersion++;
}
if (MathF.Abs(nextDurabilityPerformance - DurabilityPerformance) > 0.00001f)
{
_durabilityVersion++;
}
if (MathF.Abs(nextQuality - _lastQuality) > 0.00001f)
{
_qualityVersion++;
}
ThermalPerformance = nextThermalPerformance;
DurabilityPerformance = nextDurabilityPerformance;
_lastQuality = nextQuality;
var performanceThreshold = Entity.Settings.ShutdownPerformance;
Wear = (1 - pow(ThermalPerformance,
(1 - pow(EquippableItem.Quality, ItemManager.GameplaySettings.QualityWearExponent)) *
Wear = (1 - MathF.Pow(ThermalPerformance,
(1 - MathF.Pow(EquippableItem.Quality, ItemManager.GameplaySettings.QualityWearExponent)) *
ItemManager.GameplaySettings.ThermalWearExponent) +
deltaTemp * ItemManager.GameplaySettings.DeltaTempWearExponent
) * MaxDurability / ThermalResilience;
@ -1545,6 +1877,67 @@ public class EquippedItem
oldTemperature = temp;
}
private void InvalidateStat(PerformanceStat stat)
{
if (stat != null && _statCache.TryGetValue(stat, out var entry))
entry.Dirty = true;
}
private StatConditionMask GetDependencyMask(PerformanceStat stat)
{
return stat?.GetDependencyMask(includeHeat: true) ?? StatConditionMask.None;
}
private int BuildCacheStamp(StatConditionMask dependencyMask)
{
unchecked
{
var stamp = 17;
if ((dependencyMask & StatConditionMask.Quality) != 0)
stamp = stamp * 31 + _qualityVersion;
if ((dependencyMask & StatConditionMask.Durability) != 0)
stamp = stamp * 31 + _durabilityVersion;
if ((dependencyMask & StatConditionMask.Heat) != 0)
stamp = stamp * 31 + _thermalVersion;
if ((dependencyMask & StatConditionMask.Charge) != 0)
stamp = stamp * 31 + _chargeVersion;
if ((dependencyMask & StatConditionMask.Ammo) != 0)
stamp = stamp * 31 + _ammoVersion;
if ((dependencyMask & StatConditionMask.Range) != 0)
stamp = stamp * 31 + _rangeVersion;
if ((dependencyMask & StatConditionMask.Integrity) != 0)
stamp = stamp * 31 + _integrityVersion;
if ((dependencyMask & StatConditionMask.PilotSkill) != 0)
stamp = stamp * 31 + _pilotSkillVersion;
if ((dependencyMask & StatConditionMask.Environment) != 0)
stamp = stamp * 31 + _environmentVersion;
return stamp;
}
}
private StatEvaluationContext CreateStatEvaluationContext()
{
return new StatEvaluationContext(
quality: EquippableItem.Quality,
durability: DurabilityPerformance,
heat: ThermalPerformance,
charge: _statCharge,
ammo: _statAmmo,
range: _statRange,
integrity: _statIntegrity,
pilotSkill: _statPilotSkill,
environment: _statEnvironment);
}
private static void SetTrackedStatCondition(ref float current, ref int version, float next)
{
if (MathF.Abs(current - next) <= 0.00001f)
return;
current = next;
version++;
}
private float EvaluateThermalPerformance(float temperature)
{
if (_thermalPerformanceCurve == null ||
@ -1554,8 +1947,8 @@ public class EquippedItem
return 1;
}
var t = unlerp((float)RuntimeItem.MinimumTemperature, (float)RuntimeItem.MaximumTemperature, temperature);
return saturate(_thermalPerformanceCurve.Evaluate(t));
var t = AetheriaScalar.Unlerp((float)RuntimeItem.MinimumTemperature, (float)RuntimeItem.MaximumTemperature, temperature);
return AetheriaScalar.Saturate(_thermalPerformanceCurve.Evaluate(t));
}
private static BezierCurve CreateThermalPerformanceCurve(AetheriaRuntimeCatalogItem item)
@ -1565,12 +1958,9 @@ public class EquippedItem
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()
};
return BezierCurve.FromKeys(
item.ThermalPerformanceCurveKeys
.Select(key => ((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent)));
}
public void Update(float delta)
@ -1626,10 +2016,50 @@ public class EquippedItem
Max = (float)stat.Max,
HeatExponentMultiplier = (float)stat.HeatExponentMultiplier,
DurabilityExponentMultiplier = (float)stat.DurabilityExponentMultiplier,
QualityExponent = (float)stat.QualityExponent
QualityExponent = (float)stat.QualityExponent,
Recipe = CreateStatRecipe(stat.Recipe)
};
}
private static StatRecipe CreateStatRecipe(AetheriaRuntimeStatRecipe recipe)
{
if (recipe == null)
return null;
return new StatRecipe
{
BaseValue = (float)recipe.BaseValue,
Modifiers = recipe.Modifiers
.Select(CreateStatRecipeModifier)
.Where(modifier => modifier != null)
.ToArray()
};
}
private static StatRecipeModifier CreateStatRecipeModifier(AetheriaRuntimeStatRecipeModifier modifier)
{
if (modifier == null)
return null;
return new StatRecipeModifier
{
Condition = StatRecipeTokens.ToConditionMask(modifier.Condition),
Operation = StatRecipeTokens.ToModifierOperation(modifier.Operation),
Amount = (float)modifier.Amount,
Curve = CreateCurve(modifier.CurveKeys),
Enabled = modifier.Enabled
};
}
private static BezierCurve CreateCurve(IReadOnlyList<AetheriaRuntimeCurveKey> keys)
{
if (keys == null || keys.Count == 0)
return null;
return BezierCurve.FromKeys(
keys.Select(key => ((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent)));
}
private readonly struct EquippedItemAudioStatBinding
{
public EquippedItemAudioStatBinding(uint parameter, PerformanceStat stat)
@ -1641,6 +2071,34 @@ public class EquippedItem
public uint Parameter { get; }
public PerformanceStat Stat { get; }
}
private sealed class EquippedItemStatCacheEntry
{
public EquippedItemStatCacheEntry(PerformanceStat stat, StatConditionMask dependencyMask)
{
Stat = stat;
DependencyMask = dependencyMask;
Dirty = true;
}
public PerformanceStat Stat { get; set; }
public StatConditionMask DependencyMask { get; set; }
public float Value { get; set; }
public int InputStamp { get; set; }
public bool Dirty { get; set; }
}
private readonly struct EquippedItemBaseStatCacheEntry
{
public EquippedItemBaseStatCacheEntry(float value, int inputStamp)
{
Value = value;
InputStamp = inputStamp;
}
public float Value { get; }
public int InputStamp { get; }
}
}
public class EquippedCargoBay : EquippedItem
@ -1735,7 +2193,7 @@ public class EquippedCargoBay : EquippedItem
if (cargoCommodity.Quantity >= maxStack) continue;
// Subtract remaining space in existing stack from remaining quantity
remainingQuantity -= min(maxStack - cargoCommodity.Quantity, remainingQuantity);
remainingQuantity -= Math.Min(maxStack - cargoCommodity.Quantity, remainingQuantity);
positions.Add(Cargo[cargoItem]);
// If we've moved all of the items into existing stacks, no need to search for empty space!
@ -1769,7 +2227,7 @@ public class EquippedCargoBay : EquippedItem
}
}
position = int2.zero;
position = new int2(0, 0);
return false;
}
@ -1978,7 +2436,7 @@ public class EquippedCargoBay : EquippedItem
if (cells == null) return shape;
foreach (var cell in cells)
shape[new int2(cell.X, cell.Y)] = true;
shape.SetCell(cell.X, cell.Y, true);
return shape;
}
@ -2025,7 +2483,7 @@ public class EquippedDockingBay : EquippedCargoBay
var typedDockingBay = ItemManager.GetRuntimeItem(EquippableItem);
MaxSize = typedDockingBay != null
? new int2(typedDockingBay.DockingMaxSizeX, typedDockingBay.DockingMaxSizeY)
: int2.zero;
: new int2(0, 0);
}
}