Sync daemon authority migration
This commit is contained in:
parent
030b4b5d74
commit
39a0dc8737
295 changed files with 25121 additions and 7360 deletions
|
|
@ -2,6 +2,7 @@
|
|||
"name": "Aetheria.Shared.Unity",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"CultMath",
|
||||
"Unity.Mathematics",
|
||||
"UniRx",
|
||||
"Ink",
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using float3 = CultMath.float3;
|
||||
|
||||
public class Faction : INamedEntry
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
public static class Noise1D
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
496
Assets/Scripts/ServerShared/StatRecipe.cs
Normal file
496
Assets/Scripts/ServerShared/StatRecipe.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/ServerShared/StatRecipe.cs.meta
Normal file
2
Assets/Scripts/ServerShared/StatRecipe.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 04283d92f0302fc49978de4ee33c6a6c
|
||||
8
Assets/Scripts/ServerShared/Stats.meta
Normal file
8
Assets/Scripts/ServerShared/Stats.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e4871bd17ece70646b5e8f3dbf42e923
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Unity.Mathematics;
|
||||
using float2 = CultMath.float2;
|
||||
|
||||
public class Wormhole
|
||||
{
|
||||
public float2 Position;
|
||||
public GalaxyZone Target;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
845
Assets/Scripts/ServerShared/YmirPhysicsContracts.cs
Normal file
845
Assets/Scripts/ServerShared/YmirPhysicsContracts.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/ServerShared/YmirPhysicsContracts.cs.meta
Normal file
2
Assets/Scripts/ServerShared/YmirPhysicsContracts.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eb6349d47a9ffb14381a03fe5fc21324
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue