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

@ -2,6 +2,7 @@
"name": "Aetheria.Shared.Unity",
"rootNamespace": "",
"references": [
"CultMath",
"Unity.Mathematics",
"UniRx",
"Ink",

View file

@ -1,13 +1,78 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using CultMath;
using static CultMath.math;
using double2 = CultMath.double2;
using double3 = CultMath.double3;
using float2 = CultMath.float2;
using float3 = CultMath.float3;
using unityfloat2 = Unity.Mathematics.float2;
using unityfloat3 = Unity.Mathematics.float3;
public static class AetheriaMath
{
public const float Deg2Rad = 0.01745329f;
public const float Rad2Deg = 57.29578f;
public static float2 ToCult(unityfloat2 value) => float2(value.x, value.y);
public static float3 ToCult(unityfloat3 value) => float3(value.x, value.y, value.z);
public static float2 ToCultXZ(unityfloat3 value) => float2(value.x, value.z);
public static unityfloat2 ToUnity(float2 value) => new(value.x, value.y);
public static unityfloat3 ToUnity(float3 value) => new(value.x, value.y, value.z);
public static unityfloat3 ToUnityXZ(float2 value) => new(value.x, 0, value.y);
public static float AngleDegrees(float2 from, float2 to)
{
return AngleRadians(from, to) * Rad2Deg;
}
public static float AngleDegrees(float3 from, float3 to)
{
return AngleRadians(from, to) * Rad2Deg;
}
public static float AngleDegrees(unityfloat2 from, unityfloat2 to)
{
return AngleDegrees(ToCult(from), ToCult(to));
}
public static float AngleDegrees(unityfloat3 from, unityfloat3 to)
{
return AngleDegrees(ToCult(from), ToCult(to));
}
public static float AngleRadians(float2 from, float2 to)
{
var denom = sqrt(lengthsq(from) * lengthsq(to));
if (denom <= 0.000001f)
{
return 0;
}
return acos(clamp(dot(from, to) / denom, -1.0f, 1.0f));
}
public static float AngleRadians(float3 from, float3 to)
{
var denom = sqrt(lengthsq(from) * lengthsq(to));
if (denom <= 0.000001f)
{
return 0;
}
return acos(clamp(dot(from, to) / denom, -1.0f, 1.0f));
}
public static float AngleRadians(unityfloat2 from, unityfloat2 to)
{
return AngleRadians(ToCult(from), ToCult(to));
}
public static float AngleRadians(unityfloat3 from, unityfloat3 to)
{
return AngleRadians(ToCult(from), ToCult(to));
}
public static float Decay(float source, float lambda, float dt)
{
return source * exp(-lambda * dt);
@ -18,10 +83,20 @@ public static class AetheriaMath
return source * exp(-lambda * dt);
}
public static unityfloat3 Decay(unityfloat3 source, float lambda, float dt)
{
return ToUnity(Decay(ToCult(source), lambda, dt));
}
public static float3 Decay(float3 source, float3 lambda, float dt)
{
return source * exp(-lambda * dt);
}
public static unityfloat3 Decay(unityfloat3 source, unityfloat3 lambda, float dt)
{
return ToUnity(Decay(ToCult(source), ToCult(lambda), dt));
}
public static float Damp(float a, float b, float lambda, float dt)
{
@ -32,11 +107,21 @@ public static class AetheriaMath
{
return lerp(a, b, 1 - exp(-lambda * dt));
}
public static unityfloat2 Damp(unityfloat2 a, unityfloat2 b, float lambda, float dt)
{
return ToUnity(Damp(ToCult(a), ToCult(b), lambda, dt));
}
public static float3 Damp(float3 a, float3 b, float lambda, float dt)
{
return lerp(a, b, 1 - exp(-lambda * dt));
}
public static unityfloat3 Damp(unityfloat3 a, unityfloat3 b, float lambda, float dt)
{
return ToUnity(Damp(ToCult(a), ToCult(b), lambda, dt));
}
public static double Damp(double a, double b, double lambda, double dt)
{
@ -72,6 +157,22 @@ public static class AetheriaMath
);
return targetPosition + t*targetRelativeVelocity;
}
public static unityfloat3 FirstOrderIntercept
(
unityfloat3 shooterPosition,
unityfloat3 shooterVelocity,
float shotSpeed,
unityfloat3 targetPosition,
unityfloat3 targetVelocity
) {
return ToUnity(FirstOrderIntercept(
ToCult(shooterPosition),
ToCult(shooterVelocity),
shotSpeed,
ToCult(targetPosition),
ToCult(targetVelocity)));
}
//first-order intercept using relative target position
public static float FirstOrderInterceptTime
@ -112,6 +213,15 @@ public static class AetheriaMath
else //determinant = 0; one intercept path, pretty much never happens
return max(-b/(2f*a), 0f); //don't shoot back in time
}
public static float FirstOrderInterceptTime
(
float shotSpeed,
unityfloat3 targetRelativePosition,
unityfloat3 targetRelativeVelocity
) {
return FirstOrderInterceptTime(shotSpeed, ToCult(targetRelativePosition), ToCult(targetRelativeVelocity));
}
// http://csharphelper.com/blog/2016/09/find-the-shortest-distance-between-a-point-and-a-line-segment-in-c/
// Calculate the distance between
@ -240,4 +350,38 @@ public static class AetheriaMath
x = saturate(x);
return x * x * x * (x * (x * 6 - 15) + 10);
}
public static float2 Rotate(float2 value, float radians)
{
var s = (float)System.Math.Sin(radians);
var c = (float)System.Math.Cos(radians);
return float2(value.x * c - value.y * s, value.x * s + value.y * c);
}
public static float2 Rotate(float2 value, ItemRotation rotation)
{
switch (rotation)
{
case ItemRotation.None:
return value;
case ItemRotation.CounterClockwise:
return float2(-value.y, value.x);
case ItemRotation.Reversed:
return float2(-value.x, -value.y);
case ItemRotation.Clockwise:
return float2(value.y, -value.x);
default:
throw new System.ArgumentOutOfRangeException(nameof(rotation), rotation, null);
}
}
public static unityfloat2 Rotate(unityfloat2 value, float radians)
{
return ToUnity(Rotate(ToCult(value), radians));
}
public static unityfloat2 Rotate(unityfloat2 value, ItemRotation rotation)
{
return ToUnity(Rotate(ToCult(value), rotation));
}
}

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using cfloat3 = CultMath.float3;
public interface INamedEntry
{
@ -26,5 +26,5 @@ public class AetheriaRuntimeItemReference
public interface ITintInspector
{
float3 TintColor { get; }
cfloat3 TintColor { get; }
}

View file

@ -7,9 +7,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using static CultMath.math;
using cfloat2 = CultMath.float2;
public class Agent
{
@ -55,26 +54,27 @@ public class Agent
}
}
public void Accelerate(float2 targetVelocity, bool noTurn = false)
public void Accelerate(cfloat2 targetVelocity, bool noTurn = false)
{
var deltaV = targetVelocity - Ship.Velocity;
var deltaV = targetVelocity - Ship.CultVelocity;
var deltaVMag = length(deltaV);
var deltaVDirection = normalize(deltaV);
// If Delta V is above the threshold, direct the ship towards the delta and use only main thrusters
if (!noTurn && deltaVMag > FORWARD_DELTA_THRESHOLD)
{
Ship.LookDirection = float3(deltaVDirection.x, 0, deltaVDirection.y);
Ship.MovementDirection = float2(0, pow(dot(Ship.Direction, deltaVDirection), 2));
Ship.CultLookDirectionXZ = deltaVDirection;
Ship.MovementDirection = new cfloat2(0, pow(dot(Ship.CultDirection, deltaVDirection), 2));
}
// If Delta V is low, direct the ship towards the target and use all thrusters
else if(deltaVMag > THRUST_DELTA_THRESHOLD)
{
var right = Ship.Direction.Rotate(ItemRotation.Clockwise);
Ship.MovementDirection = float2(dot(right, deltaVDirection), dot(Ship.Direction, deltaVDirection));
var shipDirection = Ship.CultDirection;
var right = AetheriaMath.Rotate(shipDirection, ItemRotation.Clockwise);
Ship.MovementDirection = new cfloat2(dot(right, deltaVDirection), dot(shipDirection, deltaVDirection));
}
else
{
Ship.MovementDirection = float2.zero;
Ship.MovementDirection = cfloat2.zero;
}
}
}

View file

@ -2,10 +2,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using float2x2 = Unity.Mathematics.float2x2;
using static CultMath.math;
using cfloat3 = CultMath.float3;
public class CombatState : BaseState
{
@ -13,7 +11,7 @@ public class CombatState : BaseState
private float _optimumRange;
private readonly List<(int index, float dps)> _availableGroups = new List<(int index, float dps)>();
private readonly List<LockWeapon> _availableLockingWeapons = new List<LockWeapon>();
public CombatState(Agent agent) : base(agent)
{
SampleDps();
@ -31,7 +29,9 @@ public class CombatState : BaseState
_availableGroups.Clear();
_availableLockingWeapons.Clear();
var toTarget = target.Position - _agent.Ship.Position;
var shipPosition = _agent.Ship.CultPosition;
var targetPosition = target.CultPosition;
var toTarget = targetPosition - shipPosition;
var targetDistance = length(toTarget);
for (var i = 0; i < _agent.Ship.WeaponGroups.Length; i++)
{
@ -66,16 +66,17 @@ public class CombatState : BaseState
}
}
var targetRight = target.Direction.Rotate(ItemRotation.Clockwise);
var targetDirection = target.CultDirection;
var targetRight = AetheriaMath.Rotate(targetDirection, ItemRotation.Clockwise);
var optimumRangeDelta = abs(_optimumRange - targetDistance);
var directionToTarget = normalize(toTarget.xz);
var targetPortAlignment = dot(targetRight, directionToTarget);
var targetForeAlignment = dot(-target.Direction, directionToTarget);
var targetForeAlignment = dot(-targetDirection, directionToTarget);
var forwardness = saturate(optimumRangeDelta / _agent.Settings.AgentMaxForwardDistance) * _agent.Settings.AgentForwardLerp;
if (targetForeAlignment > 0) forwardness = lerp(forwardness, 1, pow(targetPortAlignment, 2));
var movementDirection = normalize(lerp(
directionToTarget.Rotate(targetPortAlignment > 0 ? ItemRotation.Clockwise : ItemRotation.CounterClockwise),
AetheriaMath.Rotate(directionToTarget, targetPortAlignment > 0 ? ItemRotation.Clockwise : ItemRotation.CounterClockwise),
targetDistance > _optimumRange ? directionToTarget : -directionToTarget, forwardness));
if (selectedGroup >= 0)
@ -84,17 +85,16 @@ public class CombatState : BaseState
if(testWeapon.Velocity > 1)
{
var targetHull = _agent.ItemManager.GetRuntimeItem(target.Hull);
var targetVelocity = float3(target.Velocity.x, 0, target.Velocity.y);
var shipVelocity = float3(_agent.Ship.Velocity.x, 0, _agent.Ship.Velocity.y);
var targetVelocity = new cfloat3(target.CultVelocity.x, 0, target.CultVelocity.y);
var predictedPosition = AetheriaMath.FirstOrderIntercept(
_agent.Ship.Position,
float3.zero,
shipPosition,
cfloat3.zero,
testWeapon.Velocity,
target.Position,
targetPosition,
targetVelocity
);
predictedPosition.y = _agent.Ship.Zone.GetHeight(predictedPosition.xz) + (float)(targetHull?.HullGridOffset ?? 0);
toTarget = normalize(predictedPosition - _agent.Ship.Position);
toTarget = normalize(predictedPosition - shipPosition);
}
var shouldFire = dot(
@ -108,7 +108,7 @@ public class CombatState : BaseState
weapon.Deactivate();
}
}
_agent.Ship.LookDirection = toTarget;
_agent.Ship.CultLookDirection = toTarget;
_agent.Accelerate(movementDirection * _agent.TopSpeed, true);//selectedGroup >= 0);
// Fire charged guns!

View file

@ -1,10 +1,9 @@
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using static CultMath.math;
using cfloat2 = CultMath.float2;
public abstract class MatchVelocityState : BaseState
{
protected abstract float2 TargetVelocity { get; }
protected abstract cfloat2 TargetVelocity { get; }
protected MatchVelocityState(Agent agent) : base(agent) { }
@ -13,12 +12,12 @@ public abstract class MatchVelocityState : BaseState
_agent.Accelerate(TargetVelocity);
}
public float2 MatchDistanceTime
public cfloat2 MatchDistanceTime
{
get
{
var velocity = length(_agent.Ship.Velocity);
var deltaV = TargetVelocity - _agent.Ship.Velocity;
var velocity = length(_agent.Ship.CultVelocity);
var deltaV = TargetVelocity - _agent.Ship.CultVelocity;
var stoppingTime = length(deltaV) / (_agent.Ship.ForwardThrust / _agent.Ship.Mass);
var stoppingDistance = stoppingTime * (velocity / 2);
@ -26,7 +25,7 @@ public abstract class MatchVelocityState : BaseState
var turnaroundTime = _agent.Ship.TurnTime(deltaV);
var turnaroundDistance = turnaroundTime * velocity;
return float2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime);
return new cfloat2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime);
}
}
}
}

View file

@ -1,13 +1,12 @@
using System;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using static CultMath.math;
using cfloat2 = CultMath.float2;
public abstract class MoveToState : BaseState
{
private VelocityLimit _velocityLimit;
protected abstract float2 TargetPosition { get; }
protected abstract cfloat2 TargetPosition { get; }
public float Distance { get; private set; }
protected MoveToState(Agent agent) : base(agent)
@ -16,13 +15,13 @@ public abstract class MoveToState : BaseState
public override void Update(float delta)
{
var diff = TargetPosition - _agent.Ship.Position.xz;
var diff = TargetPosition - _agent.Ship.CultPositionXZ;
var dir = normalize(diff);
Distance = length(diff);
// We want to go top speed in the direction of our target
var desiredVelocity = dir * _agent.TopSpeed;
_agent.Ship.LookDirection = float3(dir.x, 0, dir.y);
_agent.Ship.CultLookDirectionXZ = dir;
_agent.Accelerate(desiredVelocity);
}
}
@ -30,7 +29,7 @@ public abstract class MoveToState : BaseState
public class MoveToEntityState : MoveToState
{
public Entity TargetEntity { get; set; }
protected override float2 TargetPosition => TargetEntity?.Position.xz ?? float2.zero;
protected override cfloat2 TargetPosition => TargetEntity != null ? TargetEntity.CultPositionXZ : cfloat2.zero;
public MoveToEntityState(Agent agent) : base(agent) { }
}
@ -40,5 +39,5 @@ public class MoveToOrbitState : MoveToState
public string OrbitKey { get; set; } = "";
public MoveToOrbitState(Agent agent) : base(agent) { }
protected override float2 TargetPosition => _agent.Ship.Zone.GetOrbitPosition(OrbitKey);
protected override cfloat2 TargetPosition => AetheriaMath.ToCult(_agent.Ship.Zone.GetOrbitPosition(OrbitKey));
}

View file

@ -2,15 +2,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
using cfloat2 = CultMath.float2;
using cfloat3 = CultMath.float3;
public class AetherDrive : Behavior
{
private readonly float3 _rotorDiameter;
private readonly float3 _rotorMass;
private readonly cfloat3 _rotorDiameter;
private readonly cfloat3 _rotorMass;
private readonly PerformanceStat _maximumRpm;
private readonly float3 _couplingLambda;
private readonly cfloat3 _couplingLambda;
private readonly PerformanceStat _lambdaMultiplier;
private readonly PerformanceStat _couplingEfficiency;
private readonly PerformanceStat _torque;
@ -19,15 +20,15 @@ public class AetherDrive : Behavior
private readonly PerformanceStat _passiveCoupling;
private readonly uint _rpmAudioParameter;
private readonly uint _torqueRatioAudioParameter;
private float3 _axis;
private cfloat3 _axis;
public float3 Thrust { get; private set; }
public float3 Rpm { get; private set; }
public cfloat3 Thrust { get; private set; }
public cfloat3 Rpm { get; private set; }
public float MaximumRpm { get; private set; }
public float2 ThrustDirection { get; private set; }
public cfloat2 ThrustDirection { get; private set; }
public string Particles { get; }
public float3 Axis
public cfloat3 Axis
{
get => _axis;
set => _axis = clamp(value, -1, 1);
@ -83,16 +84,17 @@ public class AetherDrive : Behavior
{
var rotorSpeed = Rpm * _rotorDiameter / 100;
var forward = normalize(Entity.Direction);
var right = forward.Rotate(ItemRotation.Clockwise);
var forward = normalize(Entity.CultDirection);
var right = AetheriaMath.Rotate(Entity.CultDirection, ItemRotation.Clockwise);
var velocity = Entity.CultVelocity;
var speed = float2(dot(Entity.Velocity, forward), dot(Entity.Velocity, right));
var speed = float2(dot(velocity, forward), dot(velocity, right));
var couplingEfficiency = Evaluate(_couplingEfficiency);
var efficiency = float3(saturate(1 - speed / max(rotorSpeed.xy, 1) * sign(_axis.xy)) * couplingEfficiency, 1);
Thrust = (Rpm - AetheriaMath.Decay(Rpm, _couplingLambda, dt)) * _rotorMass * efficiency;
var couplingLambda = _couplingLambda * Item.Evaluate(_lambdaMultiplier) * max(abs(_axis), Evaluate(_passiveCoupling));
var couplingLambda = _couplingLambda * Evaluate(_lambdaMultiplier) * max(abs(_axis), Evaluate(_passiveCoupling));
var previousRpm = Rpm;
Rpm = AetheriaMath.Decay(Rpm, couplingLambda, dt);
var rpmLoss = previousRpm - Rpm;
@ -102,13 +104,14 @@ public class AetherDrive : Behavior
AddHeat((heat.x + heat.y + heat.z)*ItemManager.GameplaySettings.AetherHeatMultiplier);
ThrustDirection = forward * (_axis.x * force.x / Entity.Mass) + right * (_axis.y * force.y / Entity.Mass);
Entity.Velocity += ThrustDirection;
Entity.CultVelocity += ThrustDirection;
Entity.Direction = mul(Entity.Direction,
Unity.Mathematics.float2x2.Rotate(force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass));
Entity.CultDirection = AetheriaMath.Rotate(
Entity.CultDirection,
force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass);
if(float.IsNaN(Entity.Velocity.x))
ItemManager.Log("FUCK FUCK FUCK FUCK");
if(float.IsNaN(Entity.CultVelocity.x))
ItemManager.Log("AetherDrive produced NaN velocity.");
MaximumRpm = Evaluate(_maximumRpm);
var torqueProfile = float3(
@ -136,11 +139,11 @@ public class AetherDrive : Behavior
}
public void RestoreRuntimeState(
float3 axis,
float3 thrust,
float3 rpm,
cfloat3 axis,
cfloat3 thrust,
cfloat3 rpm,
float maximumRpm,
float2 thrustDirection)
cfloat2 thrustDirection)
{
Axis = axis;
Thrust = thrust;

View file

@ -1,9 +1,3 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class AutoWeapon : InstantWeapon
{

View file

@ -7,9 +7,9 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Unity;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static Unity.Mathematics.noise;
using static CultMath.math;
using cfloat3 = CultMath.float3;
using cfloat4 = CultMath.float4;
public abstract class Behavior
{
@ -23,7 +23,7 @@ public abstract class Behavior
protected Entity Entity => Item?.Entity ?? Consumable.Entity;
public float Temperature => Item?.Temperature ?? Consumable.Entity.MaxTemp;
public float3 Direction
public cfloat3 Direction
{
get
{
@ -36,12 +36,13 @@ public abstract class Behavior
}
else
{
var itemDirection = Entity.Direction.Rotate(Item.EquippableItem.Rotation);
return float3(itemDirection.x, 0, itemDirection.y);
var itemDirection = AetheriaMath.Rotate(Entity.CultDirection, Item.EquippableItem.Rotation);
return new cfloat3(itemDirection.x, 0, itemDirection.y);
}
}
return float3(Entity.Direction.x, 0, Entity.Direction.y);
var entityDirection = Entity.CultDirection;
return new cfloat3(entityDirection.x, 0, entityDirection.y);
}
}
@ -65,6 +66,18 @@ public abstract class Behavior
public float Evaluate(PerformanceStat stat) => Item?.Evaluate(stat) ?? Consumable.Evaluate(stat);
public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value) =>
Item?.Evaluate(stat, condition, value) ?? Consumable.Evaluate(stat, condition, value);
public float Evaluate(
PerformanceStat stat,
StatConditionMask firstCondition,
float firstValue,
StatConditionMask secondCondition,
float secondValue) =>
Item?.Evaluate(stat, firstCondition, firstValue, secondCondition, secondValue) ??
Consumable.Evaluate(stat, firstCondition, firstValue, secondCondition, secondValue);
public bool TryGetPerformanceStat(string statName, out PerformanceStat stat)
{
if (!string.IsNullOrWhiteSpace(statName) &&
@ -82,6 +95,7 @@ public abstract class Behavior
if (!string.IsNullOrWhiteSpace(statName) && stat != null)
{
_performanceStats[statName] = stat;
Item?.RegisterPerformanceStat(Kind, statName, stat);
}
}
@ -208,7 +222,7 @@ public sealed class RuntimeBehaviorDefinition
return _fields.TryGetValue(key, out var value) ? checked((uint)value.NumberValue) : fallback;
}
public float3 Float3(int key, float3 fallback = default)
public cfloat3 Float3(int key, cfloat3 fallback = default)
{
return _fields.TryGetValue(key, out var value) ? ToFloat3(value) : fallback;
}
@ -228,7 +242,9 @@ public sealed class RuntimeBehaviorDefinition
public PerformanceStat PerformanceStat(int key, PerformanceStat fallback)
{
return _fields.TryGetValue(key, out var value) ? ToPerformanceStat(value) : fallback;
return _fields.TryGetValue(key, out var value)
? AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(value)
: fallback;
}
public BezierCurve BezierCurve(int key, BezierCurve fallback)
@ -238,15 +254,12 @@ public sealed class RuntimeBehaviorDefinition
return fallback;
}
return new BezierCurve
{
Keys = value.Children.Count > 0
? value.Children[0].Children.Select(ToFloat4).ToArray()
: Array.Empty<float4>()
};
return global::BezierCurve.FromKeys(value.Children.Count > 0
? value.Children[0].Children.Select(ToBezierKey)
: Enumerable.Empty<(float, float, float, float)>());
}
public float4[] Float4Array(int key, float4[] fallback)
public cfloat4[] Float4Array(int key, cfloat4[] fallback)
{
return _fields.TryGetValue(key, out var value)
? value.Children.Select(ToFloat4).ToArray()
@ -260,49 +273,29 @@ public sealed class RuntimeBehaviorDefinition
return fallback;
}
return new StatReference
{
Target = ChildString(value, 1),
Stat = ChildString(value, 2)
};
return AetheriaRuntimeBehaviorValueReader.ReadStatReference(value);
}
public T Enum<T>(int key, T fallback) where T : struct
{
if (!_fields.TryGetValue(key, out var value))
{
return fallback;
}
if (!string.IsNullOrWhiteSpace(value.StringValue) &&
System.Enum.TryParse(value.StringValue, true, out T parsed))
{
return parsed;
}
return (T)System.Enum.ToObject(typeof(T), checked((int)value.NumberValue));
return _fields.TryGetValue(key, out var value)
? AetheriaRuntimeBehaviorValueReader.ReadEnum(value, fallback)
: fallback;
}
private static PerformanceStat ToPerformanceStat(AetheriaRuntimeBehaviorValue value)
private static cfloat4 ToFloat4(AetheriaRuntimeBehaviorValue value)
{
return new PerformanceStat
{
Min = ChildFloat(value, 0),
Max = ChildFloat(value, 1),
HeatExponentMultiplier = ChildFloat(value, 2),
DurabilityExponentMultiplier = ChildFloat(value, 3),
QualityExponent = ChildFloat(value, 4)
};
return new cfloat4(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3));
}
private static float4 ToFloat4(AetheriaRuntimeBehaviorValue value)
private static (float, float, float, float) ToBezierKey(AetheriaRuntimeBehaviorValue value)
{
return new float4(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3));
return (ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3));
}
private static float3 ToFloat3(AetheriaRuntimeBehaviorValue value)
private static cfloat3 ToFloat3(AetheriaRuntimeBehaviorValue value)
{
return new float3(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2));
return new cfloat3(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2));
}
private static float ChildFloat(AetheriaRuntimeBehaviorValue value, int index)
@ -314,4 +307,9 @@ public sealed class RuntimeBehaviorDefinition
{
return value.Children.Count > index ? value.Children[index].StringValue ?? "" : "";
}
private static AetheriaRuntimeBehaviorValue ChildValue(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index] : null;
}
}

View file

@ -2,9 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class Capacitor : Behavior
{

View file

@ -1,8 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class ChargedWeapon : InstantWeapon
{
@ -30,9 +29,9 @@ public class ChargedWeapon : InstantWeapon
public override float DamagePerSecond => Damage * _chargeFiringDamageMultiplier / (Cooldown + ChargeTime);
public override float RangeDamagePerSecond(float range)
{
return Damage *
_chargeFiringDamageMultiplier *
DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) /
return EvaluateRangeDamage(range, StatConditionMask.Charge, saturate(_charge)) *
lerp(1, _chargeFiringDamageMultiplier, saturate(_charge)) *
_chargeFiringDamageMultiplier /
(Cooldown + ChargeTime);
}
@ -106,7 +105,8 @@ public class ChargedWeapon : InstantWeapon
ChargeTime = Evaluate(_chargeTime);
ChargeEnergy = Evaluate(_chargeEnergy);
ChargeHeat = Evaluate(_chargeHeat);
Damage *= lerp(1, _chargeFiringDamageMultiplier, saturate(_charge));
Damage = EvaluateDamage(StatConditionMask.Charge, saturate(_charge)) *
lerp(1, _chargeFiringDamageMultiplier, saturate(_charge));
Heat *= lerp(1, _chargeFiringHeatMultiplier, saturate(_charge));
Spread *= lerp(1, _chargeFiringSpreadMultiplier, saturate(_charge));
BurstCount *= lerp(1, _chargeFiringBurstCountMultiplier, saturate(_charge));

View file

@ -2,10 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class Cockpit : Behavior
{
public Cockpit(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item)

View file

@ -3,8 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
{
@ -30,7 +29,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
public override float DamagePerSecond => Damage;
public override float RangeDamagePerSecond(float range)
{
return Damage * DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range)));
return EvaluateRangeDamage(range);
}
public event Action OnReloadBegin;

View file

@ -2,10 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class Cooldown : Behavior, IAlwaysUpdatedBehavior, IProgressBehavior
{

View file

@ -2,10 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class HeatStorage : Behavior
{
public HeatStorage(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item)

View file

@ -1,8 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
{
@ -29,7 +28,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
public override float DamagePerSecond => Damage / Cooldown;
public override float RangeDamagePerSecond(float range)
{
return Damage * DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) / Cooldown;
return EvaluateRangeDamage(range) / Cooldown;
}
public override int Ammo

View file

@ -1,8 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class LockWeapon : InstantWeapon
{
@ -82,10 +79,11 @@ public class LockWeapon : InstantWeapon
DirectionImpact = Evaluate(_directionImpact);
Decay = Evaluate(_decay);
var degrees = acos(dot(normalize(Entity.Target.Value.Position - Entity.Position), normalize(Entity.LookDirection))) * 57.2958f;
if (degrees < LockAngle)
var targetDirection = Entity.Target.Value.CultPosition - Entity.CultPosition;
var angleDegrees = AetheriaMath.AngleDegrees(targetDirection, Entity.CultLookDirection);
if (angleDegrees < LockAngle)
{
var lerp = 1 - unlerp(0, 90, degrees);
var lerp = 1 - unlerp(0, 90, angleDegrees);
_lock = saturate(_lock + pow(lerp, DirectionImpact) * dt * LockSpeed * pow(Entity.EntityInfoGathered[Entity.Target.Value], SensorImpact));
}
else _lock = saturate(_lock - dt * Decay);

View file

@ -2,10 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class MiningTool : Behavior
{
@ -45,10 +42,11 @@ public class MiningTool : Behavior
public override bool Execute(float dt)
{
Range = Evaluate(_range);
if (Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt) &&
!string.IsNullOrWhiteSpace(AsteroidBeltBodyKey) &&
if (!string.IsNullOrWhiteSpace(AsteroidBeltBodyKey) &&
Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt) &&
Entity.Zone.AsteroidExists(AsteroidBeltBodyKey, Asteroid) &&
length(Entity.Position.xz - belt.Transforms[Asteroid].xy) - belt.Transforms[Asteroid].w < Range)
Entity.Zone.TryGetCultAsteroidTransform(AsteroidBeltBodyKey, Asteroid, out var asteroidPosition, out var asteroidRadius) &&
length(Entity.CultPositionXZ - asteroidPosition) - asteroidRadius < Range)
{
Entity.Zone.MineAsteroid(
Entity,

View file

@ -2,8 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class Radiator : Behavior, IAlwaysUpdatedBehavior, IInitializableBehavior
{

View file

@ -6,8 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class Reactor : Behavior, IOrderedBehavior, IDisposable
{

View file

@ -2,9 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class Reflector : Behavior
{
private readonly PerformanceStat _crossSection;
@ -23,7 +20,7 @@ public class Reflector : Behavior
public override bool Execute(float dt)
{
Entity.VisibilitySources[this] = Evaluate(_crossSection) * Entity.Zone.GetLight(Entity.Position.xz);
Entity.VisibilitySources[this] = Evaluate(_crossSection) * Entity.Zone.GetLight(Entity.CultPositionXZ);
return true;
}

View file

@ -3,9 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
{
@ -60,9 +58,8 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
{
if (Entity.Zone.TryGetAsteroidBelt(ScanTargetBodyKey, out var belt))
{
if (Asteroid > -1 &&
belt.ContainsAsteroid(Asteroid) &&
length(Entity.Position.xz - belt.Transforms[Asteroid].xy) < Range)
if (Entity.Zone.TryGetCultAsteroidTransform(ScanTargetBodyKey, Asteroid, out var asteroidPosition, out _) &&
length(Entity.CultPositionXZ - asteroidPosition) < Range)
{
_scanTime += dt;
if (_scanTime > ScanDuration)
@ -75,7 +72,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
}
else if (Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet))
{
if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planet.OrbitKey)) < Range)
if(length(Entity.CultPositionXZ - AetheriaMath.ToCult(Entity.Zone.GetOrbitPosition(planet.OrbitKey))) < Range)
{
_scanTime += dt;
if (_scanTime > ScanDuration)

View file

@ -4,8 +4,7 @@
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class Sensor : Behavior, IEventBehavior
{
@ -117,12 +116,13 @@ public class Sensor : Behavior, IEventBehavior
// TODO: Handle Active Detection / Visibility From Reflected Radiance
var forward = Direction.xz;
var entityPosition = Entity.CultPositionXZ;
foreach (var entity in Entity.Zone.Entities)
{
if (entity == Entity) continue;
var diff = entity.Position.xz - Entity.Position.xz;
var angle = acos(dot(forward, normalize(diff)));
var diff = entity.CultPositionXZ - entityPosition;
var angle = AetheriaMath.AngleRadians(forward, diff);
var dist = length(diff);
float previous, next;
Entity.EntityInfoGathered.TryGetValue(entity, out previous);

View file

@ -2,9 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class Shield : Behavior, IProgressBehavior
{
public float Efficiency { get; private set; }

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Unity;
@ -14,14 +15,14 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
private readonly StatModifierType _type;
private readonly string _requiredBehaviorKind;
private PerformanceStat[] _stats;
private StatModifierTarget[] _targets;
private bool _applied;
private bool _executed;
public bool Applied => _applied;
public bool Executed => _executed;
public int TargetStatCount => _stats?.Length ?? 0;
public int TargetStatCount => _targets?.Length ?? 0;
public StatModifier(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item)
{
@ -47,13 +48,19 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
public void Initialize()
{
_stats = Entity.Equipment
.Where(HasRequiredBehavior)
.SelectMany(gear => gear.Behaviors ?? Array.Empty<Behavior>())
.Where(behavior => BehaviorKindMatches(behavior.Kind, _targetBehaviorKind))
.Select(FindTargetStat)
.Where(stat => stat != null)
.ToArray();
var targets = new List<StatModifierTarget>();
foreach (var gear in Entity.Equipment.Where(HasRequiredBehavior))
foreach (var behavior in gear.Behaviors ?? Array.Empty<Behavior>())
{
if (!BehaviorKindMatches(behavior.Kind, _targetBehaviorKind))
continue;
var stat = FindTargetStat(behavior);
if (stat != null)
targets.Add(new StatModifierTarget(gear, stat));
}
_targets = targets.ToArray();
}
private PerformanceStat FindTargetStat(Behavior behavior)
@ -91,17 +98,21 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
private void ApplyModifier()
{
_applied = true;
foreach (var stat in _stats)
(_type == StatModifierType.Constant
? stat.GetConstantModifiers(Entity)
: stat.GetScaleModifiers(Entity))[this] = Evaluate(_modifier);
RefreshModifier();
}
private void RemoveModifier()
{
_applied = false;
foreach (var stat in _stats)
(_type == StatModifierType.Constant ? stat.GetConstantModifiers(Entity) : stat.GetScaleModifiers(Entity)).Remove(this);
foreach (var target in _targets ?? Array.Empty<StatModifierTarget>())
target.Item.RemoveStatModifier(target.Stat, this, _type);
}
private void RefreshModifier()
{
var value = Evaluate(_modifier);
foreach (var target in _targets ?? Array.Empty<StatModifierTarget>())
target.Item.SetStatModifier(target.Stat, this, _type, value);
}
public override bool Execute(float dt)
@ -120,6 +131,8 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
{
if(_executed && !_applied)
ApplyModifier();
else if(_executed && _applied)
RefreshModifier();
if(!_executed && _applied)
RemoveModifier();
_executed = false;
@ -127,14 +140,28 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
public void RestoreRuntimeState(bool applied, bool executed)
{
if (_stats == null)
if (_targets == null)
Initialize();
if (applied && !_applied)
ApplyModifier();
if (applied && _applied)
RefreshModifier();
if (!applied && _applied)
RemoveModifier();
_executed = executed;
}
private readonly struct StatModifierTarget
{
public StatModifierTarget(EquippedItem item, PerformanceStat stat)
{
Item = item;
Stat = stat;
}
public EquippedItem Item { get; }
public PerformanceStat Stat { get; }
}
}
public enum StatModifierType

View file

@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
using cfloat2 = CultMath.float2;
public class Thruster : Behavior, IAnalogBehavior
{
@ -40,7 +40,7 @@ public class Thruster : Behavior, IAnalogBehavior
var hullCenter = hullShape.CenterOfMass;
var itemCenter = hullShape.Inset(itemShape, item.Position, item.EquippableItem.Rotation).CenterOfMass;
var toCenter = hullCenter - itemCenter;
Torque = -dot(normalize(toCenter), float2(1, 0).Rotate(item.EquippableItem.Rotation));
Torque = -dot(normalize(toCenter), AetheriaMath.Rotate(new cfloat2(1, 0), item.EquippableItem.Rotation));
Thrust = Evaluate(_thrust);
}
@ -65,9 +65,10 @@ public class Thruster : Behavior, IAnalogBehavior
if(_input > .01f && Entity.TryConsumeEnergy(_input * Evaluate(_energyUsage)))
{
Thrust = Evaluate(_thrust);
Entity.Velocity -= Direction.xz * _input * Thrust / Entity.Mass * dt;
Entity.Direction = mul(Entity.Direction,
Unity.Mathematics.float2x2.Rotate(_input * Torque * Thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass * dt));
Entity.CultVelocity -= Direction.xz * _input * Thrust / Entity.Mass * dt;
Entity.CultDirection = AetheriaMath.Rotate(
Entity.CultDirection,
_input * Torque * Thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass * dt);
AddHeat(_input * Evaluate(_heat) * dt);
var vis = _input * Evaluate(_visibility);
if (!Entity.VisibilitySources.TryGetValue(this, out var existingVisibility) || vis > existingVisibility)

View file

@ -5,9 +5,9 @@
using System.Collections.Generic;
using System.Linq;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float3 = Unity.Mathematics.float3;
using static CultMath.math;
using cfloat3 = CultMath.float3;
public class TurretController : Behavior, IInitializableBehavior
{
private List<Weapon> _weapons = new List<Weapon>();
@ -44,27 +44,29 @@ public class TurretController : Behavior, IInitializableBehavior
{
if (Entity.Target.Value != null)
{
var diff = Entity.Target.Value.Position - Entity.Position;
var entityPosition = Entity.CultPosition;
var targetPosition = Entity.Target.Value.CultPosition;
var diff = targetPosition - entityPosition;
if (_predictShots)
{
var targetHull = Entity.ItemManager.GetRuntimeItem(Entity.Target.Value.Hull);
var targetVelocity = float3(Entity.Target.Value.Velocity.x, 0, Entity.Target.Value.Velocity.y);
var targetVelocity = new cfloat3(Entity.Target.Value.CultVelocity.x, 0, Entity.Target.Value.CultVelocity.y);
var predictedPosition = AetheriaMath.FirstOrderIntercept(
Entity.Position, float3.zero, _shotSpeed,
Entity.Target.Value.Position, targetVelocity
entityPosition, cfloat3.zero, _shotSpeed,
targetPosition, targetVelocity
);
predictedPosition.y = Entity.Zone.GetHeight(predictedPosition.xz) + (float)(targetHull?.HullGridOffset ?? 0);
Entity.LookDirection = normalize(predictedPosition - Entity.Position);
Entity.CultLookDirection = normalize(predictedPosition - entityPosition);
}
else
Entity.LookDirection = normalize(diff);
Entity.CultLookDirection = normalize(diff);
var dist = length(diff);
foreach (var x in _weapons)
{
var fire = dot(
x.Direction,
Entity.LookDirection) > .99f;
Entity.CultLookDirection) > .99f;
if (x.EvaluateRange() > dist && fire)
{
x.Activate();

View file

@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class VelocityConversion : Behavior
{
private readonly PerformanceStat _lambda;
@ -22,7 +22,8 @@ public class VelocityConversion : Behavior
public override bool Execute(float dt)
{
Entity.Velocity = AetheriaMath.Damp(Entity.Velocity, Entity.Direction * length(Entity.Velocity), Evaluate(_lambda), dt);
var velocity = Entity.CultVelocity;
Entity.CultVelocity = AetheriaMath.Damp(velocity, Entity.CultDirection * length(velocity), Evaluate(_lambda), dt);
return true;
}
}

View file

@ -2,8 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
public class VelocityLimit : Behavior
{
@ -28,8 +27,9 @@ public class VelocityLimit : Behavior
public override bool Execute(float dt)
{
Limit = Evaluate(_topSpeed);
if (length(Entity.Velocity) > Limit)
Entity.Velocity = normalize(Entity.Velocity) * Limit;
var velocity = Entity.CultVelocity;
if (length(velocity) > Limit)
Entity.CultVelocity = normalize(velocity) * Limit;
return true;
}

View file

@ -4,8 +4,8 @@
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
using float4 = CultMath.float4;
public abstract class Weapon : Behavior, IActivatedBehavior
{
@ -156,6 +156,25 @@ public abstract class Weapon : Behavior, IActivatedBehavior
Velocity = Evaluate(_velocity);
}
protected float EvaluateRangeDamage(float range)
{
var normalizedRange = saturate(unlerp(MinRange, Range, range));
return Evaluate(_damage, StatConditionMask.Range, normalizedRange) *
DamageCurve.Evaluate(normalizedRange);
}
protected float EvaluateRangeDamage(float range, StatConditionMask condition, float value)
{
var normalizedRange = saturate(unlerp(MinRange, Range, range));
return Evaluate(_damage, StatConditionMask.Range, normalizedRange, condition, value) *
DamageCurve.Evaluate(normalizedRange);
}
protected float EvaluateDamage(StatConditionMask condition, float value)
{
return Evaluate(_damage, condition, value);
}
public override bool Execute(float dt)
{
UpdateStats();

View file

@ -2,10 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class Wear : Behavior
{
private readonly bool _perSecond;

View file

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using CultMath;
using static CultMath.math;
public class BezierCurve
{
@ -13,6 +14,16 @@ public class BezierCurve
private float? _maximum;
public static BezierCurve FromKeys(IEnumerable<(float time, float value, float inTangent, float outTangent)> keys)
{
return new BezierCurve
{
Keys = keys
.Select(key => float4(key.time, key.value, key.inTangent, key.outTangent))
.ToArray()
};
}
public float Maximum
{
get

View file

@ -2,15 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
using cfloat3 = CultMath.float3;
using cfloat4 = CultMath.float4;
// Thanks to Ian Taylor at https://www.chilliant.com/rgb2hsv.html
public static class ColorMath
{
public static float3 HueToRgb(in float h)
public static cfloat3 HueToRgb(in float h)
{
float r = abs(h * 6 - 3) - 1;
float g = 2 - abs(h * 6 - 2);
@ -18,40 +17,40 @@ public static class ColorMath
return saturate(float3(r,g,b));
}
public static float3 HsvToRgb(in float3 hsv)
public static cfloat3 HsvToRgb(in cfloat3 hsv)
{
float3 rgb = HueToRgb(hsv.x);
cfloat3 rgb = HueToRgb(hsv.x);
return ((rgb - 1) * hsv.y + 1) * hsv.z;
}
public static float3 HslToRgb(in float3 hsl)
public static cfloat3 HslToRgb(in cfloat3 hsl)
{
float3 rgb = HueToRgb(hsl.x);
cfloat3 rgb = HueToRgb(hsl.x);
float c = (1 - abs(2 * hsl.z - 1)) * hsl.y;
return (rgb - 0.5f) * c + hsl.z;
}
const float Epsilon = 1e-10f;
public static float3 RgbToHcv(in float3 rgb)
public static cfloat3 RgbToHcv(in cfloat3 rgb)
{
// Based on work by Sam Hocevar and Emil Persson
float4 p = (rgb.y < rgb.z) ? float4(rgb.zy, -1.0f, 2.0f/3.0f) : float4(rgb.zy, 0.0f, -1.0f/3.0f);
float4 q = (rgb.x < p.x) ? float4(p.xyw, rgb.x) : float4(rgb.x, p.yzx);
cfloat4 p = (rgb.y < rgb.z) ? float4(rgb.zy, -1.0f, 2.0f/3.0f) : float4(rgb.zy, 0.0f, -1.0f/3.0f);
cfloat4 q = (rgb.x < p.x) ? float4(p.xyw, rgb.x) : float4(rgb.x, p.yzx);
float c = q.x - min(q.w, q.y);
float h = abs((q.w - q.y) / (6 * c + Epsilon) + q.z);
return float3(h, c, q.x);
}
public static float3 RgbToHsv(in float3 rgb)
public static cfloat3 RgbToHsv(in cfloat3 rgb)
{
float3 hcv = RgbToHcv(rgb);
cfloat3 hcv = RgbToHcv(rgb);
float s = hcv.y / (hcv.z + Epsilon);
return float3(hcv.x, s, hcv.z);
}
public static float3 RgbToHsl(in float3 rgb)
public static cfloat3 RgbToHsl(in cfloat3 rgb)
{
float3 hcv = RgbToHcv(rgb);
cfloat3 hcv = RgbToHcv(rgb);
float l = hcv.z - hcv.y * 0.5f;
float s = hcv.y / (1 - abs(l * 2 - 1) + Epsilon);
return float3(hcv.x, s, l);

View file

@ -5,7 +5,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using float3 = CultMath.float3;
public class Faction : INamedEntry
{

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);
}
}

View file

@ -1,8 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using int2 = Unity.Mathematics.int2;
public static class EntityConstructionBlueprintProjector
{
@ -19,8 +19,8 @@ public static class EntityConstructionBlueprintProjector
else if (entity is Ship ship)
blueprint = new ShipConstructionBlueprint
{
Position = ship.Position,
Direction = ship.Direction,
Position = AetheriaMath.ToUnity(ship.CultPosition),
Direction = AetheriaMath.ToUnity(ship.CultDirection),
IsPlayerShip = ship.IsPlayerShip
};
else throw new ArgumentException("Attempted to capture an instance of abstract class Entity!");
@ -41,30 +41,40 @@ public static class EntityConstructionBlueprintProjector
return blueprint;
}
public static Entity InstantiateFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint, bool instantiate = false)
public static Entity InstantiateAuthoritativeFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint)
{
return BuildFromBlueprint(itemManager, zone, blueprint, false);
}
public static Entity ProjectObservedFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint)
{
return BuildFromBlueprint(itemManager, zone, blueprint, true);
}
private static Entity BuildFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint, bool instantiate)
{
blueprint.Settings ??= itemManager.GameplaySettings.DefaultEntitySettings.Copy();
return blueprint switch
{
ShipConstructionBlueprint shipBlueprint => InstantiateFromBlueprint(itemManager, zone, shipBlueprint, instantiate),
OrbitalEntityConstructionBlueprint orbitalBlueprint => InstantiateFromBlueprint(itemManager, zone, orbitalBlueprint, instantiate),
ShipConstructionBlueprint shipBlueprint => BuildFromBlueprint(itemManager, zone, shipBlueprint, instantiate),
OrbitalEntityConstructionBlueprint orbitalBlueprint => BuildFromBlueprint(itemManager, zone, orbitalBlueprint, instantiate),
_ => null
};
}
private static Ship InstantiateFromBlueprint(ItemManager itemManager, Zone zone, ShipConstructionBlueprint blueprint, bool instantiate = false)
private static Ship BuildFromBlueprint(ItemManager itemManager, Zone zone, ShipConstructionBlueprint blueprint, bool instantiate)
{
var entity = new Ship(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(blueprint.Hull) : blueprint.Hull, blueprint.Settings);
Restore(itemManager, zone, blueprint, entity, instantiate);
entity.Position = blueprint.Position;
entity.Direction = blueprint.Direction;
entity.CultPosition = AetheriaMath.ToCult(blueprint.Position);
entity.CultDirection = AetheriaMath.ToCult(blueprint.Direction);
entity.IsPlayerShip = blueprint.IsPlayerShip;
return entity;
}
private static OrbitalEntity InstantiateFromBlueprint(ItemManager itemManager, Zone zone, OrbitalEntityConstructionBlueprint blueprint, bool instantiate = false)
private static OrbitalEntity BuildFromBlueprint(ItemManager itemManager, Zone zone, OrbitalEntityConstructionBlueprint blueprint, bool instantiate)
{
var entity = new OrbitalEntity(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(blueprint.Hull) : blueprint.Hull, blueprint.OrbitKey, blueprint.Settings);
Restore(itemManager, zone, blueprint, entity, instantiate);
@ -80,7 +90,7 @@ public static class EntityConstructionBlueprintProjector
entity.Faction = ResolveFaction(zone?.Galaxy, blueprint);
entity.Children = blueprint.Children.Select(c =>
{
var child = InstantiateFromBlueprint(itemManager, zone, c, instantiate);
var child = BuildFromBlueprint(itemManager, zone, c, instantiate);
child.Parent = entity;
return child;
}).ToList();

View file

@ -1,8 +1,7 @@
using System;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static Unity.Mathematics.noise;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using unitynoise = Unity.Mathematics.noise;
[Serializable]
public class ZoneEnvironment
@ -78,8 +77,8 @@ public abstract class Brush
float powerPulse( float x, float power )
{
x = saturate(abs(x))-.001f;
return pow((x + 1.0f) * (1.0f - x), power);
x = Saturate(MathF.Abs(x))-.001f;
return MathF.Pow((x + 1.0f) * (1.0f - x), power);
}
protected abstract float Evaluate(float2 world, float2 uv);
@ -87,10 +86,26 @@ public abstract class Brush
public float Evaluate(float2 world, float2 pos, float2 radius)
{
var uv = (world - pos) / radius;
float dist = length(uv)*2;
float envelope = min(Cutoff, powerPulse(dist,EnvelopeExponent)) * smoothstep(1, .95f, dist);
float dist = Length(uv)*2;
float envelope = MathF.Min(Cutoff, powerPulse(dist,EnvelopeExponent)) * SmoothStep(1, .95f, dist);
return Depth * Evaluate(world, uv) * envelope;
}
private static float Length(float2 value)
{
return MathF.Sqrt(value.x * value.x + value.y * value.y);
}
private static float Saturate(float value)
{
return value < 0 ? 0 : value > 1 ? 1 : value;
}
private static float SmoothStep(float edge0, float edge1, float x)
{
var t = Saturate((x - edge0) / (edge1 - edge0));
return t * t * (3.0f - 2.0f * t);
}
}
[Serializable]
@ -124,8 +139,8 @@ public class SimplexBrush : TextureBrush
protected override float Evaluate(float2 world, float2 uv)
{
var noise = snoise(world * Frequency + Phase);
return AbsoluteValue ? abs(noise) : noise;
var noise = unitynoise.snoise(world * Frequency + Phase);
return AbsoluteValue ? MathF.Abs(noise) : noise;
}
}
@ -136,8 +151,8 @@ public class AnimatedSimplexBrush : AnimatedBrush
protected override float Evaluate(float2 world, float2 uv)
{
var noise = snoise(float3(float2(world * Frequency + Phase), AnimationSpeed * Time));
return AbsoluteValue ? abs(noise) : noise;
var noise = unitynoise.snoise(new float3(world * Frequency + Phase, AnimationSpeed * Time));
return AbsoluteValue ? MathF.Abs(noise) : noise;
}
}
@ -148,8 +163,8 @@ public class RadialWaveBrush : TextureBrush
protected override float Evaluate(float2 world, float2 uv)
{
float dist = length(uv);
float ang = atan2(uv.y,uv.x);
return cos((ang + Phase.x) * Frequency.x * PI + (pow(dist, WaveExponent) + Phase.y) * Frequency.y);
float dist = MathF.Sqrt(uv.x * uv.x + uv.y * uv.y);
float ang = MathF.Atan2(uv.y,uv.x);
return MathF.Cos((ang + Phase.x) * Frequency.x * MathF.PI + (MathF.Pow(dist, WaveExponent) + Phase.y) * Frequency.y);
}
}

View file

@ -1,12 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* 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/. */
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
[Serializable]
public class ExponentialCurve

View file

@ -6,11 +6,8 @@ using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Text.RegularExpressions;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Random = Unity.Mathematics.Random;
using Unity.Tiny;
using float2 = Unity.Mathematics.float2;
using float2 = CultMath.float2;
using Random = CultMath.Random;
public static class Extensions
{
@ -79,13 +76,13 @@ public static class Extensions
switch (rotation)
{
case ItemRotation.None:
return float2(0, 1);
return new float2(0, 1);
case ItemRotation.CounterClockwise:
return float2(-1, 0);
return new float2(-1, 0);
case ItemRotation.Reversed:
return float2(0, -1);
return new float2(0, -1);
case ItemRotation.Clockwise:
return float2(1, 0);
return new float2(1, 0);
default:
throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null);
}
@ -98,11 +95,11 @@ public static class Extensions
case ItemRotation.None:
return v;
case ItemRotation.CounterClockwise:
return float2(-v.y, v.x);
return new float2(-v.y, v.x);
case ItemRotation.Reversed:
return float2(-v.x, -v.y);
return new float2(-v.x, -v.y);
case ItemRotation.Clockwise:
return float2(v.y, -v.x);
return new float2(v.y, -v.x);
default:
throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null);
}
@ -112,9 +109,10 @@ public static class Extensions
//private static Random Random => (Random) (_random ??= new Random((uint) (DateTime.Now.Ticks%uint.MaxValue)));
// public static T RandomElement<T>(this IEnumerable<T> enumerable) => enumerable.ElementAt(Random.NextInt(0, enumerable.Count()));
public static float NextPowerDistribution(this ref Random random, float min, float max, float exp, float randexp) =>
pow((pow(max, exp + 1) - pow(min, exp + 1)) * pow(random.NextFloat(), randexp) + pow(min, exp + 1), 1 / (exp + 1));
MathF.Pow((MathF.Pow(max, exp + 1) - MathF.Pow(min, exp + 1)) * MathF.Pow(random.NextFloat(), randexp) + MathF.Pow(min, exp + 1), 1 / (exp + 1));
public static float NextUnbounded(this ref Random random) => 1 / (1 - random.NextFloat()) - 1;
public static float NextUnbounded(this ref Random random, float bias, float power, float ceiling) => 1 / (1 - pow(min(random.NextFloat(), ceiling), 1 - pow(clamp(bias,0,.99f), 1 / power))) - 1;
public static float NextUnbounded(this ref Random random, float bias, float power, float ceiling) =>
1 / (1 - MathF.Pow(MathF.Min(random.NextFloat(), ceiling), 1 - MathF.Pow(Clamp(bias, 0, .99f), 1 / power))) - 1;
public static bool IsDefault<T>(this T value) where T : struct
{
@ -130,8 +128,23 @@ public static class Extensions
public static float Angle(this float2 from, float2 to)
{
var num = sqrt(lengthsq(from) * lengthsq(to));
return num < 1.00000000362749E-15 ? 0.0f : acos(clamp(dot(from, to) / num, -1f, 1f)) * 57.29578f;
var num = MathF.Sqrt(LengthSquared(from) * LengthSquared(to));
return num < 1.00000000362749E-15 ? 0.0f : MathF.Acos(Clamp(Dot(from, to) / num, -1f, 1f)) * 57.29578f;
}
private static float Clamp(float value, float min, float max)
{
return value < min ? min : value > max ? max : value;
}
private static float Dot(float2 a, float2 b)
{
return a.x * b.x + a.y * b.y;
}
private static float LengthSquared(float2 value)
{
return Dot(value, value);
}
}

View file

@ -10,8 +10,7 @@ using Ink.Runtime;
using MIConvexHull;
using JM.LinqFaster;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using Random = Unity.Mathematics.Random;
public class Galaxy
@ -85,11 +84,144 @@ public class Galaxy
CalculateFactionInfluence(progressCallback);
GenerateNames(nameGeneratorSettings, ref random, progressCallback);
var nameRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue));
GenerateNames(nameGeneratorSettings, ref nameRandom, progressCallback);
progressCallback?.Invoke("Done!");
if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao
}
public static Galaxy ProjectObservedDaemonRun(
AetheriaRuntimeRunCheckpointCommit run,
SectorBackgroundSettings background,
AetheriaRuntimeCatalogSnapshot runtimeCatalog,
Action<string> log)
{
return new Galaxy(run, background, runtimeCatalog, log);
}
private Galaxy(
AetheriaRuntimeRunCheckpointCommit run,
SectorBackgroundSettings background,
AetheriaRuntimeCatalogSnapshot runtimeCatalog,
Action<string> log)
{
if (run == null) throw new ArgumentNullException(nameof(run));
_runtimeCatalog = runtimeCatalog ?? throw new InvalidOperationException("Daemon-observed galaxy requires the typed Aetheria runtime catalog.");
_allFactions = ProjectFactions(_runtimeCatalog);
IsPrelude = run.IsTutorial;
Background = background;
Log = log;
GenerationSeed = run.GenerationSeed;
NameGeneratorSettings = null;
Factions = ResolveDaemonFactions(run);
foreach (var faction in Factions)
FactionRelationships[faction] = ResolveDaemonFactionRelationship(run, faction);
Zones = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
.Where(zone => zone != null && zone.ZoneIndex >= 0)
.OrderBy(zone => zone.ZoneIndex)
.Select(zone => new GalaxyZone
{
Name = string.IsNullOrWhiteSpace(zone.Name) ? $"Daemon Zone {zone.ZoneIndex}" : zone.Name,
Position = new float2((float)zone.PositionX, (float)zone.PositionY),
Factions = ResolveDaemonZoneFactions(zone),
Owner = ResolveDaemonFactionByIndex(zone.OwnerFactionIndex),
NamedZone = !string.IsNullOrWhiteSpace(zone.Name)
})
.ToArray();
var zonesByIndex = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
.Where(zone => zone != null && zone.ZoneIndex >= 0)
.OrderBy(zone => zone.ZoneIndex)
.Select((zone, ordinal) => new { zone.ZoneIndex, Zone = Zones[ordinal] })
.ToDictionary(pair => pair.ZoneIndex, pair => pair.Zone);
foreach (var source in run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
{
if (source == null || !zonesByIndex.TryGetValue(source.ZoneIndex, out var sourceZone))
continue;
foreach (var adjacentIndex in source.AdjacentZoneIndices ?? Array.Empty<int>())
{
if (zonesByIndex.TryGetValue(adjacentIndex, out var adjacentZone) &&
!sourceZone.AdjacentZones.Contains(adjacentZone))
{
sourceZone.AdjacentZones.Add(adjacentZone);
}
}
}
Entrance = ResolveDaemonZone(run.EntranceZoneIndex) ?? ResolveDaemonZone(run.CurrentZoneIndex) ?? Zones.FirstOrDefault();
Exit = ResolveDaemonZone(run.ExitZoneIndex);
foreach (var zone in run.DiscoveredZoneIndices ?? Array.Empty<int>())
{
var discovered = ResolveDaemonZone(zone);
if (discovered != null)
DiscoveredZones.Add(discovered);
}
if (DiscoveredZones.Count == 0 && Entrance != null)
DiscoveredZones.Add(Entrance);
CalculateDistanceMatrix();
GalaxyZone ResolveDaemonZone(int zoneIndex)
{
return zonesByIndex.TryGetValue(zoneIndex, out var zone) ? zone : null;
}
}
private Faction[] ResolveDaemonFactions(AetheriaRuntimeRunCheckpointCommit run)
{
var factionKeys = new HashSet<string>(
(run.FactionRelationships ?? Array.Empty<AetheriaRuntimeFactionRelationshipCommit>())
.Select(relationship => relationship?.FactionKey)
.Where(key => !string.IsNullOrWhiteSpace(key)),
StringComparer.OrdinalIgnoreCase);
var factionIndices = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
.Where(zone => zone != null)
.SelectMany(zone => (zone.FactionIndices ?? Array.Empty<int>()).Concat(new[] { zone.OwnerFactionIndex }))
.Where(index => index >= 0)
.Distinct()
.ToArray();
foreach (var index in factionIndices)
{
if (index >= 0 && index < _allFactions.Length)
factionKeys.Add(_allFactions[index].FactionKey);
}
var factions = _allFactions
.Where(faction => factionKeys.Contains(faction.FactionKey))
.ToArray();
return factions.Length == 0 ? _allFactions : factions;
}
private Faction[] ResolveDaemonZoneFactions(AetheriaRuntimeZoneSnapshotCommit zone)
{
return (zone.FactionIndices ?? Array.Empty<int>())
.Select(ResolveDaemonFactionByIndex)
.Where(faction => faction != null)
.ToArray();
}
private Faction ResolveDaemonFactionByIndex(int index)
{
return index >= 0 && index < _allFactions.Length ? _allFactions[index] : null;
}
private FactionRelationship ResolveDaemonFactionRelationship(
AetheriaRuntimeRunCheckpointCommit run,
Faction faction)
{
var relationship = (run.FactionRelationships ?? Array.Empty<AetheriaRuntimeFactionRelationshipCommit>())
.FirstOrDefault(candidate => string.Equals(candidate?.FactionKey ?? "", faction?.FactionKey ?? "", StringComparison.OrdinalIgnoreCase));
return Enum.TryParse<FactionRelationship>(relationship?.Relationship ?? "", out var parsed)
? parsed
: FactionRelationship.Neutral;
}
public Faction ResolveFaction(string name)
{
@ -161,15 +293,15 @@ public class Galaxy
.MaxBy(z => ConnectedRegion(z, antagonistFaction.InfluenceDistance).Count * z.Distance[HomeZones[protagonistFaction]]);
HomeZones[protagonistFaction] = Zones
.MaxBy(z => ConnectedRegion(z, protagonistFaction.InfluenceDistance).Count * sqrt(z.Distance[HomeZones[antagonistFaction]]));
.MaxBy(z => ConnectedRegion(z, protagonistFaction.InfluenceDistance).Count * MathF.Sqrt(z.Distance[HomeZones[antagonistFaction]]));
// var antagonistRegion = ConnectedRegion(HomeZones[antagonistFaction], antagonistFaction.InfluenceDistance);
// var protagonistRegion = ConnectedRegion(HomeZones[protagonistFaction], protagonistFaction.InfluenceDistance);
// Place the buffer faction in a zone where it has equal distance to the pro/antagonist HQs and where it can control the most territory
var bufferDistance = Zones.Min(z => abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]));
var bufferDistance = Zones.Min(z => Math.Abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]));
var potentialBufferZones = Zones
.Where(z => abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]) == bufferDistance);
.Where(z => Math.Abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]) == bufferDistance);
HomeZones[bufferFaction] = potentialBufferZones.MaxBy(z => ConnectedRegion(z, bufferFaction.InfluenceDistance).Count);
// Place neutral headquarters away from existing factions while also maximizing territory
@ -177,7 +309,7 @@ public class Galaxy
{
HomeZones[faction] = Zones.MaxBy(z =>
ConnectedRegion(z, faction.InfluenceDistance).Count *
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])));
}
CalculateFactionInfluence(progressCallback);
@ -201,7 +333,8 @@ public class Galaxy
CalculateFactionInfluence(progressCallback);
GenerateNames(nameGeneratorSettings, ref random, progressCallback);
var nameRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue));
GenerateNames(nameGeneratorSettings, ref nameRandom, progressCallback);
// progressCallback?.Invoke("Weaving Narrative");
// var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random, Log);
@ -216,7 +349,7 @@ public class Galaxy
var outputSamples = WeightedSampleElimination.GeneratePoints(zoneCount,
ref random,
Background.CloudDensity,
v => (.2f - lengthsq(v - float2(.5f))) * 4,
v => (.2f - LengthSquared(v - new float2(.5f, .5f))) * 4,
progressCallback);
return outputSamples.Select(v => new GalaxyZone {Position = v}).ToArray();
}
@ -305,17 +438,17 @@ public class Galaxy
HomeZones[mega] = ConnectedRegion(BossZones[mega], mega.InfluenceDistance)
.MaxBy(z =>
ConnectedRegion(z, mega.InfluenceDistance).Count *
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])));
}
// Place remaining headquarters away from existing megas while also maximizing territory
foreach (var mega in Factions.Where(m => !bossMegas.Contains(m)))
{
HomeZones[mega] = Zones.MaxBy(z =>
pow(ConnectedRegion(z, mega.InfluenceDistance).Count, HomeZones.Count) *
MathF.Pow(ConnectedRegion(z, mega.InfluenceDistance).Count, HomeZones.Count) *
Exit.Distance[z] * Entrance.Distance[z] *
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])) *
BossZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])) *
BossZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])));
}
}
@ -343,7 +476,7 @@ public class Galaxy
}
private void GenerateNames(NameGeneratorSettings nameGeneratorSettings,
ref Random random,
ref CultMath.Random random,
Action<string> progressCallback = null)
{
for (var i = 0; i < Factions.Length; i++)
@ -416,8 +549,8 @@ public class Galaxy
float LinkWeight((GalaxyZone, GalaxyZone) link)
{
return 1 / saturate(Background.CloudDensity((link.Item1.Position + link.Item2.Position) / 2)) *
lengthsq(link.Item1.Position - link.Item2.Position) *
return 1 / Saturate(Background.CloudDensity((link.Item1.Position + link.Item2.Position) / 2)) *
LengthSquared(link.Item1.Position - link.Item2.Position) *
(link.Item1.AdjacentZones.Count - 1) * (link.Item2.AdjacentZones.Count - 1);
}
@ -583,12 +716,22 @@ public class Galaxy
if (!bestFirst) zonesToSearch = zonesToSearch.Where(z => !searched.Contains(z));
foreach (var dijkstraStar in zonesToSearch
// Cost is parent cost plus distance squared
.Select(zone => new DijkstraVertex {Parent = s, Zone = zone, Cost = s.Cost + lengthsq(s.Zone.Position - zone.Position)}))
.Select(zone => new DijkstraVertex {Parent = s, Zone = zone, Cost = s.Cost + LengthSquared(s.Zone.Position - zone.Position)}))
// Add new member to list, sorted by cost plus optional heuristic
unsearchedNodes.PushObj(dijkstraStar, bestFirst ? dijkstraStar.Cost + lengthsq(dijkstraStar.Zone.Position - target.Position) : dijkstraStar.Cost);
unsearchedNodes.PushObj(dijkstraStar, bestFirst ? dijkstraStar.Cost + LengthSquared(dijkstraStar.Zone.Position - target.Position) : dijkstraStar.Cost);
searched.Add(s.Zone);
}
}
private static float LengthSquared(float2 value)
{
return value.x * value.x + value.y * value.y;
}
private static float Saturate(float value)
{
return value < 0 ? 0 : value > 1 ? 1 : value;
}
}
public class GalaxyZone

View file

@ -6,8 +6,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public abstract class ItemInstance
{

View file

@ -6,9 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Unity;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Random = Unity.Mathematics.Random;
using Random = CultMath.Random;
using JM.LinqFaster;
using UniRx;
@ -157,7 +155,7 @@ public class ItemManager
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;
}
@ -219,19 +217,30 @@ public class ItemManager
maxDurability = Math.Max(item.Durability, 1f);
}
var quality = pow(item.Quality, stat.QualityExponent);
var durabilityExponent = lerp(
var durabilityRatio = item.Durability / maxDurability;
var durabilityExponent = Lerp(
GameplaySettings.DurabilityQualityMin,
GameplaySettings.DurabilityQualityMax,
pow(item.Quality, GameplaySettings.DurabilityQualityExponent));
var durability = pow(item.Durability / maxDurability, durabilityExponent * stat.DurabilityExponentMultiplier);
var result = lerp(stat.Min, stat.Max, quality * durability);
MathF.Pow(item.Quality, GameplaySettings.DurabilityQualityExponent));
var result = stat.EvaluateRecipeOrLegacy(
new StatEvaluationContext(
quality: item.Quality,
durability: durabilityRatio,
heat: 1),
durabilityExponent,
thermalExponent: 0,
includeHeat: false);
if (float.IsNaN(result))
throw new InvalidOperationException($"Performance Stat on {typedItem?.Name ?? item.ItemKey} evaluating as NaN: input data is invalid! Durability: {item.Durability} / {maxDurability}");
return result;
}
private static float Lerp(float from, float to, float t)
{
return from + (to - from) * t;
}
public int GetPrice(CraftedItemInstance item)
{
var typedItem = GetRuntimeItem(item);

View file

@ -2,9 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Unity;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Random = Unity.Mathematics.Random;
using Random = CultMath.Random;
public class LoadoutGenerator
{
@ -170,8 +168,8 @@ public class LoadoutGenerator
? allegiance / ManufacturerDistancePenalty(manufacturerKey)
: 0;
return allegianceWeight *
pow(item.OccupiedCells, sizeExponent) / // Prioritize larger items
pow(item.Price, PriceExponent); // Penalize item price to a controllable degree
MathF.Pow(item.OccupiedCells, sizeExponent) / // Prioritize larger items
MathF.Pow(item.Price, PriceExponent); // Penalize item price to a controllable degree
},
count
);
@ -289,7 +287,7 @@ public class LoadoutGenerator
var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1));
foreach (var cell in cells)
{
shape[int2(cell.X, cell.Y)] = true;
shape.SetCell(cell.X, cell.Y, true);
}
return shape;

View file

@ -5,8 +5,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static Unity.Mathematics.math;
using Random = Unity.Mathematics.Random;
using static CultMath.math;
using Random = CultMath.Random;
//Generates random names based on the statistical weight of letter sequences
//in a collection of sample names
@ -128,4 +128,4 @@ public class MarkovNameGenerator
int n = _random.NextInt(0,letters.Count);
return letters[n];
}
}
}

View file

@ -6,7 +6,7 @@ using System.Linq;
using Ink;
using Ink.Parsed;
using Path = System.IO.Path;
using Random = Unity.Mathematics.Random;
using Random = CultMath.Random;
using Story = Ink.Runtime.Story;
public class StoryProcessor : IZoneResolver, IFactionResolver

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Random = Unity.Mathematics.Random;
using Random = CultMath.Random;
public abstract class ZoneSelector
{

View file

@ -1,5 +1,5 @@
using Unity.Mathematics;
using static Unity.Mathematics.math;
using CultMath;
using static CultMath.math;
public static class Noise1D
{

View file

@ -4,9 +4,6 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
public class OrbitalEntity : Entity
{
@ -25,8 +22,8 @@ public class OrbitalEntity : Entity
{
if (!string.IsNullOrWhiteSpace(OrbitKey))
{
Position.xz = Zone.GetOrbitPosition(OrbitKey);
Velocity = Zone.GetOrbitVelocity(OrbitKey);
CultPositionXZ = AetheriaMath.ToCult(Zone.GetOrbitPosition(OrbitKey));
CultVelocity = AetheriaMath.ToCult(Zone.GetOrbitVelocity(OrbitKey));
}
base.Update(delta);

View file

@ -1,6 +1,6 @@
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using CultMath;
using static CultMath.math;
using float2 = CultMath.float2;
namespace Unity.Tiny
{

View file

@ -5,10 +5,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using cfloat2 = CultMath.float2;
using cfloat3 = CultMath.float3;
using int2 = Unity.Mathematics.int2;
using unityfloat2 = Unity.Mathematics.float2;
public class Shape
{
@ -58,10 +58,10 @@ public class Shape
{
return rotation switch
{
ItemRotation.Clockwise => int2(position.y, Width - 1 - position.x),
ItemRotation.Reversed => int2(Width - 1 - position.x, Height - 1 - position.y),
ItemRotation.CounterClockwise => int2(Height - 1 - position.y, position.x),
_ => int2(position.x, position.y)
ItemRotation.Clockwise => new int2(position.y, Width - 1 - position.x),
ItemRotation.Reversed => new int2(Width - 1 - position.x, Height - 1 - position.y),
ItemRotation.CounterClockwise => new int2(Height - 1 - position.y, position.x),
_ => new int2(position.x, position.y)
};
}
@ -88,7 +88,7 @@ public class Shape
for (var x = 0; x < Width; x++)
{
if (Cells[x, y])
yield return int2(x, y);
yield return new int2(x, y);
}
}
}
@ -103,27 +103,35 @@ public class Shape
{
for (var x = 0; x < Width; x++)
{
yield return int2(x, y);
yield return new int2(x, y);
}
}
}
private float2? _centerOfMass;
private cfloat2? _centerOfMass;
public float2 CenterOfMass => _centerOfMass ??= Coordinates
.Aggregate(float2.zero, (total, coord) => total + coord) / Coordinates.Length;
public cfloat2 CenterOfMass => _centerOfMass ??= Coordinates
.Aggregate(new cfloat2(0, 0), (total, coord) => total + new cfloat2(coord.x, coord.y)) / Coordinates.Length;
public bool GetCell(int x, int y)
{
return x >= 0 && y >= 0 && x < Width && y < Height && Cells[x, y];
}
public void SetCell(int x, int y, bool value)
{
if (x < 0 || y < 0 || x >= Width || y >= Height)
return;
_dirty = true;
_centerOfMass = null;
Cells[x, y] = value;
}
public bool this[int2 pos]
{
get { return pos.x >= 0 && pos.y >= 0 && pos.x < Width && pos.y < Height && Cells[pos.x, pos.y]; }
set
{
if (pos.x < 0 || pos.y < 0 || pos.x >= Width || pos.y >= Height)
return;
_dirty = true;
Cells[pos.x, pos.y] = value;
}
get { return GetCell(pos.x, pos.y); }
set { SetCell(pos.x, pos.y, value); }
}
public Shape Shrink()
@ -132,9 +140,9 @@ public class Shape
foreach (var shapeCoord in Coordinates)
{
shape[shapeCoord] = (
this[shapeCoord + int2(-1, -1)] && this[shapeCoord + int2(0, -1)] && this[shapeCoord + int2(1, -1)] &&
this[shapeCoord + int2(-1, 0)] && this[shapeCoord + int2(1, 0)] &&
this[shapeCoord + int2(-1, 1)] && this[shapeCoord + int2(0, 1)] && this[shapeCoord + int2(1, 1)]
this[shapeCoord + new int2(-1, -1)] && this[shapeCoord + new int2(0, -1)] && this[shapeCoord + new int2(1, -1)] &&
this[shapeCoord + new int2(-1, 0)] && this[shapeCoord + new int2(1, 0)] &&
this[shapeCoord + new int2(-1, 1)] && this[shapeCoord + new int2(0, 1)] && this[shapeCoord + new int2(1, 1)]
);
}
@ -143,7 +151,7 @@ public class Shape
public Shape Inset(Shape inset, int2 insetPosition, ItemRotation rotation = ItemRotation.None)
{
var shape = new Shape(max(Width, insetPosition.x + inset.Width - 1), max(Height, insetPosition.y + inset.Height - 1));
var shape = new Shape(Math.Max(Width, insetPosition.x + inset.Width - 1), Math.Max(Height, insetPosition.y + inset.Height - 1));
foreach (var v in inset.Coordinates)
{
var insetCoord = inset.Rotate(v, rotation) + insetPosition;
@ -159,9 +167,9 @@ public class Shape
foreach (var shapeCoord in AllCoordinates)
{
shape[shapeCoord] = (
this[shapeCoord + int2(-1, -1)] || this[shapeCoord + int2(0, -1)] || this[shapeCoord + int2(1, -1)] ||
this[shapeCoord + int2(-1, 0)] || this[shapeCoord] || this[shapeCoord + int2(1, 0)] ||
this[shapeCoord + int2(-1, 1)] || this[shapeCoord + int2(0, 1)] || this[shapeCoord + int2(1, 1)]
this[shapeCoord + new int2(-1, -1)] || this[shapeCoord + new int2(0, -1)] || this[shapeCoord + new int2(1, -1)] ||
this[shapeCoord + new int2(-1, 0)] || this[shapeCoord] || this[shapeCoord + new int2(1, 0)] ||
this[shapeCoord + new int2(-1, 1)] || this[shapeCoord + new int2(0, 1)] || this[shapeCoord + new int2(1, 1)]
);
}
@ -194,7 +202,7 @@ public class Shape
{
for (var y = 0; y < other.Height - height + 1; y++)
{
position = int2(x, y);
position = new int2(x, y);
var fits = true;
foreach (var v in Coordinates)
{
@ -212,7 +220,10 @@ public class Shape
return false;
}
public void SetLine(float2 a, float2 b)
public void SetLine(unityfloat2 a, unityfloat2 b) =>
SetLine(AetheriaMath.ToCult(a), AetheriaMath.ToCult(b));
public void SetLine(cfloat2 a, cfloat2 b)
{
if (a.Equals(b))
return;
@ -220,8 +231,8 @@ public class Shape
var steep = Math.Abs(b.y - a.y) > Math.Abs(b.x - a.x);
if (steep)
{
a = new float2(a.y, a.x);
b = new float2(b.y, b.x);
a = new cfloat2(a.y, a.x);
b = new cfloat2(b.y, b.x);
}
if (a.x > b.x)
@ -270,57 +281,28 @@ public class HardpointData : ITintInspector
return $"{Enum.GetName(typeof(HardpointType), Type)} Hardpoint {Rotation.Arrow()}";
}
public float3 TintColor
public cfloat3 TintColor
{
get { return GetColor(Type); }
}
public static float3 GetColor(HardpointType type)
public static cfloat3 GetColor(HardpointType type)
{
if (_tintColors == null)
{
var hardpointTypes = (HardpointType[])Enum.GetValues(typeof(HardpointType));
_tintColors = hardpointTypes.ToDictionary(
x => x,
x => ColorMath.HsvToRgb(float3(frac((float)(int)x / hardpointTypes.Length + .25f), 1, 1)));
x => ColorMath.HsvToRgb(new cfloat3(Fraction((float)(int)x / hardpointTypes.Length + .25f), 1, 1)));
}
return _tintColors.TryGetValue(type, out var color) ? color : _tintColors[HardpointType.Hull];
}
private static Dictionary<HardpointType, float3> _tintColors;
}
public class PerformanceStat
{
public float Min;
public float Max;
public float HeatExponentMultiplier;
public float DurabilityExponentMultiplier;
public float QualityExponent;
private Dictionary<Entity, Dictionary<Behavior, float>> _scaleModifiers;
private Dictionary<Entity, Dictionary<Behavior, float>> _constantModifiers;
private Dictionary<Entity, Dictionary<Behavior, float>> ScaleModifiers =>
_scaleModifiers ??= new Dictionary<Entity, Dictionary<Behavior, float>>();
private Dictionary<Entity, Dictionary<Behavior, float>> ConstantModifiers =>
_constantModifiers ??= new Dictionary<Entity, Dictionary<Behavior, float>>();
public Dictionary<Behavior, float> GetScaleModifiers(Entity entity)
private static float Fraction(float value)
{
if (!ScaleModifiers.ContainsKey(entity))
ScaleModifiers[entity] = new Dictionary<Behavior, float>();
return ScaleModifiers[entity];
return value - MathF.Floor(value);
}
public Dictionary<Behavior, float> GetConstantModifiers(Entity entity)
{
if (!ConstantModifiers.ContainsKey(entity))
ConstantModifiers[entity] = new Dictionary<Behavior, float>();
return ConstantModifiers[entity];
}
private static Dictionary<HardpointType, cfloat3> _tintColors;
}

View file

@ -3,15 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using DataStructures.ViliWonka.Heap;
using DataStructures.ViliWonka.KDTree;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using Random = Unity.Mathematics.Random;
public static class WeightedSampleElimination
{
private static float2 boundsMin = float2.zero; // The minimum bounds of the sampling domain
private static float2 boundsMax = float2(1); // The maximum bounds of the sampling domain
private static float2 boundsMin = new float2(0, 0); // The minimum bounds of the sampling domain
private static float2 boundsMax = new float2(1, 1); // The maximum bounds of the sampling domain
// Returns the minimum bounds of the sampling domain.
// The sampling domain boundaries are used for tiling and computing the maximum possible
@ -82,8 +80,8 @@ public static class WeightedSampleElimination
if ( d_max < .001f ) d_max = 2 * GetMaxPoissonDiskRadius( outputPoints.Length );
Eliminate( inputPoints, outputPoints, (p0, p1, d2, dmax, density) =>
{
float d = sqrt(d2);
return pow( 1 - d / dmax, 5 + 6 * density );
float d = MathF.Sqrt(d2);
return MathF.Pow( 1 - d / dmax, 5 + 6 * density );
}, densityFunc, d_max);
}
@ -97,7 +95,7 @@ public static class WeightedSampleElimination
while (sample < inputSamples.Length)
{
var v = random.NextFloat2();
accumulator += pow(saturate(density(v)), 2f) * saturate(envelope(v));
accumulator += MathF.Pow(Saturate(density(v)), 2f) * Saturate(envelope(v));
if (accumulator > .5f)
{
accumulator = 0;
@ -120,7 +118,7 @@ public static class WeightedSampleElimination
var domainSize = boundsMax[0] - boundsMin[0];
domainSize *= domainSize;
float sampleArea = domainSize / sampleCount;
return sqrt( sampleArea / ( 2 * sqrt(3) ) );
return MathF.Sqrt( sampleArea / ( 2 * MathF.Sqrt(3) ) );
}
// This is the method that performs weighted sample elimination.
@ -184,6 +182,11 @@ public static class WeightedSampleElimination
static float GetWeightLimitFraction( int inputSize, int outputSize, float beta, float gamma )
{
float ratio = (float) outputSize / inputSize;
return ( 1 - pow( ratio, gamma ) ) * beta;
return ( 1 - MathF.Pow( ratio, gamma ) ) * beta;
}
}
private static float Saturate(float value)
{
return value < 0 ? 0 : value > 1 ? 1 : value;
}
}

View file

@ -5,9 +5,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static Unity.Mathematics.noise;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using unitynoise = Unity.Mathematics.noise;
[Serializable]
public class PlanetSettings
@ -69,8 +69,8 @@ public class SectorBackgroundSettings
for(int i = 0; i < octaves; i++)
{
if(i<4)
sum += (1-abs(snoise(p * freq))) * amp;
else sum += abs(snoise(p * freq)) * amp;
sum += (1 - MathF.Abs(unitynoise.snoise(p * freq))) * amp;
else sum += MathF.Abs(unitynoise.snoise(p * freq)) * amp;
freq *= NoiseLacunarity;
amp *= NoiseGain;
}
@ -80,7 +80,7 @@ public class SectorBackgroundSettings
public float CloudDensity(float2 uv)
{
float noise = fBm(uv + NoisePosition, 10);
return pow(noise, CloudExponent) * CloudAmplitude;
return MathF.Pow(noise, CloudExponent) * CloudAmplitude;
}
}

View file

@ -7,15 +7,17 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using CultMath;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using quaternion = Unity.Mathematics.quaternion;
using static CultMath.math;
using cfloat2 = CultMath.float2;
using cfloat3 = CultMath.float3;
using cquaternion = CultMath.quaternion;
public class Ship : Entity
{
public Entity HomeEntity;
public float2 MovementDirection;
public cfloat2 MovementDirection;
public bool IsPlayerShip;
private HashSet<EquippedItem> _thrusterItems;
@ -32,10 +34,10 @@ public class Ship : Entity
private bool _exitingWormhole = false;
private bool _enteringWormhole = false;
private float _wormholeAnimationProgress;
private float2 _wormholeEntryPosition;
private float2 _wormholeEntryDirection;
private float2 _wormholePosition;
private float2 _wormholeExitVelocity;
private cfloat2 _wormholeEntryPosition;
private cfloat2 _wormholeEntryDirection;
private cfloat2 _wormholePosition;
private cfloat2 _wormholeExitVelocity;
public bool WormholeAnimationInProgress => _enteringWormhole || _exitingWormhole;
public float ForwardThrust { get; private set; }
@ -49,35 +51,40 @@ public class Ship : Entity
public float RightStrafeTotalTorque { get; private set; }
private List<Thruster> RightStrafeTorqueThrusters = new List<Thruster>();
public float TurnTime(float2 direction)
public float TurnTime(cfloat2 direction)
{
var angleDiff = Direction.Angle(normalize(direction));
var clockwise = dot(direction, Direction.Rotate(ItemRotation.Clockwise)) > 0;
var cultDirection = normalize(direction);
var shipDirection = CultDirection;
var angleDiff = AetheriaMath.AngleDegrees(shipDirection, cultDirection);
var clockwise = dot(cultDirection, AetheriaMath.Rotate(shipDirection, ItemRotation.Clockwise)) > 0;
return angleDiff / ((clockwise ? ClockwiseTorque : CounterClockwiseTorque) / Mass);
}
public event Action OnExitedWormhole;
public event Action OnEnteredWormhole;
public quaternion Rotation { get; private set; }
public cquaternion Rotation { get; private set; }
public void ExitWormhole(float2 wormholePosition, float2 exitVelocity)
private static cquaternion LookRotation(cfloat3 forward, cfloat3 up) =>
cquaternion.LookRotation(forward, up);
public void ExitWormhole(cfloat2 wormholePosition, cfloat2 exitVelocity)
{
_exitingWormhole = true;
_wormholeAnimationProgress = 0;
_wormholePosition = wormholePosition;
_wormholeExitVelocity = exitVelocity;
Direction = normalize(exitVelocity);
CultDirection = normalize(_wormholeExitVelocity);
}
public void EnterWormhole(float2 wormholePosition)
public void EnterWormhole(cfloat2 wormholePosition)
{
Target.Value = null;
_wormholeAnimationProgress = 0;
_enteringWormhole = true;
_wormholeEntryPosition = Position.xz;
_wormholeEntryDirection = normalize(_wormholeEntryPosition-wormholePosition);
_wormholeEntryPosition = CultPositionXZ;
_wormholePosition = wormholePosition;
_wormholeEntryDirection = normalize(_wormholeEntryPosition - _wormholePosition);
}
public override void Activate()
@ -245,33 +252,35 @@ public class Ship : Entity
{
RecalculateThrust();
foreach (var thruster in _allThrusters) thruster.Axis = 0;
var movementDirection = MovementDirection;
var rightThrusterTorqueCompensation = abs(RightStrafeTotalTorque) / RightStrafeTorqueThrusters.Count;
foreach (var thruster in _rightThrusters)
{
var thrust = 0f;
thrust += MovementDirection.x;
thrust += movementDirection.x;
if (RightStrafeTorqueThrusters.Contains(thruster))
thrust -= MovementDirection.x * (rightThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust));
thrust -= movementDirection.x * (rightThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust));
thruster.Axis = thrust;
}
var leftThrusterTorqueCompensation = abs(LeftStrafeTotalTorque) / LeftStrafeTorqueThrusters.Count;
foreach (var thruster in _leftThrusters)
{
var thrust = 0f;
thrust += -MovementDirection.x;
thrust += -movementDirection.x;
if (LeftStrafeTorqueThrusters.Contains(thruster))
thrust += MovementDirection.x * (leftThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust));
thrust += movementDirection.x * (leftThrusterTorqueCompensation / (abs(thruster.Torque) * thruster.Thrust));
thruster.Axis = thrust;
}
foreach (var thruster in _forwardThrusters) thruster.Axis += MovementDirection.y;
foreach (var thruster in _reverseThrusters) thruster.Axis += -MovementDirection.y;
foreach (var thruster in _forwardThrusters) thruster.Axis += movementDirection.y;
foreach (var thruster in _reverseThrusters) thruster.Axis += -movementDirection.y;
var look = normalize(LookDirection.xz);
var deltaRot = dot(look, normalize(Direction).Rotate(ItemRotation.Clockwise));
var look = normalize(CultLookDirectionXZ);
var shipDirection = normalize(CultDirection);
var deltaRot = dot(look, AetheriaMath.Rotate(shipDirection, ItemRotation.Clockwise));
if (abs(deltaRot) < .01f)
{
deltaRot = 0;
Direction = lerp(Direction, look, min(delta, 1));
CultDirection = lerp(shipDirection, look, min(delta, 1));
}
deltaRot = pow(abs(deltaRot), .5f) * sign(deltaRot);
@ -279,58 +288,60 @@ public class Ship : Entity
foreach (var thruster in _counterClockwiseThrusters) thruster.Axis += -deltaRot;
foreach (var drive in _aetherDrives)
drive.Axis = float3(MovementDirection.y, MovementDirection.x, deltaRot);
drive.Axis = new cfloat3(movementDirection.y, movementDirection.x, deltaRot);
}
var velocityMagnitude = length(Velocity);
var velocity = CultVelocity;
var velocityMagnitude = length(velocity);
if(velocityMagnitude > .01f)
Velocity = normalize(Velocity) * AetheriaMath.Decay(velocityMagnitude, (float)(ItemManager.GetRuntimeItem(Hull)?.HullDrag ?? 0), delta);
CultVelocity = normalize(velocity) * AetheriaMath.Decay(velocityMagnitude, (float)(ItemManager.GetRuntimeItem(Hull)?.HullDrag ?? 0), delta);
Position.xz += Velocity * delta;
CultPositionXZ += CultVelocity * delta;
var normal = Zone.GetNormal(Position.xz);
var force = new float2(normal.x, normal.z);
var normal = Zone.GetNormal(CultPositionXZ);
var force = new cfloat2(normal.x, normal.z);
var forceMagnitude = lengthsq(force);
if (forceMagnitude > .001f)
{
var fa = 1 / (1 - forceMagnitude) - 1;
Velocity += normalize(force) * Zone.Settings.GravityStrength * fa;
CultVelocity += normalize(force) * Zone.Settings.GravityStrength * fa;
}
var shipRight = Direction.Rotate(ItemRotation.Clockwise);
var forward = cross(float3(shipRight.x, 0, shipRight.y), normal);
Rotation = quaternion.LookRotation(forward, normal);
var shipRight = AetheriaMath.Rotate(CultDirection, ItemRotation.Clockwise);
var forward = cross(new cfloat3(shipRight.x, 0, shipRight.y), normal);
Rotation = LookRotation(forward, normal);
base.Update(delta);
if (_exitingWormhole)
{
var inverseDirection = new cfloat3(-CultDirection.x, 0, -CultDirection.y);
_wormholeAnimationProgress += delta / ItemManager.GameplaySettings.WormholeAnimationDuration;
if(_wormholeAnimationProgress < 1)
{
if (_wormholeAnimationProgress < ItemManager.GameplaySettings.WormholeExitCurveStart)
{
Position.xz = _wormholePosition;
Rotation = quaternion.LookRotation(float3(0, 1, 0), float3(-Direction.x, 0, -Direction.y));
CultPositionXZ = _wormholePosition;
Rotation = LookRotation(new cfloat3(0, 1, 0), inverseDirection);
}
else
{
var exitLerp = (_wormholeAnimationProgress - ItemManager.GameplaySettings.WormholeExitCurveStart) /
(1 - ItemManager.GameplaySettings.WormholeExitCurveStart);
exitLerp = AetheriaMath.Smootherstep(exitLerp); // Square the interpolation variable to produce curve with zero slope at start
Position.xz = _wormholePosition + normalize(_wormholeExitVelocity) * exitLerp * ItemManager.GameplaySettings.WormholeExitRadius;
Rotation = quaternion.LookRotation(
lerp(float3(0, 1, 0), forward, exitLerp),
lerp(float3(-Direction.x, 0, -Direction.y), normal, exitLerp));
CultPositionXZ = _wormholePosition + normalize(_wormholeExitVelocity) * exitLerp * ItemManager.GameplaySettings.WormholeExitRadius;
Rotation = LookRotation(
lerp(new cfloat3(0, 1, 0), forward, exitLerp),
lerp(inverseDirection, normal, exitLerp));
}
Position.y = Position.y - lerp(ItemManager.GameplaySettings.WormholeDepth, 0, _wormholeAnimationProgress);
CultPositionY -= lerp(ItemManager.GameplaySettings.WormholeDepth, 0, _wormholeAnimationProgress);
}
else
{
_exitingWormhole = false;
OnExitedWormhole?.Invoke();
OnExitedWormhole = null;
Velocity = _wormholeExitVelocity;
CultVelocity = _wormholeExitVelocity;
}
}
@ -343,19 +354,19 @@ public class Ship : Entity
{
var enterLerp = _wormholeAnimationProgress / (1 - ItemManager.GameplaySettings.WormholeExitCurveStart);
enterLerp = AetheriaMath.Smootherstep(enterLerp); // Square the interpolation variable to produce curve with zero slope at vertical
Position.xz = lerp(_wormholeEntryPosition, _wormholePosition, enterLerp);
Rotation = quaternion.LookRotation(
lerp(forward, float3(0, -1, 0), enterLerp),
lerp(normal, float3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y), enterLerp));
CultPositionXZ = lerp(_wormholeEntryPosition, _wormholePosition, enterLerp);
Rotation = LookRotation(
lerp(forward, new cfloat3(0, -1, 0), enterLerp),
lerp(normal, new cfloat3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y), enterLerp));
}
else
{
Position.xz = _wormholePosition;
Rotation = quaternion.LookRotation(float3(0, -1, 0),
float3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y));
CultPositionXZ = _wormholePosition;
Rotation = LookRotation(new cfloat3(0, -1, 0),
new cfloat3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y));
}
Position.y = Position.y - lerp(0, ItemManager.GameplaySettings.WormholeDepth, _wormholeAnimationProgress);
CultPositionY -= lerp(0, ItemManager.GameplaySettings.WormholeDepth, _wormholeAnimationProgress);
}
else
{

View file

@ -0,0 +1,496 @@
/* 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/. */
using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Aetheria.State.Unity;
public class PerformanceStat
{
public float Min;
public float Max;
public float HeatExponentMultiplier;
public float DurabilityExponentMultiplier;
public float QualityExponent;
public StatRecipe Recipe;
private Dictionary<Entity, Dictionary<Behavior, float>> _scaleModifiers;
private Dictionary<Entity, Dictionary<Behavior, float>> _constantModifiers;
private Dictionary<Entity, Dictionary<Behavior, float>> ScaleModifiers =>
_scaleModifiers ??= new Dictionary<Entity, Dictionary<Behavior, float>>();
private Dictionary<Entity, Dictionary<Behavior, float>> ConstantModifiers =>
_constantModifiers ??= new Dictionary<Entity, Dictionary<Behavior, float>>();
public Dictionary<Behavior, float> GetScaleModifiers(Entity entity)
{
if (!ScaleModifiers.ContainsKey(entity))
ScaleModifiers[entity] = new Dictionary<Behavior, float>();
return ScaleModifiers[entity];
}
public bool TryGetScaleModifiers(Entity entity, out Dictionary<Behavior, float> modifiers)
{
modifiers = null;
return entity != null &&
_scaleModifiers != null &&
_scaleModifiers.TryGetValue(entity, out modifiers) &&
modifiers.Count > 0;
}
public Dictionary<Behavior, float> GetConstantModifiers(Entity entity)
{
if (!ConstantModifiers.ContainsKey(entity))
ConstantModifiers[entity] = new Dictionary<Behavior, float>();
return ConstantModifiers[entity];
}
public bool TryGetConstantModifiers(Entity entity, out Dictionary<Behavior, float> modifiers)
{
modifiers = null;
return entity != null &&
_constantModifiers != null &&
_constantModifiers.TryGetValue(entity, out modifiers) &&
modifiers.Count > 0;
}
public float ApplyEntityModifiers(Entity entity, float baseValue)
{
var scaleModifier = 1.0f;
if (TryGetScaleModifiers(entity, out var scaleModifiers))
{
foreach (var modifier in scaleModifiers.Values)
scaleModifier *= modifier;
}
float constantModifier = 0;
if (TryGetConstantModifiers(entity, out var constantModifiers))
{
foreach (var modifier in constantModifiers.Values)
constantModifier += modifier;
}
return baseValue * scaleModifier + constantModifier;
}
public StatConditionMask GetDependencyMask(bool includeHeat)
{
if (Recipe != null)
return Recipe.DependencyMask;
var mask = StatConditionMask.None;
if (QualityExponent != 0)
mask |= StatConditionMask.Quality;
if (DurabilityExponentMultiplier != 0)
mask |= StatConditionMask.Durability;
if (includeHeat && HeatExponentMultiplier != 0)
mask |= StatConditionMask.Heat;
return mask;
}
public float EvaluateRecipeOrLegacy(
in StatEvaluationContext context,
float durabilityExponent,
float thermalExponent,
bool includeHeat)
{
if (Recipe != null)
return Recipe.Evaluate(context);
var heat = !includeHeat || HeatExponentMultiplier == 0
? 1
: CultMath.math.pow(context.Heat, thermalExponent * HeatExponentMultiplier);
var durability = DurabilityExponentMultiplier == 0
? 1
: CultMath.math.pow(context.Durability, durabilityExponent * DurabilityExponentMultiplier);
var quality = QualityExponent == 0
? 1
: CultMath.math.pow(context.Quality, QualityExponent);
return CultMath.math.lerp(Min, Max, durability * quality * heat);
}
public float EvaluateConsumableBaseline(in StatEvaluationContext context)
{
if (Recipe != null)
return Recipe.Evaluate(context);
return CultMath.math.lerp(Min, Max, context.Durability * CultMath.math.pow(context.Quality, QualityExponent));
}
}
[Flags]
public enum StatConditionMask
{
None = 0,
Quality = 1 << 0,
Durability = 1 << 1,
Heat = 1 << 2,
Charge = 1 << 3,
Ammo = 1 << 4,
Range = 1 << 5,
Integrity = 1 << 6,
PilotSkill = 1 << 7,
Environment = 1 << 8
}
public enum StatModifierOperation
{
Add,
Multiply,
Override
}
public static class StatRecipeTokens
{
public const string Quality = "quality";
public const string Durability = "durability";
public const string Heat = "heat";
public const string Charge = "charge";
public const string Ammo = "ammo";
public const string Range = "range";
public const string Integrity = "integrity";
public const string PilotSkill = "pilotSkill";
public const string Environment = "environment";
public const string Add = "add";
public const string Multiply = "multiply";
public const string Override = "override";
public static StatConditionMask ToConditionMask(string condition)
{
switch (condition ?? "")
{
case Quality:
return StatConditionMask.Quality;
case Durability:
return StatConditionMask.Durability;
case Heat:
return StatConditionMask.Heat;
case Charge:
return StatConditionMask.Charge;
case Ammo:
return StatConditionMask.Ammo;
case Range:
return StatConditionMask.Range;
case Integrity:
return StatConditionMask.Integrity;
case PilotSkill:
return StatConditionMask.PilotSkill;
case Environment:
return StatConditionMask.Environment;
default:
return StatConditionMask.None;
}
}
public static StatModifierOperation ToModifierOperation(string operation)
{
switch (operation ?? "")
{
case Multiply:
return StatModifierOperation.Multiply;
case Override:
return StatModifierOperation.Override;
default:
return StatModifierOperation.Add;
}
}
}
public static class AetheriaRuntimeBehaviorValueReader
{
public static PerformanceStat ReadPerformanceStat(AetheriaRuntimeBehaviorValue value)
{
return new PerformanceStat
{
Min = ChildFloat(value, 0),
Max = ChildFloat(value, 1),
HeatExponentMultiplier = ChildFloat(value, 2),
DurabilityExponentMultiplier = ChildFloat(value, 3),
QualityExponent = ChildFloat(value, 4),
Recipe = ReadStatRecipe(ChildValue(value, 5))
};
}
public static StatRecipe ReadStatRecipe(AetheriaRuntimeBehaviorValue value)
{
if (value == null || value.Children.Count == 0)
return null;
return new StatRecipe
{
BaseValue = ChildFloat(value, 0),
Modifiers = ChildValue(value, 1)?.Children
.Select(ReadStatRecipeModifier)
.Where(modifier => modifier != null)
.ToArray() ?? Array.Empty<StatRecipeModifier>()
};
}
public static StatRecipeModifier ReadStatRecipeModifier(AetheriaRuntimeBehaviorValue value)
{
if (value == null)
return null;
return new StatRecipeModifier
{
Condition = StatRecipeTokens.ToConditionMask(ChildString(value, 0)),
Operation = StatRecipeTokens.ToModifierOperation(ChildString(value, 1)),
Amount = ChildFloat(value, 2),
Curve = ReadBezierCurve(ChildValue(value, 3)),
Enabled = value.Children.Count <= 4 || ChildBool(value, 4, fallback: true)
};
}
public static BezierCurve ReadBezierCurve(AetheriaRuntimeBehaviorValue value)
{
if (value == null || value.Children.Count == 0)
return null;
return BezierCurve.FromKeys(
value.Children
.Where(key => key.Children.Count >= 4)
.Select(key => (
ChildFloat(key, 0),
ChildFloat(key, 1),
ChildFloat(key, 2),
ChildFloat(key, 3))));
}
public static StatReference ReadStatReference(AetheriaRuntimeBehaviorValue value)
{
return new StatReference
{
Target = ChildString(value, 1),
Stat = ChildString(value, 2)
};
}
public static T ReadEnum<T>(AetheriaRuntimeBehaviorValue value, T fallback) where T : struct
{
if (!string.IsNullOrWhiteSpace(value?.StringValue) && Enum.TryParse(value.StringValue, true, out T parsed))
return parsed;
return value != null && Enum.IsDefined(typeof(T), (int)value.NumberValue)
? (T)Enum.ToObject(typeof(T), (int)value.NumberValue)
: fallback;
}
private static float ChildFloat(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? (float)value.Children[index].NumberValue : 0;
}
private static bool ChildBool(AetheriaRuntimeBehaviorValue value, int index, bool fallback = false)
{
return value != null && value.Children.Count > index ? value.Children[index].BoolValue : fallback;
}
private static string ChildString(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index].StringValue ?? "" : "";
}
private static AetheriaRuntimeBehaviorValue ChildValue(AetheriaRuntimeBehaviorValue value, int index)
{
return value != null && value.Children.Count > index ? value.Children[index] : null;
}
}
public sealed class StatRecipe
{
public float BaseValue;
public StatRecipeModifier[] Modifiers = Array.Empty<StatRecipeModifier>();
private StatConditionMask? _dependencyMask;
private StatRecipeModifier[] _enabledModifiers;
public StatConditionMask DependencyMask
{
get
{
if (_dependencyMask.HasValue)
return _dependencyMask.Value;
var mask = StatConditionMask.None;
foreach (var modifier in EnabledModifiers)
mask |= modifier.Condition;
_dependencyMask = mask;
return mask;
}
}
private StatRecipeModifier[] EnabledModifiers
{
get
{
if (_enabledModifiers != null)
return _enabledModifiers;
var modifiers = Modifiers ?? Array.Empty<StatRecipeModifier>();
var enabledCount = 0;
for (var i = 0; i < modifiers.Length; i++)
{
if (modifiers[i] != null && modifiers[i].Enabled && modifiers[i].Condition != StatConditionMask.None)
enabledCount++;
}
if (enabledCount == 0)
{
_enabledModifiers = Array.Empty<StatRecipeModifier>();
return _enabledModifiers;
}
_enabledModifiers = new StatRecipeModifier[enabledCount];
var next = 0;
for (var i = 0; i < modifiers.Length; i++)
{
var modifier = modifiers[i];
if (modifier != null && modifier.Enabled && modifier.Condition != StatConditionMask.None)
_enabledModifiers[next++] = modifier;
}
return _enabledModifiers;
}
}
public void InvalidateCache()
{
_dependencyMask = null;
_enabledModifiers = null;
}
public float Evaluate(in StatEvaluationContext context)
{
var value = BaseValue;
foreach (var modifier in EnabledModifiers)
{
var sample = modifier.Sample(context.GetConditionValue(modifier.Condition));
switch (modifier.Operation)
{
case StatModifierOperation.Multiply:
value *= 1 + ((modifier.Amount - 1) * sample);
break;
case StatModifierOperation.Override:
value = CultMath.math.lerp(value, modifier.Amount, sample);
break;
default:
value += modifier.Amount * sample;
break;
}
}
return value;
}
}
public sealed class StatRecipeModifier
{
public StatConditionMask Condition;
public StatModifierOperation Operation;
public float Amount;
public BezierCurve Curve;
public bool Enabled = true;
public float Sample(float conditionValue)
{
var value = CultMath.math.saturate(conditionValue);
return Curve == null ? value : CultMath.math.saturate(Curve.Evaluate(value));
}
}
public readonly struct StatEvaluationContext
{
public StatEvaluationContext(
float quality,
float durability,
float heat,
float charge = 0,
float ammo = 0,
float range = 0,
float integrity = 0,
float pilotSkill = 0,
float environment = 0)
{
Quality = quality;
Durability = durability;
Heat = heat;
Charge = charge;
Ammo = ammo;
Range = range;
Integrity = integrity;
PilotSkill = pilotSkill;
Environment = environment;
}
public float Quality { get; }
public float Durability { get; }
public float Heat { get; }
public float Charge { get; }
public float Ammo { get; }
public float Range { get; }
public float Integrity { get; }
public float PilotSkill { get; }
public float Environment { get; }
public float GetConditionValue(StatConditionMask condition)
{
switch (condition)
{
case StatConditionMask.Quality:
return Quality;
case StatConditionMask.Durability:
return Durability;
case StatConditionMask.Heat:
return Heat;
case StatConditionMask.Charge:
return Charge;
case StatConditionMask.Ammo:
return Ammo;
case StatConditionMask.Range:
return Range;
case StatConditionMask.Integrity:
return Integrity;
case StatConditionMask.PilotSkill:
return PilotSkill;
case StatConditionMask.Environment:
return Environment;
default:
return 0;
}
}
public StatEvaluationContext WithCondition(StatConditionMask condition, float value)
{
switch (condition)
{
case StatConditionMask.Quality:
return new StatEvaluationContext(value, Durability, Heat, Charge, Ammo, Range, Integrity, PilotSkill, Environment);
case StatConditionMask.Durability:
return new StatEvaluationContext(Quality, value, Heat, Charge, Ammo, Range, Integrity, PilotSkill, Environment);
case StatConditionMask.Heat:
return new StatEvaluationContext(Quality, Durability, value, Charge, Ammo, Range, Integrity, PilotSkill, Environment);
case StatConditionMask.Charge:
return new StatEvaluationContext(Quality, Durability, Heat, value, Ammo, Range, Integrity, PilotSkill, Environment);
case StatConditionMask.Ammo:
return new StatEvaluationContext(Quality, Durability, Heat, Charge, value, Range, Integrity, PilotSkill, Environment);
case StatConditionMask.Range:
return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, value, Integrity, PilotSkill, Environment);
case StatConditionMask.Integrity:
return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, Range, value, PilotSkill, Environment);
case StatConditionMask.PilotSkill:
return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, Range, Integrity, value, Environment);
case StatConditionMask.Environment:
return new StatEvaluationContext(Quality, Durability, Heat, Charge, Ammo, Range, Integrity, PilotSkill, value);
default:
return this;
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 04283d92f0302fc49978de4ee33c6a6c

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e4871bd17ece70646b5e8f3dbf42e923
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,7 +1,7 @@
using Unity.Mathematics;
using float2 = CultMath.float2;
public class Wormhole
{
public float2 Position;
public GalaxyZone Target;
}
}

View file

@ -0,0 +1,845 @@
using System;
using System.Collections.Generic;
[Serializable]
public sealed class YmirStepRequest
{
public float deltaTime;
public YmirWorld world;
}
[Serializable]
public sealed class YmirStepResult
{
public YmirWorld world;
public YmirContactEvent[] contacts;
}
[Serializable]
public sealed class YmirSphereOverlapRequest
{
public YmirVec3 center;
public float radius;
public YmirSphereQueryBody[] bodies;
}
[Serializable]
public sealed class YmirSphereOverlapResult
{
public YmirSphereOverlapHit[] hits;
}
[Serializable]
public sealed class YmirSphereOverlapHit
{
public string bodyId;
public YmirVec3 point;
public YmirVec3 normal;
public float penetration;
public float distance;
}
[Serializable]
public sealed class YmirCircleCastRequest
{
public YmirVec2 origin;
public YmirVec2 direction;
public float distance;
public float radius;
public YmirWorld world;
}
[Serializable]
public sealed class YmirCircleOverlapRequest
{
public YmirVec2 center;
public float radius;
public YmirWorld world;
}
[Serializable]
public sealed class YmirCircleOverlapResult
{
public YmirCircleOverlapHit[] hits;
}
[Serializable]
public sealed class YmirCircleOverlapHit
{
public string bodyId;
public YmirVec2 point;
public YmirVec2 normal;
public float penetration;
public float distance;
}
[Serializable]
public sealed class YmirCircleCastResult
{
public YmirCircleCastHit[] hits;
}
[Serializable]
public sealed class YmirCircleCastHit
{
public string bodyId;
public YmirVec2 point;
public YmirVec2 normal;
public float distance;
}
[Serializable]
public sealed class YmirSphereCastRequest
{
public YmirVec3 origin;
public YmirVec3 direction;
public float distance;
public float radius;
public YmirSphereQueryBody[] bodies;
}
[Serializable]
public sealed class YmirSphereCastResult
{
public YmirSphereCastHit[] hits;
}
[Serializable]
public sealed class YmirSphereCastHit
{
public string bodyId;
public YmirVec3 point;
public YmirVec3 normal;
public float distance;
}
[Serializable]
public sealed class YmirWorld
{
public float time;
public YmirPhysicsBody[] bodies;
public YmirRadialField[] fields;
}
[Serializable]
public sealed class YmirSphereQueryWorld
{
public YmirSphereQueryBody[] bodies;
}
[Serializable]
public sealed class YmirSphereQueryBody
{
public string id;
public YmirVec3 position;
public float radius;
}
[Serializable]
public sealed class YmirPhysicsBody
{
public string id;
public YmirVec2 position;
public YmirVec2 velocity;
public YmirVec2 direction;
public float angularVelocity;
public float torque;
public float momentOfInertia;
public float radius;
public float mass;
public bool isStatic;
public float restitution;
}
[Serializable]
public sealed class YmirRadialField
{
public string id;
public YmirVec2 position;
public float strength;
public float radius;
}
[Serializable]
public sealed class YmirContactEvent
{
public string bodyA;
public string bodyB;
public YmirVec2 point;
public YmirVec2 normal;
public float penetration;
public float relativeSpeed;
}
[Serializable]
public struct YmirVec2
{
public float x;
public float y;
}
[Serializable]
public struct YmirVec3
{
public float x;
public float y;
public float z;
}
public static class YmirPhysicsQueries
{
private const float Epsilon = 0.000001f;
public static YmirStepResult Step(YmirStepRequest request)
{
if (request == null || request.world == null)
{
return new YmirStepResult
{
world = new YmirWorld
{
time = 0,
bodies = Array.Empty<YmirPhysicsBody>(),
fields = Array.Empty<YmirRadialField>()
},
contacts = Array.Empty<YmirContactEvent>()
};
}
return Step(request.world, request.deltaTime);
}
public static YmirStepResult Step(YmirWorld world, float deltaTime)
{
var bodies = CopyBodies(world?.bodies);
var fields = CopyFields(world?.fields);
var steppedWorld = new YmirWorld
{
time = world == null ? 0 : world.time + Math.Max(0, deltaTime),
bodies = bodies,
fields = fields
};
if (world == null || bodies.Length == 0 || deltaTime <= 0)
{
return new YmirStepResult
{
world = steppedWorld,
contacts = Array.Empty<YmirContactEvent>()
};
}
ApplyFields(bodies, fields, deltaTime);
IntegrateBodies(bodies, deltaTime);
var contacts = ResolveContacts(bodies);
return new YmirStepResult
{
world = steppedWorld,
contacts = contacts
};
}
public static YmirSphereOverlapResult OverlapSphere(YmirSphereOverlapRequest request)
{
return new YmirSphereOverlapResult
{
hits = request == null
? Array.Empty<YmirSphereOverlapHit>()
: OverlapSphere(request.bodies, request.center, request.radius)
};
}
public static YmirSphereOverlapHit[] OverlapSphere(
IReadOnlyList<YmirSphereQueryBody> bodies,
YmirVec3 center,
float radius)
{
var hits = new List<YmirSphereOverlapHit>();
OverlapSphere(bodies, center, radius, hits);
return hits.Count == 0 ? Array.Empty<YmirSphereOverlapHit>() : hits.ToArray();
}
public static int OverlapSphere(
IReadOnlyList<YmirSphereQueryBody> bodies,
YmirVec3 center,
float radius,
List<YmirSphereOverlapHit> hits)
{
if (hits == null)
throw new ArgumentNullException(nameof(hits));
hits.Clear();
if (bodies == null || bodies.Count == 0 || radius < 0)
return 0;
for (var i = 0; i < bodies.Count; i++)
{
var body = bodies[i];
if (body == null || body.radius < 0)
continue;
var delta = Subtract(body.position, center);
var centerDistanceSquared = LengthSquared(delta);
var combinedRadius = radius + body.radius;
if (centerDistanceSquared > combinedRadius * combinedRadius)
continue;
var centerDistance = (float)Math.Sqrt(centerDistanceSquared);
var normal = centerDistance > Epsilon
? Scale(delta, 1.0f / centerDistance)
: new YmirVec3 { x = 1, y = 0, z = 0 };
hits.Add(new YmirSphereOverlapHit
{
bodyId = body.id,
point = Subtract(body.position, Scale(normal, body.radius)),
normal = normal,
penetration = combinedRadius - centerDistance,
distance = Math.Max(0, centerDistance - body.radius)
});
}
hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId));
return hits.Count;
}
public static YmirSphereCastResult CastSphere(YmirSphereCastRequest request)
{
return new YmirSphereCastResult
{
hits = request == null
? Array.Empty<YmirSphereCastHit>()
: CastSphere(request.bodies, request.origin, request.direction, request.distance, request.radius)
};
}
public static YmirCircleCastResult CastCircle(YmirCircleCastRequest request)
{
return new YmirCircleCastResult
{
hits = request == null || request.world == null
? Array.Empty<YmirCircleCastHit>()
: CastCircle(request.world.bodies, request.origin, request.direction, request.distance, request.radius)
};
}
public static YmirCircleOverlapResult OverlapCircle(YmirCircleOverlapRequest request)
{
return new YmirCircleOverlapResult
{
hits = request == null || request.world == null
? Array.Empty<YmirCircleOverlapHit>()
: OverlapCircle(request.world.bodies, request.center, request.radius)
};
}
public static YmirCircleOverlapHit[] OverlapCircle(
IReadOnlyList<YmirPhysicsBody> bodies,
YmirVec2 center,
float radius)
{
var hits = new List<YmirCircleOverlapHit>();
OverlapCircle(bodies, center, radius, hits);
return hits.Count == 0 ? Array.Empty<YmirCircleOverlapHit>() : hits.ToArray();
}
public static int OverlapCircle(
IReadOnlyList<YmirPhysicsBody> bodies,
YmirVec2 center,
float radius,
List<YmirCircleOverlapHit> hits)
{
if (hits == null)
throw new ArgumentNullException(nameof(hits));
hits.Clear();
if (bodies == null || bodies.Count == 0 || radius < 0)
return 0;
for (var i = 0; i < bodies.Count; i++)
{
var body = bodies[i];
if (body == null || body.radius < 0)
continue;
var delta = Subtract(body.position, center);
var centerDistanceSquared = LengthSquared(delta);
var combinedRadius = radius + body.radius;
if (centerDistanceSquared > combinedRadius * combinedRadius)
continue;
var centerDistance = (float)Math.Sqrt(centerDistanceSquared);
var normal = centerDistance > Epsilon
? Scale(delta, 1.0f / centerDistance)
: new YmirVec2 { x = 1, y = 0 };
hits.Add(new YmirCircleOverlapHit
{
bodyId = body.id,
point = Subtract(body.position, Scale(normal, body.radius)),
normal = normal,
penetration = combinedRadius - centerDistance,
distance = Math.Max(0, centerDistance - body.radius)
});
}
hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId));
return hits.Count;
}
public static YmirCircleCastHit[] CastCircle(
IReadOnlyList<YmirPhysicsBody> bodies,
YmirVec2 origin,
YmirVec2 direction,
float distance,
float radius)
{
var hits = new List<YmirCircleCastHit>();
CastCircle(bodies, origin, direction, distance, radius, hits);
return hits.Count == 0 ? Array.Empty<YmirCircleCastHit>() : hits.ToArray();
}
public static int CastCircle(
IReadOnlyList<YmirPhysicsBody> bodies,
YmirVec2 origin,
YmirVec2 direction,
float distance,
float radius,
List<YmirCircleCastHit> hits)
{
if (hits == null)
throw new ArgumentNullException(nameof(hits));
hits.Clear();
var directionLengthSquared = LengthSquared(direction);
if (bodies == null || bodies.Count == 0 || distance < 0 || radius < 0 || directionLengthSquared <= Epsilon)
return 0;
var directionLength = (float)Math.Sqrt(directionLengthSquared);
var rayDirection = Scale(direction, 1.0f / directionLength);
for (var i = 0; i < bodies.Count; i++)
{
var body = bodies[i];
if (body == null || body.radius < 0)
continue;
var toOrigin = Subtract(origin, body.position);
var combinedRadius = radius + body.radius;
var c = LengthSquared(toOrigin) - combinedRadius * combinedRadius;
float hitDistance;
if (c <= 0)
{
hitDistance = 0;
}
else
{
var b = Dot(toOrigin, rayDirection);
if (b > 0)
continue;
var discriminant = b * b - c;
if (discriminant < 0)
continue;
hitDistance = -b - (float)Math.Sqrt(discriminant);
if (hitDistance < 0 || hitDistance > distance)
continue;
}
var castCenter = Add(origin, Scale(rayDirection, hitDistance));
var delta = Subtract(body.position, castCenter);
var centerDistanceSquared = LengthSquared(delta);
var normal = centerDistanceSquared > Epsilon
? Scale(delta, 1.0f / (float)Math.Sqrt(centerDistanceSquared))
: Scale(rayDirection, -1);
hits.Add(new YmirCircleCastHit
{
bodyId = body.id,
point = Subtract(body.position, Scale(normal, body.radius)),
normal = normal,
distance = hitDistance
});
}
hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId));
return hits.Count;
}
public static YmirSphereCastHit[] CastSphere(
IReadOnlyList<YmirSphereQueryBody> bodies,
YmirVec3 origin,
YmirVec3 direction,
float distance,
float radius)
{
var hits = new List<YmirSphereCastHit>();
CastSphere(bodies, origin, direction, distance, radius, hits);
return hits.Count == 0 ? Array.Empty<YmirSphereCastHit>() : hits.ToArray();
}
public static int CastSphere(
IReadOnlyList<YmirSphereQueryBody> bodies,
YmirVec3 origin,
YmirVec3 direction,
float distance,
float radius,
List<YmirSphereCastHit> hits)
{
if (hits == null)
throw new ArgumentNullException(nameof(hits));
hits.Clear();
var directionLengthSquared = LengthSquared(direction);
if (bodies == null || bodies.Count == 0 || distance < 0 || radius < 0 || directionLengthSquared <= Epsilon)
return 0;
var directionLength = (float)Math.Sqrt(directionLengthSquared);
var rayDirection = Scale(direction, 1.0f / directionLength);
for (var i = 0; i < bodies.Count; i++)
{
var body = bodies[i];
if (body == null || body.radius < 0)
continue;
var toOrigin = Subtract(origin, body.position);
var combinedRadius = radius + body.radius;
var c = LengthSquared(toOrigin) - combinedRadius * combinedRadius;
float hitDistance;
if (c <= 0)
{
hitDistance = 0;
}
else
{
var b = Dot(toOrigin, rayDirection);
if (b > 0)
continue;
var discriminant = b * b - c;
if (discriminant < 0)
continue;
hitDistance = -b - (float)Math.Sqrt(discriminant);
if (hitDistance < 0 || hitDistance > distance)
continue;
}
var castCenter = Add(origin, Scale(rayDirection, hitDistance));
var delta = Subtract(body.position, castCenter);
var centerDistanceSquared = LengthSquared(delta);
var normal = centerDistanceSquared > Epsilon
? Scale(delta, 1.0f / (float)Math.Sqrt(centerDistanceSquared))
: Scale(rayDirection, -1);
hits.Add(new YmirSphereCastHit
{
bodyId = body.id,
point = Subtract(body.position, Scale(normal, body.radius)),
normal = normal,
distance = hitDistance
});
}
hits.Sort((left, right) => CompareHitOrder(left.distance, left.bodyId, right.distance, right.bodyId));
return hits.Count;
}
private static int CompareHitOrder(float leftDistance, string leftBodyId, float rightDistance, string rightBodyId)
{
var distanceOrder = leftDistance.CompareTo(rightDistance);
return distanceOrder != 0
? distanceOrder
: string.CompareOrdinal(leftBodyId, rightBodyId);
}
private static void ApplyFields(IReadOnlyList<YmirPhysicsBody> bodies, IReadOnlyList<YmirRadialField> fields, float deltaTime)
{
if (fields == null || fields.Count == 0)
return;
for (var bodyIndex = 0; bodyIndex < bodies.Count; bodyIndex++)
{
var body = bodies[bodyIndex];
if (body == null || body.isStatic)
continue;
for (var fieldIndex = 0; fieldIndex < fields.Count; fieldIndex++)
{
var field = fields[fieldIndex];
if (field == null || field.radius <= 0 || Math.Abs(field.strength) <= Epsilon)
continue;
var offset = Subtract(body.position, field.position);
var distanceSquared = LengthSquared(offset);
var radiusSquared = field.radius * field.radius;
if (distanceSquared <= Epsilon || distanceSquared > radiusSquared)
continue;
var distance = (float)Math.Sqrt(distanceSquared);
var falloff = 1.0f - Math.Min(1.0f, distance / field.radius);
var acceleration = Scale(offset, field.strength * falloff / distance);
body.velocity = Add(body.velocity, Scale(acceleration, deltaTime));
}
}
}
private static void IntegrateBodies(IReadOnlyList<YmirPhysicsBody> bodies, float deltaTime)
{
for (var i = 0; i < bodies.Count; i++)
{
var body = bodies[i];
if (body == null || body.isStatic)
continue;
if (body.momentOfInertia > Epsilon)
body.angularVelocity += body.torque / body.momentOfInertia * deltaTime;
body.position = Add(body.position, Scale(body.velocity, deltaTime));
if (Math.Abs(body.angularVelocity) > Epsilon)
body.direction = Rotate(body.direction, body.angularVelocity * deltaTime);
}
}
private static YmirContactEvent[] ResolveContacts(IReadOnlyList<YmirPhysicsBody> bodies)
{
var contacts = new List<YmirContactEvent>();
for (var leftIndex = 0; leftIndex < bodies.Count; leftIndex++)
{
var left = bodies[leftIndex];
if (left == null || left.radius < 0)
continue;
for (var rightIndex = leftIndex + 1; rightIndex < bodies.Count; rightIndex++)
{
var right = bodies[rightIndex];
if (right == null || right.radius < 0 || left.isStatic && right.isStatic)
continue;
var delta = Subtract(right.position, left.position);
var distanceSquared = LengthSquared(delta);
var combinedRadius = left.radius + right.radius;
if (distanceSquared > combinedRadius * combinedRadius)
continue;
var distance = (float)Math.Sqrt(distanceSquared);
var normal = distance > Epsilon
? Scale(delta, 1.0f / distance)
: new YmirVec2 { x = 1, y = 0 };
var penetration = combinedRadius - distance;
var relativeVelocity = Subtract(left.velocity, right.velocity);
contacts.Add(new YmirContactEvent
{
bodyA = left.id,
bodyB = right.id,
point = Add(left.position, Scale(normal, Math.Max(0, left.radius - penetration * 0.5f))),
normal = normal,
penetration = penetration,
relativeSpeed = Math.Max(0, Dot(relativeVelocity, normal))
});
SeparateBodies(left, right, normal, penetration);
ResolveVelocity(left, right, normal);
}
}
return contacts.ToArray();
}
private static void SeparateBodies(YmirPhysicsBody left, YmirPhysicsBody right, YmirVec2 normal, float penetration)
{
if (penetration <= 0)
return;
if (left.isStatic)
{
right.position = Add(right.position, Scale(normal, penetration));
return;
}
if (right.isStatic)
{
left.position = Subtract(left.position, Scale(normal, penetration));
return;
}
var leftInverseMass = InverseMass(left);
var rightInverseMass = InverseMass(right);
var inverseMassTotal = leftInverseMass + rightInverseMass;
if (inverseMassTotal <= Epsilon)
return;
var correction = Scale(normal, penetration / inverseMassTotal);
left.position = Subtract(left.position, Scale(correction, leftInverseMass));
right.position = Add(right.position, Scale(correction, rightInverseMass));
}
private static void ResolveVelocity(YmirPhysicsBody left, YmirPhysicsBody right, YmirVec2 normal)
{
var leftInverseMass = InverseMass(left);
var rightInverseMass = InverseMass(right);
var inverseMassTotal = leftInverseMass + rightInverseMass;
if (inverseMassTotal <= Epsilon)
return;
var relativeVelocity = Subtract(right.velocity, left.velocity);
var velocityAlongNormal = Dot(relativeVelocity, normal);
if (velocityAlongNormal > 0)
return;
var restitution = Math.Max(0, Math.Min(1, Math.Max(left.restitution, right.restitution)));
var impulseMagnitude = -(1 + restitution) * velocityAlongNormal / inverseMassTotal;
var impulse = Scale(normal, impulseMagnitude);
if (!left.isStatic)
left.velocity = Subtract(left.velocity, Scale(impulse, leftInverseMass));
if (!right.isStatic)
right.velocity = Add(right.velocity, Scale(impulse, rightInverseMass));
}
private static float InverseMass(YmirPhysicsBody body)
{
return body == null || body.isStatic || body.mass <= Epsilon ? 0 : 1.0f / body.mass;
}
private static YmirPhysicsBody[] CopyBodies(IReadOnlyList<YmirPhysicsBody> bodies)
{
if (bodies == null || bodies.Count == 0)
return Array.Empty<YmirPhysicsBody>();
var copy = new YmirPhysicsBody[bodies.Count];
for (var i = 0; i < copy.Length; i++)
{
var body = bodies[i];
copy[i] = body == null
? null
: new YmirPhysicsBody
{
id = body.id,
position = body.position,
velocity = body.velocity,
direction = body.direction,
angularVelocity = body.angularVelocity,
torque = body.torque,
momentOfInertia = body.momentOfInertia,
radius = body.radius,
mass = body.mass,
isStatic = body.isStatic,
restitution = body.restitution
};
}
return copy;
}
private static YmirRadialField[] CopyFields(IReadOnlyList<YmirRadialField> fields)
{
if (fields == null || fields.Count == 0)
return Array.Empty<YmirRadialField>();
var copy = new YmirRadialField[fields.Count];
for (var i = 0; i < copy.Length; i++)
{
var field = fields[i];
copy[i] = field == null
? null
: new YmirRadialField
{
id = field.id,
position = field.position,
strength = field.strength,
radius = field.radius
};
}
return copy;
}
private static YmirVec3 Subtract(YmirVec3 left, YmirVec3 right)
{
return new YmirVec3
{
x = left.x - right.x,
y = left.y - right.y,
z = left.z - right.z
};
}
private static YmirVec2 Subtract(YmirVec2 left, YmirVec2 right)
{
return new YmirVec2
{
x = left.x - right.x,
y = left.y - right.y
};
}
private static YmirVec3 Add(YmirVec3 left, YmirVec3 right)
{
return new YmirVec3
{
x = left.x + right.x,
y = left.y + right.y,
z = left.z + right.z
};
}
private static YmirVec2 Add(YmirVec2 left, YmirVec2 right)
{
return new YmirVec2
{
x = left.x + right.x,
y = left.y + right.y
};
}
private static YmirVec3 Scale(YmirVec3 value, float scale)
{
return new YmirVec3
{
x = value.x * scale,
y = value.y * scale,
z = value.z * scale
};
}
private static YmirVec2 Scale(YmirVec2 value, float scale)
{
return new YmirVec2
{
x = value.x * scale,
y = value.y * scale
};
}
private static YmirVec2 Rotate(YmirVec2 value, float radians)
{
var sine = (float)Math.Sin(radians);
var cosine = (float)Math.Cos(radians);
return new YmirVec2
{
x = value.x * cosine - value.y * sine,
y = value.x * sine + value.y * cosine
};
}
private static float LengthSquared(YmirVec3 value)
{
return value.x * value.x + value.y * value.y + value.z * value.z;
}
private static float LengthSquared(YmirVec2 value)
{
return value.x * value.x + value.y * value.y;
}
private static float Dot(YmirVec3 left, YmirVec3 right)
{
return left.x * right.x + left.y * right.y + left.z * right.z;
}
private static float Dot(YmirVec2 left, YmirVec2 right)
{
return left.x * right.x + left.y * right.y;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: eb6349d47a9ffb14381a03fe5fc21324

View file

@ -9,9 +9,11 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using cfloat2 = CultMath.float2;
using cfloat3 = CultMath.float3;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using float4 = Unity.Mathematics.float4;
using Random = Unity.Mathematics.Random;
public class Zone
@ -54,7 +56,7 @@ public class Zone
Mass = blueprint.Mass;
_itemManager = itemManager;
Settings = settings;
_random = new Random(Convert.ToUInt32(abs(galaxyZone?.Name.GetHashCode() ?? 1337)));
_random = new Random(Convert.ToUInt32(Math.Abs(galaxyZone?.Name.GetHashCode() ?? 1337)));
foreach (var orbit in blueprint.Orbits)
{
@ -87,14 +89,19 @@ public class Zone
foreach (var entityBlueprint in blueprint.Entities)
{
var entity = EntityConstructionBlueprintProjector.InstantiateFromBlueprint(_itemManager, this, entityBlueprint);
var entity = EntityConstructionBlueprintProjector.InstantiateAuthoritativeFromBlueprint(_itemManager, this, entityBlueprint);
Entities.Add(entity);
entity.Activate();
if (entity is Ship {IsPlayerShip: false} ship)
{
Agents.Add(CreateAgent(ship));
if (lengthsq(ship.Position) < 1)
ship.Position = _itemManager.Random.NextFloat3(float3(-blueprint.Radius * .5f), float3(blueprint.Radius * .5f));
if (CultMath.math.lengthsq(ship.CultPosition) < 1)
{
var halfRadius = blueprint.Radius * .5f;
ship.CultPosition = _itemManager.Random.NextFloat3(
new cfloat3(-halfRadius, -halfRadius, -halfRadius),
new cfloat3(halfRadius, halfRadius, halfRadius));
}
}
}
@ -134,6 +141,11 @@ public class Zone
return AsteroidBelts.TryGetValue(bodyKey ?? "", out belt);
}
public bool AsteroidExists(string asteroidBeltKey, int asteroid)
{
return TryGetAsteroidBelt(asteroidBeltKey, out var belt) && belt.ContainsAsteroid(asteroid);
}
public bool TryGetOrbit(string orbitKey, out Orbit orbit)
{
return Orbits.TryGetValue(orbitKey ?? "", out orbit);
@ -171,25 +183,25 @@ public class Zone
{
// Root orbit is fixed at origin
if (string.IsNullOrWhiteSpace(orbitKey))
return float2.zero;
return new float2(0, 0);
if (!Orbits.TryGetValue(orbitKey, out var orbit))
{
Log?.Invoke("Requested orbit is not part of this zone!");
return float2.zero;
return new float2(0, 0);
}
if (!_updatedOrbits.Contains(orbitKey))
{
float2 pos = float2.zero;
float2 pos = new float2(0, 0);
if (orbit.Period > .01f)
{
var phase = (float) frac(_time / orbit.Period);
pos = Orbit.Evaluate(frac(phase + orbit.Phase)) * orbit.Distance;
var phase = (float) Fraction(_time / orbit.Period);
pos = Orbit.Evaluate(Fraction(phase + orbit.Phase)) * orbit.Distance;
if (float.IsNaN(pos.x))
{
//_context.Log("Orbit position is NaN, something went very wrong!");
pos = float2.zero;
pos = new float2(0, 0);
}
}
@ -208,6 +220,10 @@ public class Zone
return TryGetOrbit(orbitKey, out var orbit) ? orbit.Velocity : float2.zero;
}
public cfloat2 GetCultOrbitPosition(string orbitKey) => AetheriaMath.ToCult(GetOrbitPosition(orbitKey));
public cfloat2 GetCultOrbitVelocity(string orbitKey) => AetheriaMath.ToCult(GetOrbitVelocity(orbitKey));
public int NearestAsteroid(string asteroidBeltKey, float2 position)
{
if (!TryGetAsteroidBelt(asteroidBeltKey, out var belt))
@ -218,7 +234,7 @@ public class Zone
float nearestDistance = Single.MaxValue;
for (int i = 0; i < belt.AsteroidCount; i++)
{
var dist = lengthsq(asteroidPositions[i].xz - position);
var dist = LengthSquared(asteroidPositions[i].xz - position);
if (belt.ContainsAsteroid(i) && dist < nearestDistance)
{
nearest = i;
@ -229,9 +245,24 @@ public class Zone
return nearest;
}
public bool AsteroidExists(string asteroidBeltKey, int asteroid)
public int NearestAsteroid(string asteroidBeltKey, cfloat2 position) =>
NearestAsteroid(asteroidBeltKey, AetheriaMath.ToUnity(position));
public bool TryGetCultAsteroidTransform(
string asteroidBeltKey,
int asteroid,
out cfloat2 position,
out float radius)
{
return TryGetAsteroidBelt(asteroidBeltKey, out var belt) && belt.ContainsAsteroid(asteroid);
position = cfloat2.zero;
radius = 0;
if (!TryGetAsteroidBelt(asteroidBeltKey, out var belt) || !belt.ContainsAsteroid(asteroid))
return false;
var transform = belt.Transforms[asteroid];
position = AetheriaMath.ToCult(transform.xy);
radius = transform.w;
return true;
}
private void UpdateAsteroidTransforms(AsteroidBelt belt)
@ -250,11 +281,11 @@ public class Zone
}
else size = Settings.AsteroidSize.Evaluate(asteroid.Size);
var rot = (float) (_time * asteroid.RotationSpeed % (PI * 2));
var pos = Orbit.Evaluate((float) frac(_time / Settings.OrbitPeriod.Evaluate(asteroid.Distance) +
var rot = (float) (_time * asteroid.RotationSpeed % (MathF.PI * 2));
var pos = Orbit.Evaluate((float) Fraction(_time / Settings.OrbitPeriod.Evaluate(asteroid.Distance) +
asteroid.Phase)) * asteroid.Distance + belt.NewOrbitPosition;
//belt.NewPositions[i] = float3(pos.x, GetHeight(pos) + Settings.AsteroidVerticalOffset, pos.y);
belt.NewTransforms[i] = float4(pos.x, pos.y, rot, size);
belt.NewTransforms[i] = new float4(pos.x, pos.y, rot, size);
}
}
@ -284,7 +315,7 @@ public class Zone
}
var resourceCount = belt.Resources.Sum(x => x.Value);
var resource = belt.Resources.MaxBy(x => pow(x.Value, 1f / penetration) * _random.NextFloat());
var resource = belt.Resources.MaxBy(x => MathF.Pow(x.Value, 1f / penetration) * _random.NextFloat());
if (efficiency * _random.NextFloat() * belt.MiningAccumulator[(miner, asteroid)] * resourceCount / Settings.MiningDifficulty > 1)
{
belt.MiningAccumulator.Remove((miner, asteroid));
@ -298,7 +329,9 @@ public class Zone
}
}
public SecurityLevel GetSecurityLevel(float2 pos)
public SecurityLevel GetSecurityLevel(float2 pos) => GetSecurityLevel(AetheriaMath.ToCult(pos));
public SecurityLevel GetSecurityLevel(cfloat2 pos)
{
if (GalaxyZone.Owner==null) return SecurityLevel.Open;
@ -307,7 +340,7 @@ public class Zone
{
if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction.HasSameKey(GalaxyZone.Owner))
{
if (orbitalEntity.SecurityLevel > security && length(orbitalEntity.Position.xz - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier)
if (orbitalEntity.SecurityLevel > security && CultMath.math.length(orbitalEntity.CultPositionXZ - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier)
security = orbitalEntity.SecurityLevel;
}
}
@ -317,16 +350,16 @@ public class Zone
public float GetHeight(float2 position)
{
var result = -PowerPulse(length(position)/(Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth;
var result = -PowerPulse(Length(position)/(Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth;
foreach (var body in PlanetInstances.Values)
{
var p = position - body.Orbit.Position;
var distSqr = lengthsq(p);
var distSqr = LengthSquared(p);
var gravityRadius = body.GravityWellRadius;
if (distSqr < gravityRadius*gravityRadius)
{
var depth = body.GravityWellDepth;
result -= PowerPulse(sqrt(distSqr) / gravityRadius, body.GravityDepthExponent) * depth;
result -= PowerPulse(MathF.Sqrt(distSqr) / gravityRadius, body.GravityDepthExponent) * depth;
}
if (body is GasGiant gas)
@ -337,7 +370,7 @@ public class Zone
var depth = gas.GravityWavesDepth;
var frequency = Settings.WaveFrequency.Evaluate(body.Mass);
var speed = gas.GravityWavesSpeed;
result -= RadialWaves(sqrt(distSqr) / waveRadius, 8, 1.25f, frequency, (float) (_time * speed)) * depth;
result -= RadialWaves(MathF.Sqrt(distSqr) / waveRadius, 8, 1.25f, frequency, (float) (_time * speed)) * depth;
}
}
}
@ -345,6 +378,8 @@ public class Zone
return result;
}
public float GetHeight(cfloat2 position) => GetHeight(AetheriaMath.ToUnity(position));
public float GetLight(float2 position)
{
var light = 0f;
@ -353,11 +388,11 @@ public class Zone
if (body is Sun sun)
{
var p = position - body.Orbit.Position;
var distSqr = lengthsq(p);
var distSqr = LengthSquared(p);
var lightRadius = sun.LightRadius;
if (distSqr < lightRadius * lightRadius)
{
light += PowerPulse(sqrt(distSqr) / lightRadius, 8);
light += PowerPulse(MathF.Sqrt(distSqr) / lightRadius, 8);
}
}
}
@ -365,24 +400,28 @@ public class Zone
return light;
}
public float GetLight(cfloat2 position) => GetLight(AetheriaMath.ToUnity(position));
public float2 GetForce(float2 position)
{
var normal = GetNormal(position);
var f = new float2(normal.x, normal.z);
return f * Settings.GravityStrength * lengthsq(f);
return f * Settings.GravityStrength * LengthSquared(f);
}
public cfloat2 GetForce(cfloat2 position) => AetheriaMath.ToCult(GetForce(AetheriaMath.ToUnity(position)));
public static float PowerPulse(float x, float exponent)
{
x *= 2;
x = clamp(x, -1, 1);
return pow((x + 1) * (1 - x), exponent);
x = Clamp(x, -1, 1);
return MathF.Pow((x + 1) * (1 - x), exponent);
}
public static float RadialWaves(float x, float maskExponent, float sineExponent, float frequency, float phase)
{
//x *= 2;
return PowerPulse(x, maskExponent) * cos(pow(x*2, sineExponent) * frequency + phase);
return PowerPulse(x, maskExponent) * MathF.Cos(MathF.Pow(x*2, sineExponent) * frequency + phase);
}
public float3 GetNormal(float2 pos, float step = .1f, float mul = 1)
@ -394,7 +433,41 @@ public class Zone
// Deduce terrain normal
float3 normal = new float3((hL - hR), (hD - hU), step*2);
return normalize(normal).xzy;
return Normalize(normal).xzy;
}
public cfloat3 GetNormal(cfloat2 pos, float step = .1f, float mul = 1) =>
AetheriaMath.ToCult(GetNormal(AetheriaMath.ToUnity(pos), step, mul));
private static float Clamp(float value, float min, float max)
{
return value < min ? min : value > max ? max : value;
}
private static double Fraction(double value)
{
return value - Math.Floor(value);
}
private static float Fraction(float value)
{
return value - MathF.Floor(value);
}
private static float Length(float2 value)
{
return MathF.Sqrt(LengthSquared(value));
}
private static float LengthSquared(float2 value)
{
return value.x * value.x + value.y * value.y;
}
private static float3 Normalize(float3 value)
{
var length = MathF.Sqrt(value.x * value.x + value.y * value.y + value.z * value.z);
return length <= 0 ? new float3(0, 0, 0) : value / length;
}
}
@ -484,8 +557,8 @@ public class GasGiant : Planet
public class Sun : GasGiant
{
public float3 LightColor { get; }
public float3 FogTintColor { get; }
public cfloat3 LightColor { get; }
public cfloat3 FogTintColor { get; }
public float LightRadiusMultiplier { get; }
public float LightRadius;
@ -559,15 +632,15 @@ public class Orbit
public float Distance { get; }
public float Phase { get; }
public float2 FixedPosition { get; }
public float2 Velocity = float2.zero;
public float2 Position = float2.zero;
public float2 PreviousPosition = float2.zero;
public float2 Velocity = new float2(0, 0);
public float2 Position = new float2(0, 0);
public float2 PreviousPosition = new float2(0, 0);
public float Period;
public static float2 Evaluate(float phase)
{
phase *= PI * 2;
return new float2(cos(phase), sin(phase));
phase *= MathF.PI * 2;
return new float2(MathF.Cos(phase), MathF.Sin(phase));
}
public Orbit(PlanetSettings settings, OrbitConstructionData data)

View file

@ -5,10 +5,10 @@
using System.Collections;
using System.Collections.Generic;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using cfloat3 = CultMath.float3;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using float4 = Unity.Mathematics.float4;
public class ZoneConstructionBlueprint
{
@ -84,9 +84,9 @@ public class GasGiantConstructionData : BodyConstructionData
public class SunConstructionData : GasGiantConstructionData
{
public float3 LightColor = float3.zero;
public cfloat3 LightColor = cfloat3.zero;
public float3 FogTintColor = float3.zero;
public cfloat3 FogTintColor = cfloat3.zero;
public float LightRadiusMultiplier = 1;
}
@ -112,6 +112,6 @@ public class OrbitConstructionData
public float Phase;
public float2 FixedPosition = float2.zero;
public float2 FixedPosition = new float2(0, 0);
}

View file

@ -9,9 +9,9 @@ using System.Globalization;
using System.Linq;
using GameCult.Aetheria.State.Unity;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using float3 = Unity.Mathematics.float3;
using float4 = Unity.Mathematics.float4;
using Random = Unity.Mathematics.Random;
public static class ZoneGenerator
@ -21,7 +21,7 @@ public static class ZoneGenerator
public float2 Center;
public float Radius;
public float Area => PI * Radius * Radius;
public float Area => MathF.PI * Radius * Radius;
public Circle(float2 center, float radius)
{
@ -29,8 +29,8 @@ public static class ZoneGenerator
Radius = radius;
}
public float DistanceTo(float2 point) => length(point - Center) - Radius;
public float DistanceTo(Circle other) => length(other.Center - Center) - Radius - other.Radius;
public float DistanceTo(float2 point) => ZoneGenerator.Length(point - Center) - Radius;
public float DistanceTo(Circle other) => ZoneGenerator.Length(other.Center - Center) - Radius - other.Radius;
}
private const int MaximumPlacementSamples = 32;
@ -45,9 +45,9 @@ public static class ZoneGenerator
{
var blueprint = new ZoneConstructionBlueprint();
var random = new Random(unchecked((uint) galaxyZone.Name.GetHashCode()) ^ hash(galaxyZone.Position));
var random = new Random(unchecked((uint) galaxyZone.Name.GetHashCode()) ^ ZoneGenerator.Hash(galaxyZone.Position));
var density = saturate(galaxy.Background.CloudDensity(galaxyZone.Position)/2);
var density = ZoneGenerator.Saturate(galaxy.Background.CloudDensity(galaxyZone.Position)/2);
blueprint.Radius = zoneSettings.ZoneRadius.Evaluate(density);
blueprint.Mass = zoneSettings.ZoneMass.Evaluate(density);
var targetSubzoneCount = zoneSettings.SubZoneCount.Evaluate(density);
@ -57,11 +57,11 @@ public static class ZoneGenerator
var planets = new List<GeneratorPlanet>();
if (targetSubzoneCount > 1)
{
var zoneBoundary = new Circle(float2.zero, blueprint.Radius * zoneSettings.ZoneBoundaryRadius);
var zoneBoundary = new Circle(new float2(0, 0), blueprint.Radius * zoneSettings.ZoneBoundaryRadius);
float boundaryTangentRadius(float2 point) => -zoneBoundary.DistanceTo(point);
var occupiedAreas = new List<Circle>();
float tangentRadius(float2 point) => min(boundaryTangentRadius(point), occupiedAreas.Min(circle => circle.DistanceTo(point)));
float tangentRadius(float2 point) => MathF.Min(boundaryTangentRadius(point), occupiedAreas.Min(circle => circle.DistanceTo(point)));
var startPosition = random.NextFloat(blueprint.Radius * .25f, blueprint.Radius * .5f) * random.NextFloat2Direction();
occupiedAreas.Add(new Circle(startPosition, boundaryTangentRadius(startPosition)));
@ -91,7 +91,7 @@ public static class ZoneGenerator
}
}
else
planets.AddRange(GenerateEntities(zoneSettings, ref random, blueprint.Mass, blueprint.Radius, float2.zero));
planets.AddRange(GenerateEntities(zoneSettings, ref random, blueprint.Mass, blueprint.Radius, new float2(0, 0)));
// Create collections to map between zone generator output and database entries
var orbitMap = new Dictionary<GeneratorPlanet, OrbitConstructionData>();
@ -186,24 +186,24 @@ public static class ZoneGenerator
else if (planetData is GasGiantConstructionData gas)
{
if (gas is SunConstructionData sun)
{
{
float primary = random.NextFloat();
float secondary = frac(primary + 1 + zoneSettings.SunSecondaryColorDistance * (random.NextFloat() > .5 ? 1 : -1));
float secondary = ZoneGenerator.Fraction(primary + 1 + zoneSettings.SunSecondaryColorDistance * (random.NextFloat() > .5 ? 1 : -1));
gas.Colors = new []
{
float4(ColorMath.HsvToRgb(float3(primary, zoneSettings.SunColorSaturation, .5f)), 0),
float4(ColorMath.HsvToRgb(float3(secondary, zoneSettings.SunColorSaturation, 1)), 1)
new float4(AetheriaMath.ToUnity(ColorMath.HsvToRgb(new CultMath.float3(primary, zoneSettings.SunColorSaturation, .5f))), 0),
new float4(AetheriaMath.ToUnity(ColorMath.HsvToRgb(new CultMath.float3(secondary, zoneSettings.SunColorSaturation, 1))), 1)
};
sun.FogTintColor = ColorMath.HsvToRgb(float3(primary, zoneSettings.SunFogTintSaturation, 1));
sun.LightColor = ColorMath.HsvToRgb(float3(primary, zoneSettings.SunLightSaturation, 1));
sun.FogTintColor = ColorMath.HsvToRgb(new CultMath.float3(primary, zoneSettings.SunFogTintSaturation, 1));
sun.LightColor = ColorMath.HsvToRgb(new CultMath.float3(primary, zoneSettings.SunLightSaturation, 1));
gas.FirstOffsetDomainRotationSpeed = 5;
}
else
{
// Define primary color and two adjacent colors
float primary = random.NextFloat();
float right = frac(primary + zoneSettings.GasGiantBandColorSeparation);
float left = frac(primary + 1 - zoneSettings.GasGiantBandColorSeparation);
float right = ZoneGenerator.Fraction(primary + zoneSettings.GasGiantBandColorSeparation);
float left = ZoneGenerator.Fraction(primary + 1 - zoneSettings.GasGiantBandColorSeparation);
// Create n time keys from 0 to 1
var bandCount = (int) (zoneSettings.GasGiantBandCount.Evaluate(random.NextFloat()) + .5f);
@ -213,10 +213,10 @@ public static class ZoneGenerator
// Each band has a chance of being either the primary or one of the adjacent hues
// Saturation and Value are random with curves applied
gas.Colors = times
.Select(time => float4(ColorMath.HsvToRgb(float3(
.Select(time => new float4(AetheriaMath.ToUnity(ColorMath.HsvToRgb(new CultMath.float3(
random.NextFloat() > zoneSettings.GasGiantBandAltColorChance ? primary : (random.NextFloat() > .5f ? right : left),
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()),
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat()))),time))
zoneSettings.GasGiantBandSaturation.Evaluate(random.NextFloat())))),time))
.ToArray();
gas.FirstOffsetDomainRotationSpeed = 0;
@ -237,7 +237,7 @@ public static class ZoneGenerator
var stationCount = (int)(random.NextFloat() * (factionPresence + 1)) + storyStations.Length;
var potentialLagrangePoints = planets
.Where(p => p.Parent != null && p.Parent.Children
.TrueForAll(c => !(c != p && abs(c.Distance - p.Distance) < .1f))) // Filter Rosettes
.TrueForAll(c => !(c != p && MathF.Abs(c.Distance - p.Distance) < .1f))) // Filter Rosettes
.OrderBy(p => p.Distance)
.ToArray();
// Pick a selection from the middle of the distribution
@ -252,7 +252,10 @@ public static class ZoneGenerator
LoadoutGenerator GetLoadoutGenerator(Faction faction)
{
if (!loadoutGenerators.ContainsKey(faction))
loadoutGenerators[faction] = new LoadoutGenerator(ref random, itemManager, runtimeCatalog, galaxy, galaxyZone, faction, .5f);
{
var loadoutRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue));
loadoutGenerators[faction] = new LoadoutGenerator(ref loadoutRandom, itemManager, runtimeCatalog, galaxy, galaxyZone, faction, .5f);
}
return loadoutGenerators[faction];
}
@ -263,7 +266,7 @@ public static class ZoneGenerator
OrbitKey = Zone.OrbitKey(Guid.NewGuid()),
ParentOrbitKey = baseOrbit.ParentOrbitKey,
Distance = baseOrbit.Distance,
Phase = baseOrbit.Phase + PI / 3 * sign(random.NextFloat() - .5f)
Phase = baseOrbit.Phase + MathF.PI / 3 * ZoneGenerator.Sign(random.NextFloat() - .5f)
};
blueprint.Orbits.Add(lagrangeOrbit);
return lagrangeOrbit;
@ -315,7 +318,7 @@ public static class ZoneGenerator
for (var i = storyStations.Length; i < selectedStationOrbits.Length; i++)
{
var orbit = selectedStationOrbits[i];
var security = (SecurityLevel) ((int) ((1f - pow(random.NextFloat(), factionPresence / 2f)) * 3f));
var security = (SecurityLevel) ((int) ((1f - MathF.Pow(random.NextFloat(), factionPresence / 2f)) * 3f));
var lagrangeOrbit = CreateLagrangeOrbit(orbit);
var station = GetLoadoutGenerator(nearestFaction).GenerateStationLoadout();
@ -463,6 +466,31 @@ public static class ZoneGenerator
return root.AllPlanets().ToArray();
}
private static uint Hash(float2 value)
{
return unchecked((uint)HashCode.Combine(value.x, value.y));
}
private static float Fraction(float value)
{
return value - MathF.Floor(value);
}
private static float Length(float2 value)
{
return MathF.Sqrt(value.x * value.x + value.y * value.y);
}
private static float Saturate(float value)
{
return value < 0 ? 0 : value > 1 ? 1 : value;
}
private static float Sign(float value)
{
return value < 0 ? -1 : value > 0 ? 1 : 0;
}
}
// All orbiting bodies are referred to here as "Planets"
@ -514,8 +542,9 @@ public class GeneratorPlanet
var p1 = Orbit.Evaluate(1.0f / vertices) * dist;
// Maximum child distance is half the distance to the neighbor minus the neighbor's radius
var p0ChildDist = (distance(p0, p1) * proportion - Settings.PlanetSafetyRadius.Evaluate(sharedMass * (1 - proportion))) * .75f;
var p1ChildDist = (distance(p0, p1) * (1 - proportion) - Settings.PlanetSafetyRadius.Evaluate(sharedMass * proportion)) * .75f;
var neighborDistance = Length(p0 - p1);
var p0ChildDist = (neighborDistance * proportion - Settings.PlanetSafetyRadius.Evaluate(sharedMass * (1 - proportion))) * .75f;
var p1ChildDist = (neighborDistance * (1 - proportion) - Settings.PlanetSafetyRadius.Evaluate(sharedMass * proportion)) * .75f;
for (int i = 0; i < vertices; i++)
{
@ -573,7 +602,7 @@ public class GeneratorPlanet
{
var oldDistances = (float[]) distances.Clone();
for (var i = 0; i < count; i++)
distances[i] = lerp(ChildDistanceMinimum, ChildDistanceMaximum,
distances[i] = Lerp(ChildDistanceMinimum, ChildDistanceMaximum,
(oldDistances[i] - oldDistances[0]) / (oldDistances[count - 1] - oldDistances[0])) + Settings.PlanetSafetyRadius.Evaluate(masses[i]);
}
@ -593,7 +622,7 @@ public class GeneratorPlanet
//child.Period = Context.GlobalData.OrbitalPeriod(child.Distance);
child.ChildDistanceMinimum = Settings.PlanetSafetyRadius.Evaluate(child.Mass) * 2;
// Maximum child distance of child is the smallest distance to either of its neighbors
child.ChildDistanceMaximum = min(i == 0 ? child.Distance - ChildDistanceMinimum : child.Distance - distances[i - 1],
child.ChildDistanceMaximum = MathF.Min(i == 0 ? child.Distance - ChildDistanceMinimum : child.Distance - distances[i - 1],
i < count - 1 ? distances[i + 1] - child.Distance : float.PositiveInfinity);
if (float.IsNaN(child.Distance))
throw new NotFiniteNumberException($"Planet created with NaN distance, something went very wrong!");
@ -602,4 +631,34 @@ public class GeneratorPlanet
}
}
}
private static uint Hash(float2 value)
{
return unchecked((uint)HashCode.Combine(value.x, value.y));
}
private static float Fraction(float value)
{
return value - MathF.Floor(value);
}
private static float Length(float2 value)
{
return MathF.Sqrt(value.x * value.x + value.y * value.y);
}
private static float Lerp(float from, float to, float t)
{
return from + (to - from) * t;
}
private static float Saturate(float value)
{
return value < 0 ? 0 : value > 1 ? 1 : value;
}
private static float Sign(float value)
{
return value < 0 ? -1 : value > 0 ? 1 : 0;
}
}