Sync daemon authority migration

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

View file

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator
// version 1.14.0
// version 1.19.0
// from Assets/Resources/Aetheria.inputactions
//
// Changes to this file may cause incorrect behavior and will be lost if
@ -85,6 +85,7 @@ public partial class @AetheriaInput: IInputActionCollection2, IDisposable
public @AetheriaInput()
{
asset = InputActionAsset.FromJson(@"{
""version"": 1,
""name"": ""Aetheria"",
""maps"": [
{

View file

@ -6,7 +6,7 @@ using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Random = Unity.Mathematics.Random;
using Random = CultMath.Random;
public class NameTools : EditorWindow
{

View file

@ -73,7 +73,7 @@ public class ActionBarConsumableBinding : ActionBarBinding
public override void Activate()
{
Entity.TryActivateConsumable(Target);
ActionGameManager.Instance.TryRequestDaemonActionBarConsumable(TargetItemKey);
}
public override void Deactivate()
@ -162,13 +162,13 @@ public class ActionBarGearBinding : ActionBarBinding
public override void Activate()
{
Active = true;
Behavior.Activate();
ActionGameManager.Instance.TryRequestDaemonActionBarBehavior(EquipmentIndex, BehaviorIndex, true);
}
public override void Deactivate()
{
Active = false;
Behavior.Deactivate();
ActionGameManager.Instance.TryRequestDaemonActionBarBehavior(EquipmentIndex, BehaviorIndex, false);
}
public override void Update()
@ -191,18 +191,12 @@ public class ActionBarWeaponGroupBinding : ActionBarBinding
public override void Activate()
{
foreach (var weapon in Entity.WeaponGroups[Group].weapons)
{
weapon.Activate();
}
ActionGameManager.Instance.TryRequestDaemonActionBarWeaponGroup(Group, true);
}
public override void Deactivate()
{
foreach (var weapon in Entity.WeaponGroups[Group].weapons)
{
weapon.Deactivate();
}
ActionGameManager.Instance.TryRequestDaemonActionBarWeaponGroup(Group, false);
}
public override void Update()

View file

@ -0,0 +1,184 @@
using System.Collections.Generic;
using GameCult.Aetheria.State.Unity;
using UnityEngine;
using UnityEngine.Rendering;
public sealed class AetheriaDaemonIndirectRenderer : MonoBehaviour
{
[SerializeField]
private AetheriaDaemonObserver observer;
[SerializeField]
private AetheriaDaemonRenderAssetCatalog assetCatalog;
[SerializeField]
private bool logMissingAssets;
[SerializeField]
private Camera targetCamera;
private MaterialPropertyBlock _properties;
private readonly Dictionary<int, AetheriaDaemonRenderGroupBatch> _batchesByGroupId =
new Dictionary<int, AetheriaDaemonRenderGroupBatch>();
private readonly HashSet<int> _seenGroupIds = new HashSet<int>();
private readonly List<int> _staleGroupIds = new List<int>();
private static readonly int MatrixBufferPropertyId =
Shader.PropertyToID(AetheriaDaemonRenderBuffer.MatrixBufferPropertyName);
private static readonly int InstanceCountPropertyId =
Shader.PropertyToID(AetheriaDaemonRenderBuffer.InstanceCountPropertyName);
public int LastDrawnCount { get; private set; }
public bool HasDrawTarget =>
observer != null &&
assetCatalog != null;
private void Reset()
{
observer = GetComponent<AetheriaDaemonObserver>();
}
private void LateUpdate()
{
DrawLatest();
}
public bool DrawLatest()
{
LastDrawnCount = 0;
if (!HasDrawTarget)
{
return false;
}
if (!observer.HasRenderNativeView)
{
return false;
}
_properties ??= new MaterialPropertyBlock();
var drewAny = false;
var index = observer.LastSoaIndex;
var view = observer.LastRenderNativeView;
_seenGroupIds.Clear();
foreach (var renderGroup in index.RenderGroups)
{
_seenGroupIds.Add(renderGroup.GroupId);
if (!assetCatalog.TryResolveMesh(renderGroup.MeshKey, out var mesh) ||
!assetCatalog.TryResolveMaterial(renderGroup.MaterialKey, out var material))
{
if (logMissingAssets)
{
Debug.LogWarning(
$"Could not resolve daemon render group {renderGroup.GroupId} " +
$"mesh='{renderGroup.MeshKey}' material='{renderGroup.MaterialKey}'.");
}
continue;
}
if (!_batchesByGroupId.TryGetValue(renderGroup.GroupId, out var batch))
{
batch = new AetheriaDaemonRenderGroupBatch();
_batchesByGroupId.Add(renderGroup.GroupId, batch);
}
if (batch.LastUploadedGeneration != view.Generation)
{
batch.UploadLatest(view, renderGroup, mesh, renderGroup.DefaultScale);
}
if (!batch.HasGpuBuffers)
{
continue;
}
_properties.Clear();
_properties.SetInt(InstanceCountPropertyId, batch.InstanceCount);
_properties.SetBuffer(MatrixBufferPropertyId, batch.MatrixBuffer);
Graphics.DrawMeshInstancedIndirect(
mesh,
renderGroup.SubMeshIndex,
material,
GetDrawBounds(renderGroup),
batch.IndirectArgsBuffer,
0,
_properties,
GetShadowCastingMode(renderGroup),
renderGroup.ReceiveShadows,
renderGroup.Layer,
targetCamera);
LastDrawnCount += batch.InstanceCount;
drewAny = true;
}
ReleaseStaleBatches();
return drewAny;
}
private Bounds GetDrawBounds(AetheriaRuntimeDaemonRenderGroupDocument renderGroup)
{
return new Bounds(
new Vector3(
renderGroup.BoundsCenterX,
renderGroup.BoundsCenterY,
renderGroup.BoundsCenterZ),
new Vector3(
renderGroup.BoundsSizeX,
renderGroup.BoundsSizeY,
renderGroup.BoundsSizeZ));
}
private static ShadowCastingMode GetShadowCastingMode(AetheriaRuntimeDaemonRenderGroupDocument renderGroup)
{
switch (renderGroup.ShadowMode)
{
case AetheriaRuntimeDaemonRenderShadowModes.Off:
return ShadowCastingMode.Off;
case AetheriaRuntimeDaemonRenderShadowModes.TwoSided:
return ShadowCastingMode.TwoSided;
case AetheriaRuntimeDaemonRenderShadowModes.ShadowsOnly:
return ShadowCastingMode.ShadowsOnly;
default:
return ShadowCastingMode.On;
}
}
private void OnDisable()
{
foreach (var batch in _batchesByGroupId.Values)
{
batch.Dispose();
}
_batchesByGroupId.Clear();
_seenGroupIds.Clear();
}
private void ReleaseStaleBatches()
{
if (_batchesByGroupId.Count == _seenGroupIds.Count)
{
return;
}
_staleGroupIds.Clear();
foreach (var groupId in _batchesByGroupId.Keys)
{
if (!_seenGroupIds.Contains(groupId))
{
_staleGroupIds.Add(groupId);
}
}
for (var i = 0; i < _staleGroupIds.Count; i++)
{
var groupId = _staleGroupIds[i];
_batchesByGroupId[groupId].Dispose();
_batchesByGroupId.Remove(groupId);
}
_staleGroupIds.Clear();
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 66b38a2f45120a041941eae4412f7da1

View file

@ -0,0 +1,158 @@
using System;
using GameCult.Aetheria.State.Unity;
using UnityEngine;
public sealed class AetheriaDaemonObserver : MonoBehaviour
{
[SerializeField]
private bool pollInUpdate = true;
[SerializeField]
private float pollIntervalSeconds = 0.05f;
[SerializeField]
private bool logChanges;
[SerializeField]
private string clientId = "unity-observer";
private readonly AetheriaRuntimeDaemonObservationCursor _cursor = new AetheriaRuntimeDaemonObservationCursor();
private float _nextPollTime;
private AetheriaRuntimeDaemonOperationClient _operationClient;
private AetheriaDaemonOperations _operations;
private AetheriaDaemonSoaMemoryMap _soaMemoryMap;
private AetheriaDaemonRenderNativeView _renderNativeView;
public AetheriaRuntimeObservedDaemonState LastObservedState { get; private set; }
public AetheriaRuntimeDaemonObservationResult LastObservation { get; private set; }
public long LastFrameId => _cursor.LastFrameId;
public long LastSoaGeneration => _cursor.LastSoaGeneration;
public bool HasAuthoritativeState => LastObservedState != null && LastObservedState.IsAuthoritative;
public bool HasSoaView => LastObservedState != null && LastObservedState.HasSoaView;
public AetheriaRuntimeDaemonSoaViewIndex LastSoaIndex =>
LastObservedState?.SoaIndex ?? AetheriaRuntimeDaemonSoaViewIndex.Empty;
public AetheriaDaemonSoaMemoryMap LastSoaMemoryMap => _soaMemoryMap;
public AetheriaDaemonRenderNativeView LastRenderNativeView => _renderNativeView;
public bool HasRenderNativeView => _renderNativeView.IsCreated;
public AetheriaDaemonOperations Operations => _operations ??= new AetheriaDaemonOperations(this);
public event Action<AetheriaRuntimeObservedDaemonState, AetheriaRuntimeDaemonObservationResult> ObservedDaemonStateChanged;
internal AetheriaRuntimeDaemonCommandEnvelope SendOperation(
AetheriaRuntimeDaemonCommandKinds kind,
Action<AetheriaRuntimeDaemonCommandDocument> configure = null)
{
_operationClient ??= new AetheriaRuntimeDaemonOperationClient(
ActionGameManager.RuntimeStateFilePath,
clientId,
LastObservedState?.Frame.SessionId ?? "local");
var command = _operationClient.Create(kind, LastObservedState);
configure?.Invoke(command);
if (!_operationClient.TrySend(command, out var envelope, out var error))
{
throw new InvalidOperationException(
$"Failed to submit Aetheria daemon operation {kind}: {error}");
}
if (logChanges)
{
Debug.Log($"Submitted Aetheria daemon operation {envelope!.Kind}: {envelope.CommandId}");
}
return envelope!;
}
private void Update()
{
if (!pollInUpdate || Time.unscaledTime < _nextPollTime)
{
return;
}
_nextPollTime = Time.unscaledTime + Mathf.Max(0.001f, pollIntervalSeconds);
Poll();
}
public bool Poll()
{
if (!AetheriaRuntimeStateReader.TryReadObservedDaemonState(
ActionGameManager.RuntimeStateFilePath,
out var observed))
{
LastObservation = _cursor.Observe(null);
return false;
}
var result = _cursor.Observe(observed);
LastObservedState = observed;
LastObservation = result;
if (result.SoaViewChanged)
{
RemapSoaView(observed);
}
if (result.Changed)
{
ObservedDaemonStateChanged?.Invoke(observed, result);
if (logChanges)
{
Debug.Log(
$"Observed Aetheria daemon frame {result.FrameId} " +
$"soa generation {result.SoaGeneration} authoritative={result.IsAuthoritative}");
}
}
return true;
}
public void ResetObservation()
{
_cursor.Reset();
DisposeSoaMemoryMap();
LastObservedState = null;
LastObservation = null;
}
private void OnDisable()
{
DisposeSoaMemoryMap();
}
private void RemapSoaView(AetheriaRuntimeObservedDaemonState observed)
{
DisposeSoaMemoryMap();
if (observed == null || !observed.HasSoaView)
{
return;
}
if (AetheriaDaemonSoaMemoryMap.TryOpen(observed.SoaIndex, out var map, out var error))
{
_soaMemoryMap = map;
if (!AetheriaDaemonRenderNativeView.TryCreate(observed.SoaIndex, map, out _renderNativeView))
{
_renderNativeView = default;
if (logChanges)
{
Debug.LogWarning("Mapped Aetheria daemon SoA memory, but no complete render native view was published.");
}
}
return;
}
if (logChanges)
{
Debug.LogWarning($"Could not map Aetheria daemon SoA view read-only: {error}");
}
}
private void DisposeSoaMemoryMap()
{
_renderNativeView = default;
_soaMemoryMap?.Dispose();
_soaMemoryMap = null;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4f5008a2876ee014fb9e442f1f6103e0

View file

@ -0,0 +1,81 @@
using System;
using UnityEngine;
[CreateAssetMenu(
fileName = "AetheriaDaemonRenderAssetCatalog",
menuName = "Aetheria/Daemon Render Asset Catalog")]
public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
{
[SerializeField]
private MeshEntry[] meshes = Array.Empty<MeshEntry>();
[SerializeField]
private MaterialEntry[] materials = Array.Empty<MaterialEntry>();
public bool TryResolveMesh(string key, out Mesh mesh)
{
mesh = null;
if (string.IsNullOrWhiteSpace(key))
{
return false;
}
for (var i = 0; i < meshes.Length; i++)
{
if (string.Equals(meshes[i].Key, key, StringComparison.Ordinal))
{
mesh = meshes[i].Mesh;
return mesh != null;
}
}
mesh = Resources.Load<Mesh>(NormalizeResourceKey(key));
return mesh != null;
}
public bool TryResolveMaterial(string key, out Material material)
{
material = null;
if (string.IsNullOrWhiteSpace(key))
{
return false;
}
for (var i = 0; i < materials.Length; i++)
{
if (string.Equals(materials[i].Key, key, StringComparison.Ordinal))
{
material = materials[i].Material;
return material != null;
}
}
material = Resources.Load<Material>(NormalizeResourceKey(key));
return material != null;
}
private static string NormalizeResourceKey(string key)
{
const string resourcesPrefix = "resources://";
if (key.StartsWith(resourcesPrefix, StringComparison.OrdinalIgnoreCase))
{
return key.Substring(resourcesPrefix.Length);
}
return key;
}
[Serializable]
private struct MeshEntry
{
public string Key;
public Mesh Mesh;
}
[Serializable]
private struct MaterialEntry
{
public string Key;
public Material Material;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8fa370b1ea2904d40ae295007ac76b48

View file

@ -0,0 +1,294 @@
using GameCult.Aetheria.State.Unity;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public sealed class AetheriaDaemonRenderBuffer : MonoBehaviour
{
public const string MatrixBufferPropertyName = "_AetheriaObjectToWorld";
public const string InstanceCountPropertyName = "_AetheriaInstanceCount";
private const int MatrixStrideBytes = 64;
private const int DrawMeshInstancedIndirectArgsCount = 5;
[SerializeField]
private AetheriaDaemonObserver observer;
[SerializeField]
private Mesh indirectArgsMesh;
[SerializeField]
private int innerLoopBatchCount = 64;
[SerializeField]
private bool updateInLateUpdate;
private readonly uint[] _indirectArgs = new uint[DrawMeshInstancedIndirectArgsCount];
private NativeArray<float4x4> _nativeMatrices;
private GraphicsBuffer _matrixBuffer;
private GraphicsBuffer _indirectArgsBuffer;
private long _allocatedGeneration = long.MinValue;
private int _allocatedCount;
public GraphicsBuffer MatrixBuffer => _matrixBuffer;
public GraphicsBuffer IndirectArgsBuffer => _indirectArgsBuffer;
public int InstanceCount { get; private set; }
public long LastUploadedFrameId { get; private set; }
public long LastUploadedGeneration { get; private set; }
public bool HasRenderableView => observer != null && observer.HasRenderNativeView;
public bool HasGpuBuffers =>
_matrixBuffer != null &&
_matrixBuffer.IsValid() &&
InstanceCount > 0;
private static readonly int MatrixBufferPropertyId = Shader.PropertyToID(MatrixBufferPropertyName);
private static readonly int InstanceCountPropertyId = Shader.PropertyToID(InstanceCountPropertyName);
private void Reset()
{
observer = GetComponent<AetheriaDaemonObserver>();
}
private void LateUpdate()
{
if (updateInLateUpdate)
{
UploadLatest();
}
}
public bool UploadLatest()
{
return UploadLatest(indirectArgsMesh);
}
public bool UploadLatest(Mesh argsMesh)
{
return UploadLatest(argsMesh, -1);
}
public bool UploadLatest(Mesh argsMesh, int renderGroupId)
{
return UploadLatest(argsMesh, renderGroupId, 0);
}
public bool UploadLatest(Mesh argsMesh, int renderGroupId, int subMeshIndex)
{
if (!HasRenderableView)
{
InstanceCount = 0;
UpdateIndirectArgs(null, 0);
return false;
}
var view = observer.LastRenderNativeView;
EnsureCapacity(view);
if (!_nativeMatrices.IsCreated || _nativeMatrices.Length == 0)
{
InstanceCount = 0;
UpdateIndirectArgs(argsMesh, 0);
return false;
}
var index = observer.LastSoaIndex;
var defaultScale = ResolveDefaultScale(index, renderGroupId);
var renderLod = ResolveRenderLod(index, renderGroupId);
var count = renderGroupId >= 0 && view.HasRenderGroupId
? BuildRenderGroupMatrices(view, renderGroupId, renderLod, defaultScale)
: BuildAllMatrices(view, renderLod, defaultScale);
_matrixBuffer.SetData(_nativeMatrices, 0, 0, count);
InstanceCount = count;
LastUploadedFrameId = view.FrameId;
LastUploadedGeneration = view.Generation;
UpdateIndirectArgs(argsMesh, count, subMeshIndex);
return true;
}
private int BuildAllMatrices(AetheriaDaemonRenderNativeView view, int renderLod, float defaultScale)
{
if (view.HasRenderVisibility || renderLod >= 0)
{
return BuildCompactedMatrices(view, -1, renderLod, defaultScale);
}
var count = math.min(view.Count, _nativeMatrices.Length);
var handle = view.ScheduleBuildRenderMatrices(
_nativeMatrices,
defaultScale,
innerLoopBatchCount);
handle.Complete();
return count;
}
private int BuildRenderGroupMatrices(
AetheriaDaemonRenderNativeView view,
int renderGroupId,
int renderLod,
float defaultScale)
{
return BuildCompactedMatrices(view, renderGroupId, renderLod, defaultScale);
}
private int BuildCompactedMatrices(
AetheriaDaemonRenderNativeView view,
int renderGroupId,
int renderLod,
float defaultScale)
{
using var writtenCount = new NativeArray<int>(1, Allocator.TempJob);
var job = new AetheriaDaemonRenderGroupMatrixJob
{
PositionX = view.PositionX,
PositionY = view.PositionY,
PositionZ = view.PositionZ,
RotationRadians = view.RotationRadians,
PhysicsBodyRadius = view.PhysicsBodyRadius,
RenderScale = view.RenderScale,
RenderVisibility = view.RenderVisibility,
RenderLod = view.RenderLod,
RenderGroupId = view.RenderGroupId,
Matrices = _nativeMatrices,
WrittenCount = writtenCount,
TargetRenderGroupId = renderGroupId >= 0 ? (uint)renderGroupId : 0,
TargetRenderLod = renderLod,
HasRotation = view.HasRotation,
HasPhysicsRadius = view.HasPhysicsRadius,
HasRenderScale = view.HasRenderScale,
HasRenderVisibility = view.HasRenderVisibility,
HasRenderLod = view.HasRenderLod,
HasRenderGroupFilter = renderGroupId >= 0,
HasRenderLodFilter = renderLod >= 0,
UniformScale = defaultScale
};
job.Schedule().Complete();
return math.clamp(writtenCount[0], 0, _nativeMatrices.Length);
}
private static float ResolveDefaultScale(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId)
{
if (index != null &&
renderGroupId >= 0 &&
index.TryGetRenderGroup(renderGroupId, out var renderGroup))
{
return math.max(renderGroup.DefaultScale, 0.0001f);
}
var renderGroups = index?.RenderGroups;
if (renderGroups != null && renderGroups.Count == 1)
{
return math.max(renderGroups[0].DefaultScale, 0.0001f);
}
return 1.0f;
}
private static int ResolveRenderLod(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId)
{
if (index != null &&
renderGroupId >= 0 &&
index.TryGetRenderGroup(renderGroupId, out var renderGroup))
{
return renderGroup.Lod;
}
var renderGroups = index?.RenderGroups;
if (renderGroups != null && renderGroups.Count == 1)
{
return renderGroups[0].Lod;
}
return -1;
}
public void ApplyTo(MaterialPropertyBlock properties)
{
if (properties == null)
{
return;
}
properties.SetInt(InstanceCountPropertyId, InstanceCount);
if (HasGpuBuffers)
{
properties.SetBuffer(MatrixBufferPropertyId, _matrixBuffer);
}
}
private void OnDisable()
{
ReleaseBuffers();
InstanceCount = 0;
LastUploadedFrameId = 0;
LastUploadedGeneration = 0;
}
private void EnsureCapacity(AetheriaDaemonRenderNativeView view)
{
var count = math.max(0, view.Count);
if (_nativeMatrices.IsCreated &&
_allocatedGeneration == view.Generation &&
_allocatedCount == count)
{
return;
}
ReleaseBuffers();
_allocatedGeneration = view.Generation;
_allocatedCount = count;
if (count <= 0)
{
return;
}
_nativeMatrices = new NativeArray<float4x4>(
count,
Allocator.Persistent,
NativeArrayOptions.UninitializedMemory);
_matrixBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, MatrixStrideBytes);
_indirectArgsBuffer = new GraphicsBuffer(
GraphicsBuffer.Target.IndirectArguments,
DrawMeshInstancedIndirectArgsCount,
sizeof(uint));
}
private void UpdateIndirectArgs(Mesh mesh, int instanceCount)
{
UpdateIndirectArgs(mesh, instanceCount, 0);
}
private void UpdateIndirectArgs(Mesh mesh, int instanceCount, int subMeshIndex)
{
if (_indirectArgsBuffer == null || !_indirectArgsBuffer.IsValid())
{
return;
}
var safeSubMeshIndex = mesh != null
? math.clamp(subMeshIndex, 0, math.max(0, mesh.subMeshCount - 1))
: 0;
_indirectArgs[0] = mesh != null ? mesh.GetIndexCount(safeSubMeshIndex) : 0;
_indirectArgs[1] = (uint)math.max(0, instanceCount);
_indirectArgs[2] = mesh != null ? mesh.GetIndexStart(safeSubMeshIndex) : 0;
_indirectArgs[3] = mesh != null ? mesh.GetBaseVertex(safeSubMeshIndex) : 0;
_indirectArgs[4] = 0;
_indirectArgsBuffer.SetData(_indirectArgs);
}
private void ReleaseBuffers()
{
if (_nativeMatrices.IsCreated)
{
_nativeMatrices.Dispose();
}
_matrixBuffer?.Release();
_matrixBuffer = null;
_indirectArgsBuffer?.Release();
_indirectArgsBuffer = null;
_allocatedGeneration = long.MinValue;
_allocatedCount = 0;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9cba277ef259bee438796ab4c0f3df64

View file

@ -0,0 +1,177 @@
using System;
using GameCult.Aetheria.State.Unity;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public sealed class AetheriaDaemonRenderGroupBatch : IDisposable
{
private const int MatrixStrideBytes = 64;
private const int DrawMeshInstancedIndirectArgsCount = 5;
private readonly uint[] _indirectArgs = new uint[DrawMeshInstancedIndirectArgsCount];
private NativeArray<float4x4> _nativeMatrices;
private GraphicsBuffer _matrixBuffer;
private GraphicsBuffer _indirectArgsBuffer;
private int _capacity;
public int GroupId { get; private set; }
public int InstanceCount { get; private set; }
public long LastUploadedFrameId { get; private set; }
public long LastUploadedGeneration { get; private set; }
public GraphicsBuffer MatrixBuffer => _matrixBuffer;
public GraphicsBuffer IndirectArgsBuffer => _indirectArgsBuffer;
public bool HasGpuBuffers =>
_matrixBuffer != null &&
_matrixBuffer.IsValid() &&
_indirectArgsBuffer != null &&
_indirectArgsBuffer.IsValid() &&
InstanceCount > 0;
public bool UploadLatest(
AetheriaDaemonRenderNativeView view,
AetheriaRuntimeDaemonRenderGroupDocument renderGroup,
Mesh mesh,
float uniformScale)
{
if (!view.IsCreated || mesh == null)
{
InstanceCount = 0;
UpdateIndirectArgs(mesh, 0, renderGroup?.SubMeshIndex ?? 0);
return false;
}
var targetCapacity = renderGroup.InstanceCount >= 0
? math.min(renderGroup.InstanceCount, view.Count)
: view.Count;
EnsureCapacity(targetCapacity);
if (!_nativeMatrices.IsCreated || _nativeMatrices.Length == 0)
{
InstanceCount = 0;
UpdateIndirectArgs(mesh, 0, renderGroup.SubMeshIndex);
return false;
}
var count = view.HasRenderGroupId
? BuildRenderGroupMatrices(view, renderGroup.GroupId, renderGroup.Lod, uniformScale)
: BuildAllMatrices(view, renderGroup.Lod, uniformScale);
_matrixBuffer.SetData(_nativeMatrices, 0, 0, count);
GroupId = renderGroup.GroupId;
InstanceCount = count;
LastUploadedFrameId = view.FrameId;
LastUploadedGeneration = view.Generation;
UpdateIndirectArgs(mesh, count, renderGroup.SubMeshIndex);
return count > 0;
}
public void Dispose()
{
if (_nativeMatrices.IsCreated)
{
_nativeMatrices.Dispose();
}
_matrixBuffer?.Release();
_matrixBuffer = null;
_indirectArgsBuffer?.Release();
_indirectArgsBuffer = null;
_capacity = 0;
InstanceCount = 0;
}
private int BuildAllMatrices(AetheriaDaemonRenderNativeView view, int renderLod, float uniformScale)
{
return BuildCompactedMatrices(view, -1, renderLod, uniformScale);
}
private int BuildRenderGroupMatrices(
AetheriaDaemonRenderNativeView view,
int renderGroupId,
int renderLod,
float uniformScale)
{
return BuildCompactedMatrices(view, renderGroupId, renderLod, uniformScale);
}
private int BuildCompactedMatrices(
AetheriaDaemonRenderNativeView view,
int renderGroupId,
int renderLod,
float uniformScale)
{
using var writtenCount = new NativeArray<int>(1, Allocator.TempJob);
var job = new AetheriaDaemonRenderGroupMatrixJob
{
PositionX = view.PositionX,
PositionY = view.PositionY,
PositionZ = view.PositionZ,
RotationRadians = view.RotationRadians,
PhysicsBodyRadius = view.PhysicsBodyRadius,
RenderScale = view.RenderScale,
RenderVisibility = view.RenderVisibility,
RenderLod = view.RenderLod,
RenderGroupId = view.RenderGroupId,
Matrices = _nativeMatrices,
WrittenCount = writtenCount,
TargetRenderGroupId = renderGroupId >= 0 ? (uint)renderGroupId : 0,
TargetRenderLod = renderLod,
HasRotation = view.HasRotation,
HasPhysicsRadius = view.HasPhysicsRadius,
HasRenderScale = view.HasRenderScale,
HasRenderVisibility = view.HasRenderVisibility,
HasRenderLod = view.HasRenderLod,
HasRenderGroupFilter = renderGroupId >= 0,
HasRenderLodFilter = renderLod >= 0,
UniformScale = uniformScale
};
job.Schedule().Complete();
return math.clamp(writtenCount[0], 0, _nativeMatrices.Length);
}
private void EnsureCapacity(int capacity)
{
capacity = math.max(0, capacity);
if (_nativeMatrices.IsCreated && _capacity == capacity)
{
return;
}
Dispose();
_capacity = capacity;
if (capacity <= 0)
{
return;
}
_nativeMatrices = new NativeArray<float4x4>(
capacity,
Allocator.Persistent,
NativeArrayOptions.UninitializedMemory);
_matrixBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, MatrixStrideBytes);
_indirectArgsBuffer = new GraphicsBuffer(
GraphicsBuffer.Target.IndirectArguments,
DrawMeshInstancedIndirectArgsCount,
sizeof(uint));
}
private void UpdateIndirectArgs(Mesh mesh, int instanceCount, int subMeshIndex)
{
if (_indirectArgsBuffer == null || !_indirectArgsBuffer.IsValid())
{
return;
}
var safeSubMeshIndex = mesh != null
? math.clamp(subMeshIndex, 0, math.max(0, mesh.subMeshCount - 1))
: 0;
_indirectArgs[0] = mesh != null ? mesh.GetIndexCount(safeSubMeshIndex) : 0;
_indirectArgs[1] = (uint)math.max(0, instanceCount);
_indirectArgs[2] = mesh != null ? mesh.GetIndexStart(safeSubMeshIndex) : 0;
_indirectArgs[3] = mesh != null ? mesh.GetBaseVertex(safeSubMeshIndex) : 0;
_indirectArgs[4] = 0;
_indirectArgsBuffer.SetData(_indirectArgs);
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0038a222765ff82468c22a516cfcdbc1

View file

@ -0,0 +1,92 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
[BurstCompile]
public struct AetheriaDaemonRenderGroupMatrixJob : IJob
{
[ReadOnly]
public NativeArray<float> PositionX;
[ReadOnly]
public NativeArray<float> PositionY;
[ReadOnly]
public NativeArray<float> PositionZ;
[ReadOnly]
public NativeArray<float> RotationRadians;
[ReadOnly]
public NativeArray<float> PhysicsBodyRadius;
[ReadOnly]
public NativeArray<float> RenderScale;
[ReadOnly]
public NativeArray<byte> RenderVisibility;
[ReadOnly]
public NativeArray<int> RenderLod;
[ReadOnly]
public NativeArray<uint> RenderGroupId;
[WriteOnly]
public NativeArray<float4x4> Matrices;
public NativeArray<int> WrittenCount;
public uint TargetRenderGroupId;
public int TargetRenderLod;
public bool HasRotation;
public bool HasPhysicsRadius;
public bool HasRenderScale;
public bool HasRenderVisibility;
public bool HasRenderLod;
public bool HasRenderGroupFilter;
public bool HasRenderLodFilter;
public float UniformScale;
public void Execute()
{
var written = 0;
for (var index = 0; index < PositionX.Length; index++)
{
if (HasRenderGroupFilter && RenderGroupId[index] != TargetRenderGroupId)
{
continue;
}
if (HasRenderVisibility && RenderVisibility[index] == 0)
{
continue;
}
if (HasRenderLodFilter && (!HasRenderLod || RenderLod[index] != TargetRenderLod))
{
continue;
}
if (written >= Matrices.Length)
{
break;
}
var position = new float3(PositionX[index], PositionY[index], PositionZ[index]);
var rotation = HasRotation
? quaternion.RotateY(RotationRadians[index])
: quaternion.identity;
var scaleValue = HasRenderScale
? math.max(RenderScale[index], 0.0001f)
: HasPhysicsRadius
? math.max(PhysicsBodyRadius[index] * 2.0f, 0.0001f)
: math.max(UniformScale, 0.0001f);
Matrices[written] = float4x4.TRS(position, rotation, new float3(scaleValue));
written++;
}
WrittenCount[0] = written;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 146aa8d6d00cc1a44b44a5f83d69edea

View file

@ -0,0 +1,49 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
[BurstCompile]
public struct AetheriaDaemonRenderMatrixJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<float> PositionX;
[ReadOnly]
public NativeArray<float> PositionY;
[ReadOnly]
public NativeArray<float> PositionZ;
[ReadOnly]
public NativeArray<float> RotationRadians;
[ReadOnly]
public NativeArray<float> PhysicsBodyRadius;
[ReadOnly]
public NativeArray<float> RenderScale;
[WriteOnly]
public NativeArray<float4x4> Matrices;
public bool HasRotation;
public bool HasPhysicsRadius;
public bool HasRenderScale;
public float UniformScale;
public void Execute(int index)
{
var position = new float3(PositionX[index], PositionY[index], PositionZ[index]);
var rotation = HasRotation
? quaternion.RotateY(RotationRadians[index])
: quaternion.identity;
var scaleValue = HasRenderScale
? math.max(RenderScale[index], 0.0001f)
: HasPhysicsRadius
? math.max(PhysicsBodyRadius[index] * 2.0f, 0.0001f)
: math.max(UniformScale, 0.0001f);
Matrices[index] = float4x4.TRS(position, rotation, new float3(scaleValue));
}
}

View file

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

View file

@ -0,0 +1,248 @@
using GameCult.Aetheria.State.Unity;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
public struct AetheriaDaemonRenderNativeView
{
public long FrameId;
public long Generation;
public int Count;
public NativeArray<float> PositionX;
public NativeArray<float> PositionY;
public NativeArray<float> PositionZ;
public NativeArray<float> RotationRadians;
public NativeArray<float> VelocityX;
public NativeArray<float> VelocityY;
public NativeArray<float> VelocityZ;
public NativeArray<float> PhysicsBodyRadius;
public NativeArray<float> PhysicsBodyMass;
public NativeArray<float> PhysicsBodyInverseMass;
public NativeArray<float> RenderScale;
public NativeArray<byte> RenderVisibility;
public NativeArray<int> RenderLod;
public NativeArray<uint> RenderGroupId;
public bool IsCreated =>
Count > 0 &&
PositionX.IsCreated &&
PositionY.IsCreated &&
PositionZ.IsCreated;
public bool HasRotation => RotationRadians.IsCreated;
public bool HasVelocity => VelocityX.IsCreated && VelocityY.IsCreated && VelocityZ.IsCreated;
public bool HasPhysicsRadius => PhysicsBodyRadius.IsCreated;
public bool HasPhysicsMass => PhysicsBodyMass.IsCreated;
public bool HasPhysicsInverseMass => PhysicsBodyInverseMass.IsCreated;
public bool HasRenderScale => RenderScale.IsCreated;
public bool HasRenderVisibility => RenderVisibility.IsCreated;
public bool HasRenderLod => RenderLod.IsCreated;
public bool HasRenderGroupId => RenderGroupId.IsCreated;
public JobHandle ScheduleBuildRenderMatrices(
NativeArray<float4x4> matrices,
float uniformScale = 1.0f,
int innerLoopBatchCount = 64,
JobHandle dependsOn = default)
{
if (!IsCreated)
{
return dependsOn;
}
var count = math.min(Count, matrices.IsCreated ? matrices.Length : 0);
if (count <= 0)
{
return dependsOn;
}
var job = new AetheriaDaemonRenderMatrixJob
{
PositionX = PositionX,
PositionY = PositionY,
PositionZ = PositionZ,
RotationRadians = RotationRadians,
PhysicsBodyRadius = PhysicsBodyRadius,
RenderScale = RenderScale,
Matrices = matrices,
HasRotation = HasRotation,
HasPhysicsRadius = HasPhysicsRadius,
HasRenderScale = HasRenderScale,
UniformScale = uniformScale
};
return job.Schedule(count, math.max(1, innerLoopBatchCount), dependsOn);
}
public static bool TryCreate(
AetheriaRuntimeDaemonSoaViewIndex index,
AetheriaDaemonSoaMemoryMap map,
out AetheriaDaemonRenderNativeView view)
{
view = default;
if (index == null || map == null)
{
return false;
}
if (!map.TryCreateFirstNativeArrayOfKind(
index,
AetheriaRuntimeDaemonSoaColumnKinds.PositionX,
out view.PositionX) ||
!map.TryCreateFirstNativeArrayOfKind(
index,
AetheriaRuntimeDaemonSoaColumnKinds.PositionY,
out view.PositionY) ||
!map.TryCreateFirstNativeArrayOfKind(
index,
AetheriaRuntimeDaemonSoaColumnKinds.PositionZ,
out view.PositionZ))
{
view = default;
return false;
}
view.Count = view.PositionX.Length;
if (view.PositionY.Length != view.Count || view.PositionZ.Length != view.Count)
{
view = default;
return false;
}
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RotationRadians, view.Count, out view.RotationRadians);
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityX, view.Count, out view.VelocityX);
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityY, view.Count, out view.VelocityY);
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityZ, view.Count, out view.VelocityZ);
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyRadius, view.Count, out view.PhysicsBodyRadius);
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyMass, view.Count, out view.PhysicsBodyMass);
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyInverseMass, view.Count, out view.PhysicsBodyInverseMass);
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderScale, view.Count, out view.RenderScale);
TryAssignOptionalByteColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderVisibility, view.Count, out view.RenderVisibility);
TryAssignOptionalIntColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderLod, view.Count, out view.RenderLod);
TryAssignOptionalUIntColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderGroupId, view.Count, out view.RenderGroupId);
if ((view.VelocityX.IsCreated || view.VelocityY.IsCreated || view.VelocityZ.IsCreated) &&
!view.HasVelocity)
{
view.VelocityX = default;
view.VelocityY = default;
view.VelocityZ = default;
}
view.FrameId = index.View.FrameId;
view.Generation = index.View.Generation;
return true;
}
private static bool TryAssignOptionalColumn(
AetheriaRuntimeDaemonSoaViewIndex index,
AetheriaDaemonSoaMemoryMap map,
string kind,
int expectedCount,
out NativeArray<float> array)
{
array = default;
if (!index.TryGetFirstColumnOfKind(kind, out _))
{
return false;
}
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
{
array = default;
return false;
}
if (array.Length == expectedCount)
{
return true;
}
array = default;
return false;
}
private static bool TryAssignOptionalUIntColumn(
AetheriaRuntimeDaemonSoaViewIndex index,
AetheriaDaemonSoaMemoryMap map,
string kind,
int expectedCount,
out NativeArray<uint> array)
{
array = default;
if (!index.TryGetFirstColumnOfKind(kind, out _))
{
return false;
}
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
{
array = default;
return false;
}
if (array.Length == expectedCount)
{
return true;
}
array = default;
return false;
}
private static bool TryAssignOptionalIntColumn(
AetheriaRuntimeDaemonSoaViewIndex index,
AetheriaDaemonSoaMemoryMap map,
string kind,
int expectedCount,
out NativeArray<int> array)
{
array = default;
if (!index.TryGetFirstColumnOfKind(kind, out _))
{
return false;
}
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
{
array = default;
return false;
}
if (array.Length == expectedCount)
{
return true;
}
array = default;
return false;
}
private static bool TryAssignOptionalByteColumn(
AetheriaRuntimeDaemonSoaViewIndex index,
AetheriaDaemonSoaMemoryMap map,
string kind,
int expectedCount,
out NativeArray<byte> array)
{
array = default;
if (!index.TryGetFirstColumnOfKind(kind, out _))
{
return false;
}
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
{
array = default;
return false;
}
if (array.Length == expectedCount)
{
return true;
}
array = default;
return false;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 48740dbd37e79954ca00a3f6bd7ccef1

View file

@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.IO.MemoryMappedFiles;
using GameCult.Aetheria.State.Unity;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
public unsafe sealed class AetheriaDaemonSoaMemoryMap : IDisposable
{
private readonly Dictionary<string, MappedBuffer> _buffersById =
new Dictionary<string, MappedBuffer>(StringComparer.Ordinal);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
private readonly List<AtomicSafetyHandle> _safetyHandles = new List<AtomicSafetyHandle>();
#endif
private bool _disposed;
private AetheriaDaemonSoaMemoryMap()
{
}
public static bool TryOpen(
AetheriaRuntimeDaemonSoaViewIndex index,
out AetheriaDaemonSoaMemoryMap map,
out string error)
{
map = null;
error = "";
if (index == null)
{
error = "No daemon SoA view index was provided.";
return false;
}
if (!index.IsValid)
{
error = index.ValidationErrors.Count > 0
? index.ValidationErrors[0]
: "Daemon SoA view index is invalid.";
return false;
}
var opened = new AetheriaDaemonSoaMemoryMap();
try
{
foreach (var buffer in index.View.Buffers)
{
if (!IsMappableReadOnlyBuffer(buffer))
{
continue;
}
opened.OpenBuffer(buffer);
}
map = opened;
return true;
}
catch (Exception exception)
{
opened.Dispose();
error = exception.Message;
return false;
}
}
public bool TryCreateNativeArray<T>(
AetheriaRuntimeDaemonSoaColumnBinding binding,
out NativeArray<T> array)
where T : struct
{
ThrowIfDisposed();
array = default;
if (binding == null ||
!binding.DirectMemoryCompatible ||
!_buffersById.TryGetValue(binding.Buffer.BufferId, out var buffer))
{
return false;
}
var byteOffset = checked((ulong)binding.AbsoluteByteOffset);
var byteLength = checked((ulong)Math.Max(0, binding.ByteLength));
if (byteOffset + byteLength > buffer.ByteLength)
{
return false;
}
var pointer = buffer.Pointer + byteOffset;
array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(
pointer,
binding.Column.ElementCount,
Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
var safetyHandle = AtomicSafetyHandle.Create();
_safetyHandles.Add(safetyHandle);
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, safetyHandle);
#endif
return true;
}
public bool TryCreateFirstNativeArrayOfKind<T>(
AetheriaRuntimeDaemonSoaViewIndex index,
string kind,
out NativeArray<T> array)
where T : struct
{
ThrowIfDisposed();
array = default;
if (index == null || !index.TryGetFirstColumnOfKind(kind, out var binding))
{
return false;
}
return TryCreateNativeArray(binding, out array);
}
public void Dispose()
{
if (_disposed)
{
return;
}
#if ENABLE_UNITY_COLLECTIONS_CHECKS
for (var i = 0; i < _safetyHandles.Count; i++)
{
AtomicSafetyHandle.Release(_safetyHandles[i]);
}
_safetyHandles.Clear();
#endif
foreach (var buffer in _buffersById.Values)
{
buffer.Dispose();
}
_buffersById.Clear();
_disposed = true;
}
private void OpenBuffer(AetheriaRuntimeDaemonSoaBufferDocument buffer)
{
if (string.IsNullOrWhiteSpace(buffer.BufferId) ||
_buffersById.ContainsKey(buffer.BufferId))
{
return;
}
var byteLength = checked((ulong)(buffer.ByteOffset + buffer.ByteLength));
var memoryMappedFile = MemoryMappedFile.OpenExisting(
buffer.Location,
MemoryMappedFileRights.Read);
var accessor = memoryMappedFile.CreateViewAccessor(0, (long)byteLength, MemoryMappedFileAccess.Read);
byte* pointer = null;
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
_buffersById.Add(
buffer.BufferId,
new MappedBuffer(memoryMappedFile, accessor, pointer, byteLength));
}
private static bool IsMappableReadOnlyBuffer(AetheriaRuntimeDaemonSoaBufferDocument buffer)
{
return buffer != null &&
!buffer.ObserverWritable &&
!string.IsNullOrWhiteSpace(buffer.Location) &&
string.Equals(buffer.Backend, AetheriaRuntimeDaemonSoaBackends.MemoryMappedFile, StringComparison.Ordinal);
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(AetheriaDaemonSoaMemoryMap));
}
}
private sealed class MappedBuffer : IDisposable
{
private readonly MemoryMappedFile _memoryMappedFile;
private readonly MemoryMappedViewAccessor _accessor;
private bool _disposed;
public MappedBuffer(
MemoryMappedFile memoryMappedFile,
MemoryMappedViewAccessor accessor,
byte* pointer,
ulong byteLength)
{
_memoryMappedFile = memoryMappedFile;
_accessor = accessor;
Pointer = pointer;
ByteLength = byteLength;
}
public byte* Pointer { get; }
public ulong ByteLength { get; }
public void Dispose()
{
if (_disposed)
{
return;
}
_accessor.SafeMemoryMappedViewHandle.ReleasePointer();
_accessor.Dispose();
_memoryMappedFile.Dispose();
_disposed = true;
}
}
}

View file

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

View file

@ -1,15 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class ArticulationPoint : MonoBehaviour
{
public Transform Target;
public int Group;
public float YawMin;
public float YawMax;
@ -26,26 +22,26 @@ public class ArticulationPoint : MonoBehaviour
if (Target)
{
var targetLocal = transform.InverseTransformPoint(Target.position);
var yaw = Vector2.SignedAngle(new Vector2(0, 1), new Vector2(targetLocal.x, targetLocal.z));
var pitch = Vector2.SignedAngle(new Vector2(1, 0), new Vector2(targetLocal.z, targetLocal.y));
var targetYaw = clamp(_yaw - yaw, YawMin, YawMax);
var targetPitch = clamp(_pitch - pitch, PitchMin, PitchMax);
var targetYaw = Mathf.Clamp(_yaw - yaw, YawMin, YawMax);
var targetPitch = Mathf.Clamp(_pitch - pitch, PitchMin, PitchMax);
if (abs(targetYaw - _yaw) < Speed * Time.deltaTime)
if (Mathf.Abs(targetYaw - _yaw) < Speed * Time.deltaTime)
_yaw = targetYaw;
else _yaw = _yaw + sign(targetYaw - _yaw) * Speed * Time.deltaTime;
else _yaw = _yaw + Mathf.Sign(targetYaw - _yaw) * Speed * Time.deltaTime;
if(_yaw < -360)
if (_yaw < -360)
_yaw += 360;
if(_yaw > 360)
if (_yaw > 360)
_yaw -= 360;
if (abs(targetPitch - _pitch) < Speed * Time.deltaTime)
if (Mathf.Abs(targetPitch - _pitch) < Speed * Time.deltaTime)
_pitch = targetPitch;
else _pitch = _pitch + sign(targetPitch - _pitch) * Speed * Time.deltaTime;
else _pitch = _pitch + Mathf.Sign(targetPitch - _pitch) * Speed * Time.deltaTime;
transform.localRotation = Quaternion.Euler(_pitch, _yaw, 0);
}
}

View file

@ -5,17 +5,13 @@ using System.Linq;
using GameCult.Aetheria.State.Unity;
using UniRx;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using cfloat3 = CultMath.float3;
public class EntityInstance : MonoBehaviour
{
public MeshRenderer MapIcon;
public Transform InfluencePrefab;
public Transform PingPrefab;
public Material InvisibleMaterial;
public static Transform EffectManagerParent;
public ShieldManager Shield;
public HullCollider[] HullColliders;
@ -25,7 +21,6 @@ public class EntityInstance : MonoBehaviour
public WeaponHardpoint[] WeaponHardpoints;
public ArticulationPoint[] ArticulationPoints;
public GameObject DestroyEffect;
public event Action OnFadedOut;
public event Action OnFadedIn;
@ -40,41 +35,21 @@ public class EntityInstance : MonoBehaviour
private bool _fadedElementsVisible = false;
private bool _unfadedElementsVisible = false;
private float _fadeTime;
private bool _destroyed;
private (Transform transform, MeshRenderer meshRenderer) _currentPing;
private float _pingBrightness;
private Sensor _sensor;
private Transform _influenceInstance;
//private List<(GameObject source, EquippedItem item)> _audioSources = new List<(GameObject source, EquippedItem item)>();
// private (Reactor reactor, GameObject sfxSource) _reactor;
// private Dictionary<Radiator, GameObject> _radiatorSfx = new Dictionary<Radiator, GameObject>();
private static Dictionary<string, InstantWeaponEffectManager> _instantWeaponManagers = new Dictionary<string, InstantWeaponEffectManager>();
private static Dictionary<string, ConstantWeaponEffectManager> _constantWeaponManagers = new Dictionary<string, ConstantWeaponEffectManager>();
public static void ClearWeaponManagers()
{
_instantWeaponManagers.Clear();
_constantWeaponManagers.Clear();
}
private static AetheriaRuntimeCatalogItem FindTypedHull(ItemInstance hull)
{
return ActionGameManager.RuntimeCatalog?.FindItem(hull?.ItemKey ?? "");
}
private static Shape ToShape(int width, int height, IReadOnlyList<AetheriaRuntimeShapeCell> cells)
{
var shape = new Shape(width, height);
foreach (var cell in cells)
{
if (cell.X >= 0 && cell.Y >= 0 && cell.X < width && cell.Y < height)
shape[int2(cell.X, cell.Y)] = true;
}
private static Vector2 ToVector2(CultMath.float2 value) => new Vector2(value.x, value.y);
private static Vector2 ToVector2(Unity.Mathematics.float2 value) => new Vector2(value.x, value.y);
return shape;
}
private static cfloat3 ToCult(Vector3 value) => new cfloat3(value.x, value.y, value.z);
public CompassIcon CompassIcon { get; set; }
public Dictionary<HardpointData, Transform[]> Barrels { get; private set; }
@ -186,81 +161,10 @@ public class EntityInstance : MonoBehaviour
return;
}
var hullShape = ToShape(typedHull.ShapeWidth, typedHull.ShapeHeight, typedHull.ShapeCells);
var typedHardpoints = typedHull.Hardpoints.ToArray();
if(Shield)
Shield.Entity = entity;
foreach (var hullCollider in HullColliders) hullCollider.Entity = entity;
foreach (var item in entity.Equipment)
{
foreach (var behavior in item.Behaviors)
{
if (behavior is Sensor sensor)
{
_sensor = sensor;
sensor.OnPingStart += () =>
{
var pingInstance = Instantiate(PingPrefab);
var pingMesh = pingInstance.GetComponent<MeshRenderer>();
pingInstance.position = entity.Position;
_pingBrightness = pingMesh.material.GetFloat("_Depth");
_currentPing = (pingInstance, pingMesh);
};
sensor.OnPingEnd += OnSensorPingEnd;
}
if (behavior is InstantWeapon instantWeapon)
{
var effectPrefab = instantWeapon.EffectPrefab;
if (!_instantWeaponManagers.ContainsKey(effectPrefab))
{
var managerPrefab = UnityHelpers.LoadAsset<InstantWeaponEffectManager>(effectPrefab);
if(managerPrefab)
{
_instantWeaponManagers.Add(effectPrefab, Instantiate(managerPrefab, EffectManagerParent));
}
else Debug.LogError($"No InstantWeaponEffectManager prefab found at path {effectPrefab}");
}
instantWeapon.OnFire += () =>
_instantWeaponManagers[effectPrefab].Fire(instantWeapon, item, this, entity.Target.Value != null && ZoneRenderer.EntityInstances.ContainsKey(entity.Target.Value) ? ZoneRenderer.EntityInstances[entity.Target.Value] : null);
if (behavior is ChargedWeapon chargedWeapon)
{
var chargeManager = _instantWeaponManagers[effectPrefab].GetComponent<ChargeEffectManager>();
if (chargeManager)
{
chargedWeapon.OnStartCharging += () => chargeManager.StartCharging(chargedWeapon, item, this);
chargedWeapon.OnStopCharging += () => chargeManager.StopCharging(chargedWeapon);
chargedWeapon.OnCharged += () => chargeManager.Charged(chargedWeapon);
chargedWeapon.OnFailed += () => chargeManager.Failed(chargedWeapon);
}
}
}
if (behavior is ConstantWeapon constantWeapon)
{
var effectPrefab = constantWeapon.EffectPrefab;
if (!_constantWeaponManagers.ContainsKey(effectPrefab))
{
var managerPrefab = UnityHelpers.LoadAsset<ConstantWeaponEffectManager>(effectPrefab);
if(managerPrefab)
{
_constantWeaponManagers.Add(effectPrefab, Instantiate(managerPrefab, EffectManagerParent));
}
else Debug.LogError($"No ConstantWeaponEffectManager prefab found at path {effectPrefab}");
}
constantWeapon.OnStartFiring += () =>
_constantWeaponManagers[effectPrefab].StartFiring(constantWeapon, item, this, entity.Target.Value != null ? ZoneRenderer.EntityInstances[entity.Target.Value] : null);
constantWeapon.OnStopFiring += () =>
_constantWeaponManagers[effectPrefab].StopFiring(item);
}
}
}
RadiatorMeshes = new Dictionary<Radiator, MeshRenderer>();
Barrels = new Dictionary<HardpointData, Transform[]>();
BarrelIndices = new Dictionary<HardpointData, int>();
@ -289,128 +193,6 @@ public class EntityInstance : MonoBehaviour
}
}
void DamageSchematic(float damage, Shape hitShape)
{
foreach (var v in hitShape.Coordinates)
hitShape[v] = hitShape[v] && hullShape[v];
float hullDamage = 0;
var damagePerCell = damage / hitShape.Coordinates.Length;
foreach (var v in hitShape.Coordinates)
{
var d = damagePerCell;
// Subtract surface damage from armor, passing on the remainder to the item and then to the hull
var prev = entity.Armor[v.x, v.y];
entity.Armor[v.x, v.y] = max(prev - d, 0);
entity.ArmorDamage.OnNext((v, d));
d = max(d - prev, 0);
if (d > 0.1f)
{
var item = entity.GearOccupancy[v.x, v.y];
if (item != null)
{
prev = item.EquippableItem.Durability;
item.EquippableItem.Durability = max(prev - d, 0);
entity.ItemDamage.OnNext((item, d));
d = max(d - prev, 0);
}
}
hullDamage += d;
}
if(hullDamage > .1f)
{
entity.Hull.Durability -= hullDamage;
entity.HullDamage.OnNext(hullDamage);
}
}
foreach (var collider in HullColliders)
{
collider.Splash.Subscribe(splash =>
{
var hitShape = new Shape(hullShape.Width, hullShape.Height);
foreach (var v in hullShape.Coordinates)
{
var localHitDirection = transform.InverseTransformDirection(splash.Direction);
var direction = normalize(float2(localHitDirection.x, localHitDirection.z));
var cellDot = dot(normalize(v - hullShape.CenterOfMass), direction);
if (cellDot < 0) hitShape[v] = true;
}
DamageSchematic(splash.Damage, hitShape);
});
collider.Hit.Subscribe(hit =>
{
Entity.IncomingHit.OnNext(hit.Source);
var hardpointIndex = (int) hit.TexCoord.x - 1;
var hitShape = new Shape(hullShape.Width, hullShape.Height);
// U coordinate between 0-1 indicates a hit that didn't land directly on a hardpoint
// Find the 2D position of the hit scaled to the schematic
float2 hitPos = float2.zero;
if (hardpointIndex < 0 || hardpointIndex >= typedHardpoints.Length)
{
hitPos = float2(hit.TexCoord.x * hullShape.Width, hit.TexCoord.y * hullShape.Height);
// Search all schematic border cells for the cell which is closest to the hit position
var hitCell = int2(-1);
var distance = float.MaxValue;
foreach (var v in hullShape.Coordinates)
{
var cellDist = lengthsq(hitPos - v);
if (cellDist < distance)
{
distance = cellDist;
hitCell = v;
hitPos = v + float2(.5f);
}
}
hitShape[hitCell] = true;
}
else
{
// Collider UV coordinates starting with 1 correspond to hardpoint index
var hardpoint = typedHardpoints[hardpointIndex];
// Obtain the hull coordinates of all cells occupied by the hardpoint
var hardpointShape = ToShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells);
var hardpointCells = hullShape.Inset(hardpointShape, int2(hardpoint.PositionX, hardpoint.PositionY));
hitPos = hardpointCells.CenterOfMass;
foreach (var v in hardpointCells.Coordinates)
hitShape[v] = true;
}
for (int i = 0; i < Mathf.RoundToInt(hit.Spread); i++)
{
hitShape = hitShape.Expand();
}
if (hit.Penetration > .5f)
{
// Find the local 2D vector corresponding to the direction of the incoming hit
var localHitDirection = transform.InverseTransformDirection(hit.Direction);
var penetrationVector = normalize(float2(localHitDirection.x, localHitDirection.z));
// TODO: Bresenham's line algorithm
// March a ray through the ship from the hit position
var penetrationPoint = hitPos;
var penetrationDistance = 0f;
while (penetrationDistance < hit.Penetration && hullShape[int2(penetrationPoint)])
{
penetrationDistance += .5f;
hitShape[int2(penetrationPoint)] = true;
penetrationPoint += penetrationVector * .5f;
}
}
DamageSchematic(hit.Damage, hitShape);
});
}
LookAtPoint = new GameObject($"{entity.Name} Look Point").transform;
foreach (var articulationPoint in ArticulationPoints)
@ -418,23 +200,6 @@ public class EntityInstance : MonoBehaviour
articulationPoint.Target = LookAtPoint;
}
_subscriptions.Add(Entity.HullDamage.Subscribe(_ =>
{
if (!_destroyed && Entity.Hull.Durability < .01f)
{
if (!this) return;
if (ActionGameManager.Instance?.CommitEntityDestroyed(entity, ZoneRenderer) != true)
return;
_destroyed = true;
if (DestroyEffect != null)
{
var t = Instantiate(DestroyEffect).transform;
t.position = transform.position;
}
}
}));
if (entity is OrbitalEntity orbital && orbital.IsSecureArea)
{
_influenceInstance = Instantiate(InfluencePrefab);
@ -442,11 +207,6 @@ public class EntityInstance : MonoBehaviour
}
}
private void OnSensorPingEnd()
{
Destroy(_currentPing.transform.gameObject);
}
public Transform GetBarrel(HardpointData hardpoint)
{
if (Barrels.ContainsKey(hardpoint))
@ -461,11 +221,6 @@ public class EntityInstance : MonoBehaviour
public virtual void Update()
{
if (_currentPing.transform)
{
_currentPing.transform.localScale = _sensor.PingRadius * Vector3.one;
_currentPing.meshRenderer.material.SetFloat("_Depth", _pingBrightness * _sensor.PingBrightness);
}
if (_fading)
{
if (_fadingIn)
@ -510,20 +265,23 @@ public class EntityInstance : MonoBehaviour
foreach (var x in Barrels)
{
Entity.HardpointTransforms[x.Key] = (x.Value[0].position, x.Value[0].forward);
Entity.HardpointTransforms[x.Key] = (
ToCult(x.Value[0].position),
ToCult(x.Value[0].forward));
}
LookAtPoint.position = transform.position + (Vector3) Entity.LookDirection *
(Entity.Target.Value != null ? max(Entity.TargetRange,Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance) : 10000);
LocalSpace.localPosition = transform.position = Entity.Position;
var entityPosition = (Vector3)AetheriaMath.ToUnity(Entity.CultPosition);
var entityLookDirection = (Vector3)AetheriaMath.ToUnity(Entity.CultLookDirection);
LookAtPoint.position = transform.position + entityLookDirection *
(Entity.Target.Value != null ? Mathf.Max(Entity.TargetRange, Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance) : 10000);
LocalSpace.localPosition = transform.position = entityPosition;
if (_influenceInstance)
_influenceInstance.position = new Vector3(Entity.Position.x, 0, Entity.Position.z);
_influenceInstance.position = new Vector3(entityPosition.x, 0, entityPosition.z);
}
public virtual void OnDestroy()
{
if(_sensor!=null)
_sensor.OnPingEnd -= OnSensorPingEnd;
if (_influenceInstance) Destroy(_influenceInstance.gameObject);
Destroy(LocalSpace.gameObject);
foreach(var x in _subscriptions)

View file

@ -1,15 +1,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class FieldDriver : MonoBehaviour
{
public Camera Camera;
public float2 Push;
public Vector2 Push;
public float FrontTwist;
public float RearTwist;
public float TestMagnitude;
@ -28,19 +24,6 @@ public class FieldDriver : MonoBehaviour
public float MeleeAngle;
public float MeleeAngleExponent;
public float TendrilBaseRadius = 3;
public float TendrilTipRadius = .1f;
public float TendrilExtensionExponent = .75f;
public float TendrilExtendBaseAnimationExponent = .5f;
public float TendrilBaseDamping = 2;
public float TendrilTipRadiusAnimationExponent = 2f;
public float TendrilFadeExponent = 2;
// public float GrabScaleAnimationExponent = .5f;
public float GrabExtendTime;
public float GrabEnvelopTime;
public float GrabPullTime;
public RectTransform.Axis RefractionAxis = RectTransform.Axis.Horizontal;
private Material _field;
@ -49,57 +32,36 @@ public class FieldDriver : MonoBehaviour
private float _waveOffset;
private float _meleeTime;
private bool _meleeActive;
private MeshCollider _collider;
private Transform _grabObject;
private Vector3 _grabObjectStartPos;
private Vector3 _grabObjectStartTendrilBasePos;
private Vector3 _grabObjectEndPos;
private Vector3 _grabObjectVelocity;
private float _grabObjectScale;
private float _grabTime;
private GrabPhase _grabPhase;
private Vector3 _tendrilBasePos;
private Vector3 _tendrilBendTarget;
private Vector3 _tendrilTargetPos;
private float _fadePoint;
private enum GrabPhase
{
Extend,
Envelop,
Pull
}
private struct FieldHit
{
public float3 Position;
public float3 Direction;
public Vector3 Position;
public Vector3 Direction;
public float Magnitude;
public float Time;
}
void Start()
{
_collider = GetComponent<MeshCollider>();
_field = GetComponent<MeshRenderer>().material;
var clickableCollider = GetComponent<ClickableCollider>();
if(clickableCollider!=null)
{
clickableCollider.OnClick += (_, _, ray, hit) =>
{
AddHit(hit.point, ray.direction, TestMagnitude);
AddHit(hit.Point, ray.direction, TestMagnitude);
};
}
_hitBuffer = new ComputeBuffer(MaxHits, 32);
}
public void AddHit(float3 position, float3 direction, float magnitude)
public void AddHit(Vector3 position, Vector3 direction, float magnitude)
{
if (_hits.Count >= MaxHits) return;
var hit = new FieldHit
{
Position = Vector3.Scale(normalize(transform.InverseTransformPoint(position)), transform.localScale),
Direction = normalize(transform.rotation * direction),
Position = Vector3.Scale(transform.InverseTransformPoint(position).normalized, transform.localScale),
Direction = (transform.rotation * direction).normalized,
Magnitude = magnitude,
Time = 0
};
@ -115,31 +77,21 @@ public class FieldDriver : MonoBehaviour
_meleeTime = 0;
}
public bool CanGrab => _grabObject == null;
public void GrabObject(Transform t, Vector3 v)
{
_grabPhase = GrabPhase.Extend;
_grabObject = t;
_grabObjectStartPos = t.position;
_grabObjectVelocity = v;
_grabObjectScale = t.localScale.x;
_tendrilBasePos = _grabObjectStartTendrilBasePos = _collider.ClosestPoint(_grabObjectStartPos);
_fadePoint = .999f;
}
void Update()
{
var pushMag = min(length(Push), 1);
var pushMag = Mathf.Min(Push.magnitude, 1);
_waveOffset = frac(_waveOffset + Time.deltaTime * FlowSpeed * pow(max(pushMag, max(abs(FrontTwist), abs(RearTwist))), FlowSpeedThrottleExponent));
_waveOffset = Mathf.Repeat(
_waveOffset + Time.deltaTime * FlowSpeed *
Mathf.Pow(Mathf.Max(pushMag, Mathf.Max(Mathf.Abs(FrontTwist), Mathf.Abs(RearTwist))), FlowSpeedThrottleExponent),
1);
var refractionRotation = Matrix4x4.Rotate(Camera.transform.rotation).inverse;
if (RefractionAxis == RectTransform.Axis.Vertical) refractionRotation =
Matrix4x4.Rotate(Quaternion.Euler(90, 0, 0)) * refractionRotation;
_field.SetFloat("_WaveOffset", _waveOffset * PI * 2);
_field.SetFloat("_WaveOffset", _waveOffset * Mathf.PI * 2);
_field.SetFloat("_Push", pushMag);
_field.SetVector("_PushDirection", new Vector4(-Push.x, 0, -Push.y));
_field.SetFloat("_TwistFront", FrontTwist);
@ -168,8 +120,8 @@ public class FieldDriver : MonoBehaviour
_meleeTime += Time.deltaTime / MeleeDuration;
if (_meleeTime < 1)
{
_field.SetVector("_MeleeDirection", Quaternion.AngleAxis(sign(_meleeTime*2-1) * MeleeAngle * pow(abs(_meleeTime * 2 - 1), MeleeAngleExponent), Vector3.up) * -Vector3.forward);
_field.SetFloat("_MeleeDisplacement", pow(1 - 2 * abs(_meleeTime - .5f), MeleeRangeExponent) * MeleeRange);
_field.SetVector("_MeleeDirection", Quaternion.AngleAxis(Mathf.Sign(_meleeTime * 2 - 1) * MeleeAngle * Mathf.Pow(Mathf.Abs(_meleeTime * 2 - 1), MeleeAngleExponent), Vector3.up) * -Vector3.forward);
_field.SetFloat("_MeleeDisplacement", Mathf.Pow(1 - 2 * Mathf.Abs(_meleeTime - .5f), MeleeRangeExponent) * MeleeRange);
_field.SetFloat("_MeleeShape", MeleeShaping);
_field.SetFloat("_MeleeFlattening", MeleeFlatness);
}
@ -179,73 +131,6 @@ public class FieldDriver : MonoBehaviour
_field.SetFloat("_MeleeDisplacement", 0);
}
}
if(_grabObject != null)
{
_grabTime += Time.deltaTime / _grabPhase switch
{
GrabPhase.Extend => GrabExtendTime,
GrabPhase.Envelop => GrabEnvelopTime,
GrabPhase.Pull => GrabPullTime,
_ => throw new ArgumentOutOfRangeException()
};
if (_grabTime > 1)
{
_grabTime = 0;
if (_grabPhase == GrabPhase.Envelop)
{
_grabObjectEndPos = _grabObject.position;
}
if (_grabPhase == GrabPhase.Pull)
{
Destroy(_grabObject.gameObject);
_grabObject = null;
_field.SetFloat("_TendrilInfluence", 0);
}
else _grabPhase++;
}
if (_grabObject != null)
{
_tendrilBasePos = AetheriaMath.Damp(_tendrilBasePos, _grabObject.position, TendrilBaseDamping, Time.deltaTime);
switch (_grabPhase)
{
case GrabPhase.Extend:
_tendrilBendTarget = _grabObjectStartPos;
_grabObject.position += _grabObjectVelocity * Time.deltaTime;
_tendrilTargetPos = lerp(
lerp(_grabObjectStartTendrilBasePos, _grabObjectStartPos, _grabTime),
lerp(_grabObjectStartPos, _grabObject.position, _grabTime),
pow(_grabTime, TendrilExtensionExponent));
_field.SetFloat("_TendrilInfluence", pow(_grabTime, TendrilExtendBaseAnimationExponent));
_field.SetFloat("_TendrilSize", lerp(TendrilBaseRadius/2,TendrilBaseRadius,pow(_grabTime, TendrilExtendBaseAnimationExponent)));
_field.SetFloat("_TendrilRadius", _grabObjectScale * pow(_grabTime, TendrilTipRadiusAnimationExponent) * TendrilTipRadius);
break;
case GrabPhase.Envelop:
_tendrilBendTarget = _grabObjectStartPos;
_tendrilTargetPos = _grabObject.position += _grabObjectVelocity * Time.deltaTime * (1-_grabTime);
_field.SetFloat("_TendrilInfluence", 1);
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
_field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius);
break;
case GrabPhase.Pull:
_tendrilTargetPos = _grabObject.position = lerp(_grabObjectEndPos, transform.position, _grabTime*_grabTime);
_tendrilBendTarget = lerp(_grabObjectStartPos, _grabObjectEndPos, _grabTime*_grabTime);
if (_fadePoint > .99 && transform.InverseTransformPoint(_grabObject.position).sqrMagnitude < 1)
_fadePoint = _grabTime;
_field.SetFloat("_TendrilInfluence", pow(smoothstep(1, _fadePoint, _grabTime), TendrilFadeExponent));
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
_field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius);
break;
default:
throw new ArgumentOutOfRangeException();
}
_field.SetVector("_TendrilBase", Vector3.Scale(transform.InverseTransformPoint(_tendrilBasePos).normalized, transform.localScale));
_field.SetVector("_TendrilBend", Vector3.Scale(transform.InverseTransformPoint(_tendrilBendTarget), transform.localScale));
_field.SetVector("_TendrilTarget", Vector3.Scale(transform.InverseTransformPoint(_tendrilTargetPos), transform.localScale));
}
}
}
private float almostIdentity( float x )
@ -258,20 +143,10 @@ public class FieldDriver : MonoBehaviour
return t * t * (3.0f - 2.0f * t);
}
private const int GIZMO_STEPS = 16;
private void OnDrawGizmosSelected()
private float SmoothStep(float edge0, float edge1, float x)
{
if (_grabObject != null)
{
Vector3 previous = _tendrilBasePos;
for (int i = 1; i <= GIZMO_STEPS; i++)
{
var l = (float)i / GIZMO_STEPS;
var next = AetheriaMath.GetQuadraticSplinePosition(_tendrilBasePos, _tendrilBendTarget, _tendrilTargetPos, l);
Gizmos.DrawLine(previous, next);
previous = next;
}
}
var t = Mathf.Clamp01((x - edge0) / (edge1 - edge0));
return t * t * (3.0f - 2.0f * t);
}
private void OnDestroy()

View file

@ -15,7 +15,6 @@ public class GameSettings : ScriptableObject
public string DefaultBossSoundbank = "Boss_Metal";
public string AmbienceSoundBank = "Ambience";
public string StartingHullName = "Longinus";
public float PickupLifetime = 30;
public float LootDropProbability = .25f;
public float LootDropVelocity = 25;
public float HeatstrokePhasingFloor = 0;
@ -86,4 +85,4 @@ public class BodySettingsCollection
{
public float MinimumMass;
public CelestialBodySettings[] BodySettings;
}
}

View file

@ -1,7 +1,5 @@
using System;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Mathematics.math;
using Random = UnityEngine.Random;
public class GridObject : MonoBehaviour
@ -13,7 +11,6 @@ public class GridObject : MonoBehaviour
public float Drag = .1f;
public float LaunchDrag;
private float2 _selfVelocity;
private float _timeOffset;
public Zone Zone { get; set; }
@ -27,24 +24,6 @@ public class GridObject : MonoBehaviour
void Update()
{
var t = transform;
t.localRotation = Quaternion.Euler(sin(Time.time - _timeOffset * RotationSpeed) * 90, 0, cos(Time.time - _timeOffset * RotationSpeed) * 90);
if (Zone == null) return;
var position = t.position;
var gridHeight = Zone.GetHeight(position.Flatland()) + GridOffset;
Velocity += Vector3.up * (sign(gridHeight - position.y) * GridAttraction * Time.deltaTime);
Velocity *= max(0, 1 - LaunchDrag * Time.deltaTime);
var normal = Zone.GetNormal(position.Flatland());
var force = new float2(normal.x, normal.z);
var forceMagnitude = lengthsq(force);
if (forceMagnitude > .001f)
{
var fa = 1 / (1 - forceMagnitude) - 1;
_selfVelocity += normalize(force) * Zone.Settings.GravityStrength * fa * Gravity;
}
_selfVelocity *= max(0, 1 - Drag * Time.deltaTime);
t.position = position + (Velocity + new Vector3(_selfVelocity.x, 0, _selfVelocity.y)) * Time.deltaTime;
t.localRotation = Quaternion.Euler(Mathf.Sin(Time.time - _timeOffset * RotationSpeed) * 90, 0, Mathf.Cos(Time.time - _timeOffset * RotationSpeed) * 90);
}
}
}

View file

@ -1,65 +1,65 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UniRx;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using UnityEngine;
using float2 = Unity.Mathematics.float2;
public class HullCollider : MonoBehaviour
{
public Subject<HullHitEventArgs> Hit = new Subject<HullHitEventArgs>();
public Subject<HullSplashEventArgs> Splash = new Subject<HullSplashEventArgs>();
private Renderer[] _renderers;
public Entity Entity { get; set; }
public void SendHit(float damage, float penetration, float spread, DamageType damageType, Entity source, Vector2 texCoord, Vector3 direction)
public Bounds YmirBounds => CalculateBounds(transform.position);
public float YmirPlanarRadius => Mathf.Max(YmirBounds.extents.x, YmirBounds.extents.z, 0.001f);
public void RefreshYmirBoundsSource()
{
Hit.OnNext(new HullHitEventArgs
{
Damage = damage,
Penetration = penetration,
Spread = spread,
DamageType = damageType,
Source = source,
TexCoord = texCoord,
Direction = direction
});
}
public void SendSplash(float damage, DamageType damageType, Entity source, Vector3 direction)
{
Splash.OnNext(new HullSplashEventArgs
{
Damage = damage,
DamageType = damageType,
Source = source,
Direction = direction
});
_renderers = GetComponentsInChildren<Renderer>();
}
private void OnCollisionEnter(Collision other)
private void Awake()
{
Entity.Velocity += ((float3) other.impulse).xz / Time.deltaTime;
RefreshYmirBoundsSource();
}
public class HullHitEventArgs
private void OnEnable()
{
public float Damage;
public float Penetration;
public float Spread;
public DamageType DamageType;
public Entity Source;
public float2 TexCoord;
public float3 Direction;
if (_renderers == null || _renderers.Length == 0)
RefreshYmirBoundsSource();
}
public class HullSplashEventArgs
private Bounds CalculateBounds(Vector3 fallbackCenter)
{
public float Damage;
public DamageType DamageType;
public Entity Source;
public float3 Direction;
if (_renderers == null)
RefreshYmirBoundsSource();
if (_renderers == null || _renderers.Length == 0)
return new Bounds(fallbackCenter, Vector3.one);
var firstRenderer = FirstLiveRenderer();
if (firstRenderer == null)
return new Bounds(fallbackCenter, Vector3.one);
var bounds = firstRenderer.bounds;
for (var i = 0; i < _renderers.Length; i++)
{
var renderer = _renderers[i];
if (renderer == null || renderer == firstRenderer)
continue;
bounds.Encapsulate(renderer.bounds);
}
return bounds;
}
}
private Renderer FirstLiveRenderer()
{
for (var i = 0; i < _renderers.Length; i++)
{
if (_renderers[i] != null)
return _renderers[i];
}
return null;
}
}

View file

@ -1,8 +1,8 @@
using System;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Mathematics.math;
using static CultMath.math;
using cfloat3 = CultMath.float3;
public class ItemPickup : MonoBehaviour
{
@ -15,27 +15,28 @@ public class ItemPickup : MonoBehaviour
public ItemInstance Item { get; set; }
public ZoneRenderer ZoneRenderer { get; set; }
public float3 ViewOrigin { get; set; }
public float3 ViewDirection { get; set; }
public cfloat3 ViewOrigin { get; set; }
public cfloat3 ViewDirection { get; set; }
private float _displayTime;
private void Update()
{
var diff = (float3) transform.position - ViewOrigin;
var position = transform.position;
var diff = new cfloat3(position.x, position.y, position.z) - ViewOrigin;
var toThis = normalize(diff);
var viewAngle = acos(Vector3.Dot(toThis, ViewDirection)) * Mathf.Rad2Deg;
var viewAngle = acos(dot(toThis, ViewDirection)) * AetheriaMath.Rad2Deg;
if (length(diff) < LabelDisplayMaxDistance && viewAngle < LabelDisplayAngle)
_displayTime = Time.time;
var targetAlpha = Time.time - _displayTime < LabelPersistDuration ? 1 : 0;
var c = ScanLabel.color;
c.a = c.a + sign(targetAlpha - c.a) * (Time.deltaTime / LabelFadeDuration);
ScanLabel.color = c;
ScanLabelContainer.rotation = Quaternion.LookRotation(-toThis);
ScanLabelContainer.rotation = Quaternion.LookRotation((Vector3)AetheriaMath.ToUnity(-toThis));
}
private void OnDestroy()
{
ZoneRenderer?.DestroyLoot(this);
}
}
}

View file

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

View file

@ -0,0 +1,865 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using UnityEngine;
public sealed class AetheriaYmirPhysicsBridge : MonoBehaviour
{
private const string ProjectileBodyId = "aetheria.projectile";
private const string TargetBodyPrefix = "aetheria.target.";
private static AetheriaYmirPhysicsBridge _instance;
public string ServiceUrl = "http://127.0.0.1:8877/simulate/step";
public string OverlapSphereUrl = "http://127.0.0.1:8877/query/overlap-sphere";
public string OverlapCircleUrl = "http://127.0.0.1:8877/query/overlap-circle";
public string CastCircleUrl = "http://127.0.0.1:8877/query/cast-circle";
public string CastSphereUrl = "http://127.0.0.1:8877/query/cast-sphere";
public bool EnableProjectileCutover = true;
public float ProjectileRadius = 0.1f;
public float ProjectileMass = 1.0f;
public float TargetRadiusPadding = 0.1f;
public int TimeoutMilliseconds = 20;
public AetheriaDaemonObserver DaemonObserver;
private readonly List<YmirPhysicsBody> _projectileBodies = new List<YmirPhysicsBody>();
private readonly List<YmirPhysicsBody> _targetBodies = new List<YmirPhysicsBody>();
private readonly List<YmirPhysicsBody> _zoneBodies = new List<YmirPhysicsBody>();
private readonly List<YmirPhysicsBody> _daemonBodies = new List<YmirPhysicsBody>();
private readonly List<YmirSphereQueryBody> _clickableBodies = new List<YmirSphereQueryBody>();
private readonly Dictionary<string, HullCollider> _hullBodyMap = new Dictionary<string, HullCollider>(StringComparer.Ordinal);
private readonly Dictionary<string, ClickableCollider> _clickableBodyMap = new Dictionary<string, ClickableCollider>(StringComparer.Ordinal);
public static AetheriaYmirPhysicsBridge Instance
{
get
{
if (_instance != null) return _instance;
_instance = FindAnyObjectByType<AetheriaYmirPhysicsBridge>();
if (_instance != null) return _instance;
var bridge = new GameObject("Aetheria Ymir Physics Bridge");
DontDestroyOnLoad(bridge);
_instance = bridge.AddComponent<AetheriaYmirPhysicsBridge>();
return _instance;
}
}
public bool TryStepProjectile(Projectile projectile, float deltaTime, out AetheriaYmirProjectileStep step)
{
step = default;
if (!EnableProjectileCutover || projectile == null || deltaTime <= 0)
return false;
var request = BuildProjectileRequest(projectile, deltaTime);
if (request.world.bodies.Length == 0)
return false;
YmirStepResult result;
try
{
result = PostJson<YmirStepRequest, YmirStepResult>(ServiceUrl, request);
}
catch (Exception error)
{
Debug.LogWarning($"Ymir projectile step failed: {error.Message}");
return false;
}
if (result == null || result.world == null)
return false;
var projectileBody = FindBody(result.world, ProjectileBodyId);
if (projectileBody == null)
return false;
step.Position = new Vector3(projectileBody.position.x, projectile.transform.position.y, projectileBody.position.y);
step.Velocity = new Vector3(projectileBody.velocity.x, projectile.Velocity.y, projectileBody.velocity.y);
foreach (var contact in result.contacts ?? Array.Empty<YmirContactEvent>())
{
var otherBody = string.Equals(contact.bodyA, ProjectileBodyId, StringComparison.Ordinal)
? contact.bodyB
: string.Equals(contact.bodyB, ProjectileBodyId, StringComparison.Ordinal)
? contact.bodyA
: "";
if (string.IsNullOrWhiteSpace(otherBody))
continue;
var hull = ResolveTargetHull(projectile.TargetInstance, otherBody);
if (hull == null)
continue;
step.Hit = new AetheriaYmirProjectileHit(
hull,
new Vector3(contact.point.x, projectile.transform.position.y, contact.point.y),
new Vector3(contact.normal.x, 0, contact.normal.y).normalized);
break;
}
return true;
}
public bool TryOverlapDaemonBodies(
Vector3 center,
float radius,
out IReadOnlyList<YmirCircleOverlapHit> hits)
{
hits = Array.Empty<YmirCircleOverlapHit>();
if (!TryBuildDaemonWorld(out var world) || radius <= 0)
return false;
var request = new YmirCircleOverlapRequest
{
world = world,
center = ToVec2(center),
radius = radius
};
return TryOverlapCircle(request, out hits);
}
public bool TryCastDaemonBodies(
Vector3 origin,
Vector3 direction,
float distance,
float radius,
out IReadOnlyList<YmirCircleCastHit> hits)
{
hits = Array.Empty<YmirCircleCastHit>();
if (!TryBuildDaemonWorld(out var world) || distance <= 0 || radius < 0)
return false;
var planarDirection = new Vector3(direction.x, 0, direction.z);
if (planarDirection.sqrMagnitude <= float.Epsilon)
return false;
var request = new YmirCircleCastRequest
{
world = world,
origin = ToVec2(origin),
direction = ToVec2(planarDirection.normalized),
distance = distance,
radius = radius
};
return TryCastCircle(request, "Ymir daemon body circle cast query", out hits);
}
public bool TryOverlapTargetHulls(
EntityInstance target,
Vector3 center,
float radius,
out IReadOnlyList<AetheriaYmirOverlapHit> hits)
{
hits = Array.Empty<AetheriaYmirOverlapHit>();
if (!EnableProjectileCutover || target == null || radius <= 0)
return false;
var world = BuildTargetWorld(target);
if (world.bodies.Length == 0)
return false;
var request = new YmirCircleOverlapRequest
{
center = ToVec2(center),
radius = radius,
world = world
};
if (!TryOverlapCircle(request, out var ymirHits))
return false;
var resolved = new List<AetheriaYmirOverlapHit>();
foreach (var hit in ymirHits)
{
var hull = ResolveTargetHull(target, hit.bodyId);
if (hull == null)
continue;
resolved.Add(new AetheriaYmirOverlapHit(
hull,
new Vector3(hit.point.x, center.y, hit.point.y),
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
hit.penetration,
hit.distance));
}
hits = resolved;
return true;
}
public bool TryOverlapSphere(
IReadOnlyList<YmirSphereQueryBody> bodies,
Vector3 center,
float radius,
out IReadOnlyList<YmirSphereOverlapHit> hits)
{
hits = Array.Empty<YmirSphereOverlapHit>();
if (bodies == null || bodies.Count == 0)
return false;
var bodyArray = bodies as YmirSphereQueryBody[] ?? CopySphereQueryBodies(bodies);
return TryOverlapSphere(bodyArray, center, radius, "Ymir sphere overlap query", out hits);
}
public bool TryCastTargetHulls(
EntityInstance target,
Vector3 origin,
Vector3 direction,
float distance,
float radius,
out IReadOnlyList<AetheriaYmirCastHit> hits)
{
hits = Array.Empty<AetheriaYmirCastHit>();
if (!EnableProjectileCutover || target == null || distance <= 0 || direction.sqrMagnitude <= 0.000001f)
return false;
var world = BuildTargetWorld(target);
if (world.bodies.Length == 0)
return false;
var request = new YmirCircleCastRequest
{
origin = ToVec2(origin),
direction = ToVec2(direction.normalized),
distance = distance,
radius = Mathf.Max(0, radius),
world = world
};
if (!TryCastCircle(request, "Ymir target cast query", out var ymirHits))
return false;
var resolved = new List<AetheriaYmirCastHit>();
foreach (var hit in ymirHits)
{
var hull = ResolveTargetHull(target, hit.bodyId);
if (hull == null)
continue;
resolved.Add(new AetheriaYmirCastHit(
hull,
ResolveShield(hull),
new Vector3(hit.point.x, origin.y, hit.point.y),
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
hit.distance));
}
hits = resolved;
return true;
}
public bool TryCastClickables(
IReadOnlyList<ClickableCollider> clickables,
Ray ray,
float distance,
out AetheriaYmirClickableHit hit)
{
hit = default;
if (!EnableProjectileCutover || clickables == null || clickables.Count == 0 || distance <= 0)
return false;
_clickableBodyMap.Clear();
_clickableBodies.Clear();
for (var i = 0; i < clickables.Count; i++)
{
var clickable = clickables[i];
if (clickable == null || !clickable.isActiveAndEnabled)
continue;
var bounds = clickable.ClickBounds;
var bodyId = $"aetheria.clickable.{i}";
_clickableBodyMap[bodyId] = clickable;
_clickableBodies.Add(new YmirSphereQueryBody
{
id = bodyId,
position = ToVec3(bounds.center),
radius = Mathf.Max(0.001f, bounds.extents.magnitude)
});
}
if (_clickableBodies.Count == 0)
return false;
var bodyArray = _clickableBodies.ToArray();
var request = new YmirSphereCastRequest
{
origin = ToVec3(ray.origin),
direction = ToVec3(ray.direction.normalized),
distance = distance,
radius = 0,
bodies = bodyArray
};
if (!TryCastSphere(request, "Ymir clickable cast query", out var ymirHits))
return false;
foreach (var resultHit in ymirHits)
{
if (!_clickableBodyMap.TryGetValue(resultHit.bodyId, out var clickable) || clickable == null)
continue;
hit = new AetheriaYmirClickableHit(
clickable,
ToVector3(resultHit.point),
ToVector3(resultHit.normal).normalized,
resultHit.distance);
return true;
}
return false;
}
public bool TryOverlapZoneHulls(
ZoneRenderer zoneRenderer,
Entity sourceEntity,
Vector3 center,
float radius,
out IReadOnlyList<AetheriaYmirOverlapHit> hits)
{
hits = Array.Empty<AetheriaYmirOverlapHit>();
if (!EnableProjectileCutover || zoneRenderer == null || radius <= 0)
return false;
_hullBodyMap.Clear();
var world = BuildZoneWorld(zoneRenderer, sourceEntity, _hullBodyMap);
if (world.bodies.Length == 0)
return false;
var request = new YmirCircleOverlapRequest
{
center = ToVec2(center),
radius = radius,
world = world
};
if (!TryOverlapCircle(request, out var ymirHits))
return false;
var resolved = new List<AetheriaYmirOverlapHit>();
foreach (var hit in ymirHits)
{
if (!_hullBodyMap.TryGetValue(hit.bodyId ?? "", out var hull))
continue;
resolved.Add(new AetheriaYmirOverlapHit(
hull,
new Vector3(hit.point.x, center.y, hit.point.y),
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
hit.penetration,
hit.distance));
}
hits = resolved;
return true;
}
public bool TryCastZoneHulls(
ZoneRenderer zoneRenderer,
Entity sourceEntity,
Vector3 origin,
Vector3 direction,
float distance,
float radius,
out IReadOnlyList<AetheriaYmirCastHit> hits)
{
hits = Array.Empty<AetheriaYmirCastHit>();
if (!EnableProjectileCutover || zoneRenderer == null || distance <= 0 || direction.sqrMagnitude <= 0.000001f)
return false;
_hullBodyMap.Clear();
var world = BuildZoneWorld(zoneRenderer, sourceEntity, _hullBodyMap);
if (world.bodies.Length == 0)
return false;
var request = new YmirCircleCastRequest
{
origin = ToVec2(origin),
direction = ToVec2(direction.normalized),
distance = distance,
radius = Mathf.Max(0, radius),
world = world
};
if (!TryCastCircle(request, "Ymir zone cast query", out var ymirHits))
return false;
var resolved = new List<AetheriaYmirCastHit>();
foreach (var hit in ymirHits)
{
if (!_hullBodyMap.TryGetValue(hit.bodyId ?? "", out var hull))
continue;
resolved.Add(new AetheriaYmirCastHit(
hull,
ResolveShield(hull),
new Vector3(hit.point.x, origin.y, hit.point.y),
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
hit.distance));
}
hits = resolved;
return true;
}
private YmirStepRequest BuildProjectileRequest(Projectile projectile, float deltaTime)
{
var transform = projectile.transform;
_projectileBodies.Clear();
_projectileBodies.Add(new YmirPhysicsBody
{
id = ProjectileBodyId,
position = ToVec2(transform.position),
velocity = ToVec2(projectile.Velocity),
direction = ToVec2(transform.forward),
angularVelocity = 0,
torque = 0,
momentOfInertia = 1,
radius = Mathf.Max(ProjectileRadius, 0.001f),
mass = Mathf.Max(ProjectileMass, 0.001f),
isStatic = false,
restitution = 0
});
var target = projectile.TargetInstance;
if (target == null)
{
return new YmirStepRequest
{
deltaTime = deltaTime,
world = new YmirWorld
{
time = projectile.YmirWorldTime,
bodies = _projectileBodies.ToArray(),
fields = Array.Empty<YmirRadialField>()
}
};
}
_projectileBodies.AddRange(BuildTargetWorld(target).bodies);
return new YmirStepRequest
{
deltaTime = deltaTime,
world = new YmirWorld
{
time = projectile.YmirWorldTime,
bodies = _projectileBodies.ToArray(),
fields = Array.Empty<YmirRadialField>()
}
};
}
private YmirWorld BuildTargetWorld(EntityInstance target)
{
_targetBodies.Clear();
for (var i = 0; i < target.HullColliders.Length; i++)
{
var hull = target.HullColliders[i];
if (hull == null)
continue;
var bounds = hull.YmirBounds;
_targetBodies.Add(new YmirPhysicsBody
{
id = TargetBodyPrefix + i,
position = ToVec2(bounds.center),
velocity = default,
direction = ToVec2(hull.transform.forward),
angularVelocity = 0,
torque = 0,
momentOfInertia = 1,
radius = hull.YmirPlanarRadius + TargetRadiusPadding,
mass = 1,
isStatic = true,
restitution = 0
});
}
return new YmirWorld
{
time = 0,
bodies = _targetBodies.ToArray(),
fields = Array.Empty<YmirRadialField>()
};
}
private YmirWorld BuildZoneWorld(
ZoneRenderer zoneRenderer,
Entity sourceEntity,
Dictionary<string, HullCollider> bodyMap)
{
_zoneBodies.Clear();
var entityIndex = 0;
foreach (var entityInstance in zoneRenderer.EntityInstances.Values)
{
if (entityInstance == null || entityInstance.Entity == sourceEntity)
continue;
for (var hullIndex = 0; hullIndex < entityInstance.HullColliders.Length; hullIndex++)
{
var hull = entityInstance.HullColliders[hullIndex];
if (hull == null)
continue;
var bounds = hull.YmirBounds;
var bodyId = $"aetheria.entity.{entityIndex}.hull.{hullIndex}";
bodyMap[bodyId] = hull;
_zoneBodies.Add(new YmirPhysicsBody
{
id = bodyId,
position = ToVec2(bounds.center),
velocity = default,
direction = ToVec2(hull.transform.forward),
angularVelocity = 0,
torque = 0,
momentOfInertia = 1,
radius = hull.YmirPlanarRadius + TargetRadiusPadding,
mass = 1,
isStatic = true,
restitution = 0
});
}
entityIndex++;
}
return new YmirWorld
{
time = 0,
bodies = _zoneBodies.ToArray(),
fields = Array.Empty<YmirRadialField>()
};
}
private bool TryBuildDaemonWorld(out YmirWorld world)
{
world = null;
var observer = ResolveDaemonObserver();
if (observer == null || !observer.HasRenderNativeView)
return false;
var view = observer.LastRenderNativeView;
if (!view.IsCreated || !view.HasPhysicsRadius)
return false;
_daemonBodies.Clear();
for (var i = 0; i < view.Count; i++)
{
var radius = view.PhysicsBodyRadius[i];
if (radius <= 0)
continue;
var inverseMass = view.HasPhysicsInverseMass ? view.PhysicsBodyInverseMass[i] : 1.0f;
var mass = view.HasPhysicsMass && view.PhysicsBodyMass[i] > 0
? view.PhysicsBodyMass[i]
: inverseMass > 0
? 1.0f / inverseMass
: 1.0f;
var rotation = view.HasRotation ? view.RotationRadians[i] : 0;
_daemonBodies.Add(new YmirPhysicsBody
{
id = $"aetheria.daemon.body.{i}",
position = new YmirVec2 { x = view.PositionX[i], y = view.PositionZ[i] },
velocity = view.HasVelocity
? new YmirVec2 { x = view.VelocityX[i], y = view.VelocityZ[i] }
: default,
direction = new YmirVec2 { x = Mathf.Sin(rotation), y = Mathf.Cos(rotation) },
angularVelocity = 0,
torque = 0,
momentOfInertia = 1,
radius = radius,
mass = Mathf.Max(mass, 0.001f),
isStatic = view.HasPhysicsInverseMass && inverseMass <= 0,
restitution = 0
});
}
if (_daemonBodies.Count == 0)
return false;
world = new YmirWorld
{
time = Time.time,
bodies = _daemonBodies.ToArray(),
fields = Array.Empty<YmirRadialField>()
};
return true;
}
private AetheriaDaemonObserver ResolveDaemonObserver()
{
if (DaemonObserver != null)
return DaemonObserver;
DaemonObserver = FindAnyObjectByType<AetheriaDaemonObserver>();
return DaemonObserver;
}
private bool TryOverlapSphere(
YmirSphereQueryBody[] bodies,
Vector3 center,
float radius,
string failureContext,
out IReadOnlyList<YmirSphereOverlapHit> hits)
{
hits = Array.Empty<YmirSphereOverlapHit>();
if (!EnableProjectileCutover || bodies == null || bodies.Length == 0 || radius <= 0)
return false;
try
{
var result = PostJson<YmirSphereOverlapRequest, YmirSphereOverlapResult>(OverlapSphereUrl, new YmirSphereOverlapRequest
{
center = ToVec3(center),
radius = radius,
bodies = bodies
});
hits = result.hits ?? Array.Empty<YmirSphereOverlapHit>();
return true;
}
catch (Exception error)
{
Debug.LogWarning($"{failureContext} failed: {error.Message}");
return false;
}
}
private bool TryCastSphere(
YmirSphereCastRequest request,
string failureContext,
out IReadOnlyList<YmirSphereCastHit> hits)
{
hits = Array.Empty<YmirSphereCastHit>();
if (!EnableProjectileCutover || request == null || request.bodies == null || request.bodies.Length == 0 || request.distance <= 0 || request.radius < 0)
return false;
try
{
var result = PostJson<YmirSphereCastRequest, YmirSphereCastResult>(CastSphereUrl, request);
hits = result.hits ?? Array.Empty<YmirSphereCastHit>();
return true;
}
catch (Exception error)
{
Debug.LogWarning($"{failureContext} failed: {error.Message}");
return false;
}
}
private bool TryOverlapCircle(
YmirCircleOverlapRequest request,
out IReadOnlyList<YmirCircleOverlapHit> hits)
{
hits = Array.Empty<YmirCircleOverlapHit>();
if (!EnableProjectileCutover || request == null || request.world == null || request.world.bodies == null || request.world.bodies.Length == 0 || request.radius <= 0)
return false;
if (string.IsNullOrWhiteSpace(OverlapCircleUrl))
{
Debug.LogWarning("Ymir circle overlap query failed: daemon URL is not configured.");
return false;
}
try
{
var result = PostJson<YmirCircleOverlapRequest, YmirCircleOverlapResult>(OverlapCircleUrl, request);
hits = result.hits ?? Array.Empty<YmirCircleOverlapHit>();
return true;
}
catch (Exception error)
{
Debug.LogWarning($"Ymir circle overlap query failed: {error.Message}");
return false;
}
}
private bool TryCastCircle(
YmirCircleCastRequest request,
string failureContext,
out IReadOnlyList<YmirCircleCastHit> hits)
{
hits = Array.Empty<YmirCircleCastHit>();
if (!EnableProjectileCutover || request == null || request.world == null || request.world.bodies == null || request.world.bodies.Length == 0 || request.distance <= 0 || request.radius < 0)
return false;
try
{
var result = PostJson<YmirCircleCastRequest, YmirCircleCastResult>(CastCircleUrl, request);
hits = result.hits ?? Array.Empty<YmirCircleCastHit>();
return true;
}
catch (Exception error)
{
Debug.LogWarning($"{failureContext} failed: {error.Message}");
return false;
}
}
private static YmirSphereQueryBody[] CopySphereQueryBodies(IReadOnlyList<YmirSphereQueryBody> bodies)
{
var copy = new YmirSphereQueryBody[bodies.Count];
for (var i = 0; i < copy.Length; i++)
copy[i] = bodies[i];
return copy;
}
private TResult PostJson<TRequest, TResult>(string url, TRequest request)
where TResult : class
{
if (string.IsNullOrWhiteSpace(url))
throw new InvalidOperationException("Ymir daemon URL is not configured.");
var json = JsonUtility.ToJson(request);
var bytes = Encoding.UTF8.GetBytes(json);
var http = (HttpWebRequest)WebRequest.Create(url);
http.Method = "POST";
http.ContentType = "application/json; charset=utf-8";
http.ContentLength = bytes.Length;
http.Timeout = TimeoutMilliseconds;
http.ReadWriteTimeout = TimeoutMilliseconds;
using (var stream = http.GetRequestStream())
stream.Write(bytes, 0, bytes.Length);
using var response = (HttpWebResponse)http.GetResponse();
using var reader = new StreamReader(response.GetResponseStream());
var payload = reader.ReadToEnd();
if (string.IsNullOrWhiteSpace(payload))
throw new InvalidDataException("Ymir daemon returned an empty response.");
var result = JsonUtility.FromJson<TResult>(payload);
if (result == null)
throw new InvalidDataException("Ymir daemon returned a response that could not be parsed.");
return result;
}
private static YmirPhysicsBody FindBody(YmirWorld world, string bodyId)
{
foreach (var body in world.bodies ?? Array.Empty<YmirPhysicsBody>())
{
if (string.Equals(body.id, bodyId, StringComparison.Ordinal))
return body;
}
return null;
}
private static HullCollider ResolveTargetHull(EntityInstance target, string bodyId)
{
if (!bodyId.StartsWith(TargetBodyPrefix, StringComparison.Ordinal))
return null;
return int.TryParse(bodyId.Substring(TargetBodyPrefix.Length), out var index) &&
index >= 0 &&
index < target.HullColliders.Length
? target.HullColliders[index]
: null;
}
private static ShieldManager ResolveShield(HullCollider hull)
{
var instance = hull != null ? hull.GetComponentInParent<EntityInstance>() : null;
return instance != null ? instance.Shield : null;
}
private static YmirVec2 ToVec2(Vector3 value) => new YmirVec2 { x = value.x, y = value.z };
private static YmirVec2 ToVec2(CultMath.float2 value) => new YmirVec2 { x = value.x, y = value.y };
private static YmirVec3 ToVec3(Vector3 value) => new YmirVec3 { x = value.x, y = value.y, z = value.z };
private static Vector3 ToVector3(YmirVec3 value) => new Vector3(value.x, value.y, value.z);
private static Bounds ObjectBounds(GameObject gameObject, Vector3 fallbackCenter)
{
var renderers = gameObject.GetComponentsInChildren<Renderer>();
if (renderers.Length == 0)
return new Bounds(fallbackCenter, Vector3.one);
var bounds = renderers[0].bounds;
for (var i = 1; i < renderers.Length; i++)
bounds.Encapsulate(renderers[i].bounds);
return bounds;
}
}
public struct AetheriaYmirProjectileStep
{
public Vector3 Position;
public Vector3 Velocity;
public AetheriaYmirProjectileHit Hit;
public bool HasHit => Hit.Hull != null;
}
public readonly struct AetheriaYmirProjectileHit
{
public AetheriaYmirProjectileHit(HullCollider hull, Vector3 point, Vector3 normal)
{
Hull = hull;
Point = point;
Normal = normal;
}
public HullCollider Hull { get; }
public Vector3 Point { get; }
public Vector3 Normal { get; }
}
public readonly struct AetheriaYmirOverlapHit
{
public AetheriaYmirOverlapHit(HullCollider hull, Vector3 point, Vector3 normal, float penetration, float distance)
{
Hull = hull;
Point = point;
Normal = normal;
Penetration = penetration;
Distance = distance;
}
public HullCollider Hull { get; }
public Vector3 Point { get; }
public Vector3 Normal { get; }
public float Penetration { get; }
public float Distance { get; }
}
public readonly struct AetheriaYmirCastHit
{
public AetheriaYmirCastHit(HullCollider hull, ShieldManager shield, Vector3 point, Vector3 normal, float distance)
{
Hull = hull;
Shield = shield;
Point = point;
Normal = normal;
Distance = distance;
}
public HullCollider Hull { get; }
public ShieldManager Shield { get; }
public Vector3 Point { get; }
public Vector3 Normal { get; }
public float Distance { get; }
}
public readonly struct AetheriaYmirClickableHit
{
public AetheriaYmirClickableHit(ClickableCollider clickable, Vector3 point, Vector3 normal, float distance)
{
Clickable = clickable;
Point = point;
Normal = normal;
Distance = distance;
}
public ClickableCollider Clickable { get; }
public Vector3 Point { get; }
public Vector3 Normal { get; }
public float Distance { get; }
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8d2521ff2dd1835498a2c00656d4bff6

View file

@ -1,79 +1,42 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using UnityEngine;
public class ShieldManager : MonoBehaviour
{
public Prototype ShieldPrototype;
public float CollisionHitDuration = 3;
public float LootInteractionCooldown = 0.25f;
public float ShieldCollisionCooldown = 0.1f;
public Entity Entity { get; set; }
private void OnCollisionEnter(Collision other)
private bool IsShieldActive()
{
var otherShield = other.collider.GetComponent<ShieldManager>();
if (!otherShield)
{
var gridObject = other.collider.GetComponent<GridObject>();
if (gridObject)
{
var itemPickup = other.collider.GetComponent<ItemPickup>();
var mine = other.collider.GetComponent<Mine>();
if (itemPickup)
{
if (ActionGameManager.Instance != null &&
ActionGameManager.Instance.CommitLootPickup(Entity, itemPickup.Item))
{
// TODO: Pickup notification!
Destroy(itemPickup.gameObject);
}
else
{
// TODO: Pickup failed notification!
var cp = other.GetContact(0);
gridObject.Velocity += cp.normal * 25;
Debug.Log("Attempted item pickup, but no space in cargo bay!");
}
}
else if (mine)
{
mine.Explode();
}
else
{
Debug.Log("Shield collision occurred with grid object, but other collider isn't a mine or an item pickup!");
}
}
else
Debug.Log("Shield collision occurred, but other collider isn't a shield or a grid object!");
return;
}
var contact = other.GetContact(0);
var normal = normalize(float2(contact.normal.x, contact.normal.z));
var tangent = normal.Rotate(ItemRotation.CounterClockwise);
var v1n = dot(normal, Entity.Velocity);
var v1t = dot(tangent, Entity.Velocity);
var v2n = dot(normal, otherShield.Entity.Velocity);
//var v2t = dot(tangent, otherShield.Entity.Velocity);
var v1np = PostCollisionVelocity(v1n, Entity.Mass, v2n, otherShield.Entity.Mass);
Entity.Velocity = tangent * v1t + normal * v1np;
if(Entity.Shield != null && Entity.Shield.Item.Active.Value) ShowHit(contact.point, CollisionHitDuration);
return Entity?.Shield != null && Entity.Shield.Item.Active.Value;
}
private float PostCollisionVelocity(float v1, float m1, float v2, float m2)
private Bounds ShieldBounds()
{
return (v1 * (m1 - m2) + m2 * v2) / (m1 + m2);
var renderers = GetComponentsInChildren<Renderer>();
if (renderers.Length == 0)
return new Bounds(transform.position, Vector3.one);
var bounds = renderers[0].bounds;
for (var i = 1; i < renderers.Length; i++)
bounds.Encapsulate(renderers[i].bounds);
return bounds;
}
private static float ShieldPlanarRadius(Bounds bounds)
{
return Mathf.Max(bounds.extents.x, bounds.extents.z, 0.001f);
}
public void ShowHit(Vector3 point, float duration)
{
var shield = ShieldPrototype.Instantiate<ShieldAnimation>();
shield.Direction = normalize(shield.transform.InverseTransformPoint(point));
shield.Direction = shield.transform.InverseTransformPoint(point).normalized;
shield.Duration = duration;
}
}

View file

@ -1,8 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class ShieldTest : MonoBehaviour
{
@ -12,12 +8,13 @@ public class ShieldTest : MonoBehaviour
// Update is called once per frame
void Update()
{
_velocity += -sign(transform.position.x) * Acceleration * Time.deltaTime;
var previousX = transform.position.x;
_velocity += -Mathf.Sign(transform.position.x) * Acceleration * Time.deltaTime;
transform.position += Vector3.right * (_velocity * Time.deltaTime);
}
private void OnCollisionEnter(Collision other)
{
_velocity = -_velocity;
if (Mathf.Sign(transform.position.x) != Mathf.Sign(previousX))
{
_velocity = -_velocity;
}
}
}

View file

@ -1,10 +1,7 @@
using System;
using System.Collections;
using System.Linq;
using GameCult.Aetheria.State.Unity;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
public class ShipInstance : EntityInstance
{
@ -108,11 +105,11 @@ public class ShipInstance : EntityInstance
base.Update();
TractorBeam.Power = Entity.TractorPower;
TractorBeam.Direction = Entity.LookDirection;
TractorBeam.Direction = (Vector3)AetheriaMath.ToUnity(Entity.CultLookDirection);
if (_aetherDrive != null)
{
var thrust = length(_aetherDrive.Drive.ThrustDirection);
var thrust = CultMath.math.length(_aetherDrive.Drive.ThrustDirection);
var forceOverLifetime = _aetherDrive.Particles.forceOverLifetime;
forceOverLifetime.xMultiplier = _aetherDrive.Drive.ThrustDirection.x * _aetherDrive.BaseForce;
forceOverLifetime.zMultiplier = _aetherDrive.Drive.ThrustDirection.y * _aetherDrive.BaseForce;
@ -128,7 +125,8 @@ public class ShipInstance : EntityInstance
emissionModule.rateOverTimeMultiplier = thrusterInstance.BaseEmission * thrusterInstance.Thruster.Axis * (item.Durability / GetMaxDurability(item));
}
transform.rotation = Ship.Rotation;
var rotation = Ship.Rotation;
transform.rotation = new Quaternion(rotation.x, rotation.y, rotation.z, rotation.w);
}
private float GetMaxDurability(ItemInstance item)

View file

@ -1,19 +0,0 @@
using System;
using UnityEngine;
public class TimedDestroy : MonoBehaviour
{
public float Duration;
private float _startTime;
private void Start()
{
_startTime = Time.time;
}
public void Update()
{
if(Time.time - _startTime > Duration) Destroy(gameObject);
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5d5539a23b814254b038a16f32f1d8a9
timeCreated: 1618453298

View file

@ -15,19 +15,6 @@ public class TractorBeam : MonoBehaviour
{
transform.rotation = Quaternion.LookRotation(Direction);
var emission = ParticleSystem.emission;
if (Power > .01f)
{
var objectFound = false;
foreach (var o in Physics.SphereCastAll(transform.position, Radius, Direction, Distance, 1 << 21))
{
var gridObject = o.collider.GetComponent<GridObject>();
if (gridObject == null) continue;
objectFound = true;
gridObject.Velocity += Vector3.Normalize(transform.position - gridObject.transform.position) * (Traction * Time.deltaTime);
}
emission.rateOverTimeMultiplier = objectFound ? Power : 0;
}
else emission.rateOverTimeMultiplier = 0;
emission.rateOverTimeMultiplier = Power > .01f ? Power : 0;
}
}
}

View file

@ -1,7 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Unity.Mathematics.math;
public class ConstantLaser : MonoBehaviour
{
@ -70,34 +67,34 @@ public class ConstantLaser : MonoBehaviour
}
LineRenderer.SetPosition(0, transform.position);
bool hitFound = false;
foreach (var hit in Physics.RaycastAll(new Ray(transform.position, transform.forward), Range, 1 | (1 << 17)))
var hitFound = false;
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
ActionGameManager.Instance?.ZoneRenderer,
SourceEntity,
transform.position,
transform.forward,
Range,
0,
out var hits))
{
var shield = hit.collider.GetComponent<ShieldManager>();
if (shield)
foreach (var hit in hits)
{
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
if (shield.Entity != SourceEntity)
var hull = hit.Hull;
var entity = hull.Entity;
if (entity.Shield != null && entity.Shield.Item.Active.Value)
{
shield.Entity.Shield.TakeHit(DamageType, Damage);
shield.ShowHit(hit.point, sqrt(Damage));
LineRenderer.SetPosition(1, hit.point);
hitFound = true;
break;
}
}
var hull = hit.collider.GetComponent<HullCollider>();
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
{
if (hull.Entity != SourceEntity)
{
hull.SendHit(Damage * Time.deltaTime, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
LineRenderer.SetPosition(1, hit.point);
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
LineRenderer.SetPosition(1, hit.Point);
hitFound = true;
break;
}
LineRenderer.SetPosition(1, hit.Point);
hitFound = true;
break;
}
}
if(!hitFound)
LineRenderer.SetPosition(1, transform.position + transform.forward * Range);
}

View file

@ -1,7 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Unity.Mathematics.math;
public class ConstantLightning : MonoBehaviour
{
@ -51,28 +48,34 @@ public class ConstantLightning : MonoBehaviour
}
Lightning.FixedEndpoint = false;
var hits = Physics.SphereCastAll(Barrel.position, HitRadius, Barrel.forward, Range, 1 | (1 << 17));
foreach (var hit in hits)
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
Source?.ZoneRenderer,
Source?.Entity,
Barrel.position,
Barrel.forward,
Range,
HitRadius,
out var hits))
{
var shield = hit.collider.GetComponent<ShieldManager>();
if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage)))
foreach (var hit in hits)
{
if (shield.Entity == Source.Entity) continue;
shield.Entity.Shield.TakeHit(DamageType, Damage * Time.deltaTime);
shield.ShowHit(hit.point, sqrt(Damage));
}
var hull = hit.collider.GetComponent<HullCollider>();
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
{
if (hull.Entity == Source.Entity) continue;
hull.SendHit(Damage * Time.deltaTime, Penetration, Spread, DamageType, Source.Entity, hit.textureCoord, Barrel.forward);
}
var hull = hit.Hull;
var entity = hull.Entity;
if (entity.Shield != null && entity.Shield.Item.Active.Value)
{
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
}
else
{
}
Lightning.FixedEndpoint = true;
Lightning.EndPosition = hit.point;
Lightning.FixedEndpoint = true;
Lightning.EndPosition = hit.Point;
break;
break;
}
}
if(!Lightning.FixedEndpoint)
Lightning.EndPosition = Barrel.position + Barrel.forward * Range;

View file

@ -1,32 +1,26 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ConstantParticleWeapon : MonoBehaviour
{
public ParticleSystem[] Particles;
public float CastRadius = 0.25f;
public float Damage { get; set; }
public float Range { get; set; }
public DamageType DamageType { get; set; }
public EntityInstance Source { get; set; }
public EntityInstance Target { get; set; }
private bool _stopping;
private float _emission;
private List<ParticleSystem.Particle> _collisionParticles = new List<ParticleSystem.Particle>();
private HullCollider _hull;
private void Start()
{
_emission = Particles[0].emission.rateOverTime.constant;
}
private void OnEnable()
{
foreach(var p in Particles)
p.enableEmission = true;
{
var emission = p.emission;
emission.enabled = true;
}
_stopping = false;
}
@ -38,18 +32,6 @@ public class ConstantParticleWeapon : MonoBehaviour
main.simulationSpace = ParticleSystemSimulationSpace.Custom;
main.customSimulationSpace = Source.LocalSpace;
}
var trigger = Particles[0].trigger;
while(trigger.colliderCount > 0)
trigger.RemoveCollider(0);
if (Target == null) return;
foreach (var collider in Target.HullColliders)
{
_hull = collider;
trigger.AddCollider(collider.GetComponent<Collider>());
}
}
private void Update()
@ -60,27 +42,32 @@ public class ConstantParticleWeapon : MonoBehaviour
GetComponent<Prototype>().ReturnToPool();
return;
}
}
private void OnParticleTrigger()
{
if (Target == null || !_hull) return;
_collisionParticles.Clear();
Particles[0].GetTriggerParticles(ParticleSystemTriggerEventType.Enter, _collisionParticles);
if(_collisionParticles.Count > 0)
if (_stopping || Target == null || Range <= 0)
return;
if (AetheriaYmirPhysicsBridge.Instance.TryCastTargetHulls(
Target,
transform.position,
transform.forward,
Range,
CastRadius,
out var hits))
{
_hull.SendSplash(
Damage * Time.deltaTime,
DamageType,
Source.Entity,
(transform.position - Target.transform.position).normalized);
foreach (var hit in hits)
{
break;
}
}
}
public void Stop()
{
foreach(var p in Particles)
p.enableEmission = false;
{
var emission = p.emission;
emission.enabled = false;
}
_stopping = true;
}
}

View file

@ -21,6 +21,7 @@ public class ConstantParticleWeaponManager : ConstantWeaponEffectManager
t.position = barrel.position;
p.Damage = weapon.Damage;
p.DamageType = weapon.DamageType;
p.Range = weapon.Range;
_weapons.Add(item, p);
p.Initialize();
}

View file

@ -1,13 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static Unity.Mathematics.noise;
using Random = UnityEngine.Random;
using static Noise1D;
using float3 = Unity.Mathematics.float3;
public class GuidedProjectile : MonoBehaviour
{
@ -36,7 +31,7 @@ public class GuidedProjectile : MonoBehaviour
private Vector3 _previousTargetPosition;
public Transform Target { get; set; }
public float3 StartPosition { get; set; }
public Vector3 StartPosition { get; set; }
public float Range { get; set; }
public Vector3 Velocity { get; set; }
public float Damage { get; set; }
@ -52,9 +47,7 @@ public class GuidedProjectile : MonoBehaviour
_active = _alive = true;
_phase = Random.value * 100;
_prevDist = Single.MaxValue;
Particles.startColor = Color.white;
//var main = Particles.main;
//main.startColor = new ParticleSystem.MinMaxGradient { mode = ParticleSystemGradientMode.Color, color = Color.white };
SetParticleColor(Color.white);
Particles.Clear(true);
Particles.Play(true);
}
@ -72,18 +65,18 @@ public class GuidedProjectile : MonoBehaviour
StartCoroutine(FadeOut());
return;
}
var position = (float3) t.position;
var position = t.position;
var targetPosition = TargetPosition?.Invoke() ?? Target.position;
_targetVelocity = lerp(_targetVelocity, targetPosition - _previousTargetPosition, saturate(Time.deltaTime * 5));
_targetVelocity = Vector3.Lerp(_targetVelocity, targetPosition - _previousTargetPosition, Mathf.Clamp01(Time.deltaTime * 5));
_previousTargetPosition = targetPosition;
targetPosition = AetheriaMath.FirstOrderIntercept(position,float3.zero, TopSpeed, targetPosition, _targetVelocity);
targetPosition = AetheriaMath.FirstOrderIntercept(position, Vector3.zero, TopSpeed, targetPosition, _targetVelocity);
var diff = targetPosition - transform.position;
var targetDist = diff.magnitude;
var sourceDist = length(StartPosition.xz - position.xz);
var sourceDist = Vector2.Distance(new Vector2(StartPosition.x, StartPosition.z), new Vector2(position.x, position.z));
if (sourceDist > Range || dot(diff,Velocity) < 0)
if (sourceDist > Range || Vector3.Dot(diff, Velocity) < 0)
{
StartCoroutine(FadeOut());
if (HitEffect != null)
@ -98,8 +91,8 @@ public class GuidedProjectile : MonoBehaviour
var targetDistFlat = diff.Flatland().magnitude;
var curveLerp = 1 - targetDistFlat / (sourceDist + targetDistFlat);
var dir = diff.normalized;
var right = cross(dir, float3(0, 1, 0));
var up = cross(dir, right);
var right = Vector3.Cross(dir, Vector3.up);
var up = Vector3.Cross(dir, right);
if (Children > 0 && SplitTime < curveLerp)
{
@ -108,9 +101,9 @@ public class GuidedProjectile : MonoBehaviour
var child = ChildProjectile.Instantiate<GuidedProjectile>();
child.transform.position = t.position;
child.StartPosition = StartPosition;
var randomDirection = normalize(Random.insideUnitCircle);
var randomDirection = Random.insideUnitCircle.normalized;
var perpendicularRandom = randomDirection.x * right + randomDirection.y * up;
child.Velocity = normalize(lerp(perpendicularRandom, dir, SplitSeparationForwardness)) * length(Velocity) * SplitSeparationVelocity;
child.Velocity = Vector3.Lerp(perpendicularRandom, dir, SplitSeparationForwardness).normalized * Velocity.magnitude * SplitSeparationVelocity;
child.Range = Range;
child.Damage = Damage / Children;
child.Penetration = Penetration;
@ -135,56 +128,53 @@ public class GuidedProjectile : MonoBehaviour
}
}
var dodge = normalize(lerp(
normalize(right * noise(Time.time * Frequency + _phase) + up * noise(Time.time * Frequency + (100 + _phase))),
Vector3.up, LiftCurve.Evaluate(curveLerp)));
var dodge = Vector3.Lerp(
(right * noise(Time.time * Frequency + _phase) + up * noise(Time.time * Frequency + (100 + _phase))).normalized,
Vector3.up,
LiftCurve.Evaluate(curveLerp)).normalized;
var desired = Vector3.Slerp(dodge, dir, GuidanceCurve.Evaluate(curveLerp)).normalized * TopSpeed;
var thrustCurve = ThrustCurve.Evaluate(curveLerp);
var thrust = Thrust * thrustCurve;
var c = Color.white * thrustCurve;
c.a = 1;
Particles.startColor = c;
SetParticleColor(c);
Velocity += (desired-Velocity).normalized * (thrust * Time.deltaTime);
}
if(_alive)
{
var ray = new Ray(t.position, Velocity);
foreach (var hit in Physics.RaycastAll(ray, Velocity.magnitude * Time.deltaTime, 1 | (1 << 17)))
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
ActionGameManager.Instance?.ZoneRenderer,
SourceEntity,
t.position,
Velocity,
Velocity.magnitude * Time.deltaTime,
0,
out var hits) &&
hits.Count > 0)
{
var shield = hit.collider.GetComponent<ShieldManager>();
if (shield)
var hit = hits[0];
var hull = hit.Hull;
var entity = hull.Entity;
if (entity.Shield != null && entity.Shield.Item.Active.Value)
{
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
if (shield.Entity != SourceEntity)
{
shield.Entity.Shield.TakeHit(DamageType, Damage);
shield.ShowHit(hit.point, sqrt(Damage));
}
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
}
var hull = hit.collider.GetComponent<HullCollider>();
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
else
{
if (hull.Entity != SourceEntity)
{
hull.SendHit(Damage, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
transform.position = hit.point;
StartCoroutine(Kill());
}
}
else// if (hit.transform.gameObject.layer == 1)
{
StartCoroutine(Kill());
return;
}
transform.position = hit.Point;
StartCoroutine(Kill());
if (HitEffect != null)
{
var ht = HitEffect.Instantiate<Transform>();
ht.SetParent(hit.collider.transform);
ht.position = hit.point;
return;
ht.SetParent(hull.transform);
ht.position = hit.Point;
}
return;
}
t.position += Velocity * Time.deltaTime;
@ -200,15 +190,19 @@ public class GuidedProjectile : MonoBehaviour
var lerp = 1 - (Time.time - startTime) / FadeOutTime;
var c = Color.white * lerp;
c.a = 1;
Particles.startColor = c;
//var main = Particles.main;
//main.startColor = new ParticleSystem.MinMaxGradient { mode = ParticleSystemGradientMode.Color, color = Color.white * lerp };
SetParticleColor(c);
yield return null;
}
StartCoroutine(Kill());
}
private void SetParticleColor(Color color)
{
var main = Particles.main;
main.startColor = color;
}
IEnumerator Kill()
{
_active = false;
@ -223,4 +217,4 @@ public class GuidedProjectile : MonoBehaviour
OnKill?.Invoke();
GetComponent<Prototype>().ReturnToPool();
}
}
}

View file

@ -2,9 +2,8 @@
using System.Collections;
using System.Collections.Generic;
using UniRx;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Mathematics.math;
using cfloat3 = CultMath.float3;
public class GuidedProjectileManager : InstantWeaponEffectManager
{
@ -64,7 +63,13 @@ public class GuidedProjectileManager : InstantWeaponEffectManager
p.Velocity = barrel.forward * weapon.Velocity;
p.Thrust = weapon.EvaluateGuidedProjectileThrust();
p.TopSpeed = weapon.EvaluateGuidedProjectileVelocity();
p.TargetPosition = () => source.Entity.Position + length( (float3)source.LookAtPoint.position - source.Entity.Position) * source.Entity.LookDirection;
p.TargetPosition = () =>
{
var lookPosition = source.LookAtPoint.position;
var lookPoint = new cfloat3(lookPosition.x, lookPosition.y, lookPosition.z);
var lookDistance = CultMath.math.length(lookPoint - source.Entity.CultPosition);
return (Vector3)AetheriaMath.ToUnity(source.Entity.CultPosition + lookDistance * source.Entity.CultLookDirection);
};
}
}
}

View file

@ -1,7 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Unity.Mathematics.math;
public class HitscanEffect : MonoBehaviour
{
@ -27,45 +24,47 @@ public class HitscanEffect : MonoBehaviour
{
_startTime = Time.time;
var hitFound = false;
var ray = new Ray(transform.position, transform.forward);
foreach (var hit in Physics.RaycastAll(ray, Range, 1 | (1 << 17)))
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
ActionGameManager.Instance?.ZoneRenderer,
SourceEntity,
transform.position,
transform.forward,
Range,
0,
out var hits))
{
var shield = hit.collider.GetComponent<ShieldManager>();
if (shield)
foreach (var hit in hits)
{
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
if (shield.Entity != SourceEntity)
var hull = hit.Hull;
var entity = hull.Entity;
if (entity.Shield != null && entity.Shield.Item.Active.Value)
{
shield.Entity.Shield.TakeHit(DamageType, Damage);
shield.ShowHit(hit.point, sqrt(Damage));
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
hitFound = true;
}
}
var hull = hit.collider.GetComponent<HullCollider>();
if (hull)
{
if (hull.Entity != SourceEntity)
else
{
hull.SendHit(Damage, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
hitFound = true;
}
}
if (hitFound && HitEffect != null)
{
var ht = HitEffect.Instantiate<Transform>();
ht.SetParent(hit.collider.transform);
ht.position = hit.point;
if (hitFound && HitEffect != null)
{
var ht = HitEffect.Instantiate<Transform>();
ht.SetParent(hull.transform);
ht.position = hit.Point;
}
var length = (hit.Point - transform.position).magnitude;
Line.SetPosition(1, Vector3.forward * length);
var emission = LineEffect.emission;
emission.rateOverTimeMultiplier = length;
var shape = LineEffect.shape;
shape.position = Vector3.forward * (length / 2);
shape.scale = Vector3.one * (length / 2);
break;
}
var length = (hit.point - transform.position).magnitude;
Line.SetPosition(1, Vector3.forward * length);
var emission = LineEffect.emission;
emission.rateOverTimeMultiplier = length;
var shape = LineEffect.shape;
shape.position = Vector3.forward * (length / 2);
shape.scale = Vector3.one * (length / 2);
}
if(!hitFound)
{
Line.SetPosition(1, Vector3.forward * Range);

View file

@ -1,8 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Unity.Mathematics.math;
public class Laser : MonoBehaviour
{
@ -36,34 +34,34 @@ public class Laser : MonoBehaviour
}
LineRenderer.SetPosition(0, transform.position);
bool hitFound = false;
foreach (var hit in Physics.RaycastAll(new Ray(transform.position, transform.forward), Range, 1 | (1 << 17)))
var hitFound = false;
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
ActionGameManager.Instance?.ZoneRenderer,
SourceEntity,
transform.position,
transform.forward,
Range,
0,
out var hits))
{
var shield = hit.collider.GetComponent<ShieldManager>();
if (shield)
foreach (var hit in hits)
{
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
if (shield.Entity != SourceEntity)
var hull = hit.Hull;
var entity = hull.Entity;
if (entity.Shield != null && entity.Shield.Item.Active.Value)
{
shield.Entity.Shield.TakeHit(DamageType, Damage);
shield.ShowHit(hit.point, sqrt(Damage));
LineRenderer.SetPosition(1, hit.point);
hitFound = true;
break;
}
}
var hull = hit.collider.GetComponent<HullCollider>();
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
{
if (hull.Entity != SourceEntity)
{
hull.SendHit(Damage * (Time.deltaTime / Duration), Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
LineRenderer.SetPosition(1, hit.point);
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
LineRenderer.SetPosition(1, hit.Point);
hitFound = true;
break;
}
LineRenderer.SetPosition(1, hit.Point);
hitFound = true;
break;
}
}
if(!hitFound)
LineRenderer.SetPosition(1, transform.position + transform.forward * Range);

View file

@ -1,12 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static Unity.Mathematics.noise;
using Random = UnityEngine.Random;
using static Unity.Mathematics.math;
public class Lightning : MonoBehaviour
{
@ -32,37 +25,44 @@ public class Lightning : MonoBehaviour
_colliderHit = false;
LightningCompute.OnLeaderComplete = null;
LightningCompute.FixedEndpoint = false;
var hits = Physics.SphereCastAll(Barrel.position, HitRadius, Barrel.forward, Range, 1 | (1 << 17));
foreach (var hit in hits)
_endpoint = Barrel.position + Barrel.forward * Range;
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
Source?.ZoneRenderer,
Source?.Entity,
Barrel.position,
Barrel.forward,
Range,
HitRadius,
out var hits))
{
var shield = hit.collider.GetComponent<ShieldManager>();
if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage)))
foreach (var hit in hits)
{
if (shield.Entity == Source.Entity) continue;
LightningCompute.OnLeaderComplete = () =>
var hull = hit.Hull;
var entity = hull.Entity;
if (entity.Shield != null && entity.Shield.Item.Active.Value)
{
shield.Entity.Shield.TakeHit(DamageType, Damage);
shield.ShowHit(hit.point, sqrt(Damage));
};
}
var hull = hit.collider.GetComponent<HullCollider>();
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
{
if (hull.Entity == Source.Entity) continue;
LightningCompute.OnLeaderComplete = () =>
hull.SendHit(Damage, Penetration, Spread, DamageType, Source.Entity, hit.textureCoord, Barrel.forward);
}
LightningCompute.OnLeaderComplete = () =>
{
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
};
}
else
{
LightningCompute.OnLeaderComplete = null;
}
_colliderHit = true;
_colliderLocalPosition = hit.collider.transform.InverseTransformPoint(hit.point);
_colliderTransform = hit.collider.transform;
LightningCompute.FixedEndpoint = true;
break;
_colliderHit = true;
_colliderLocalPosition = hull.transform.InverseTransformPoint(hit.Point);
_colliderTransform = hull.transform;
LightningCompute.FixedEndpoint = true;
_endpoint = hit.Point;
break;
}
}
LightningCompute.OnPulseComplete = () =>
GetComponent<Prototype>().ReturnToPool();
LightningCompute.StartAnimation();
_endpoint = Barrel.position + Barrel.forward * Range;
}
private void Update()

View file

@ -1,8 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using UnityEngine;
using Random = UnityEngine.Random;
@ -51,8 +49,6 @@ public class Mine : MonoBehaviour
// Update is called once per frame
void Update()
{
var position = transform.position;
if (Time.time - _startTime > ActivationDelay && !_active)
{
_active = true;
@ -60,25 +56,11 @@ public class Mine : MonoBehaviour
if(_active)
{
_emission = lerp(_emission, _blastCountdown ? ArmedEmission : ActiveEmission, Time.deltaTime * 10);
_emission = Mathf.Lerp(_emission, _blastCountdown ? ArmedEmission : ActiveEmission, Time.deltaTime * 10);
_pulseLerp += Time.deltaTime / (_blastCountdown ? ArmedCycleDuration : ActiveCycleDuration);
_pulseLerp %= 1;
if(!_blastCountdown)
{
foreach (var collider in Physics.OverlapSphere(position, BlastRange, 1))
{
var hull = collider.GetComponent<HullCollider>();
if (hull)
{
_blastCountdown = true;
_blastTime = Time.time + BlastDelay;
}
}
}
if ((position - Source.transform.position).magnitude > Range ||
Time.time - _startTime > Lifetime ||
_blastCountdown && Time.time > _blastTime)
if (Time.time - _startTime > Lifetime)
{
Explode();
}
@ -89,21 +71,6 @@ public class Mine : MonoBehaviour
public void Explode()
{
var position = transform.position;
foreach (var collider in Physics.OverlapSphere(position, BlastRange, 1 | (1 << 17)))
{
var shield = collider.GetComponent<ShieldManager>();
if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage)))
{
shield.Entity.Shield.TakeHit(DamageType, Damage);
shield.ShowHit(position, sqrt(Damage));
}
var hull = collider.GetComponent<HullCollider>();
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
{
hull.SendSplash(Damage, DamageType, Source.Entity, (collider.transform.position - position).normalized);
}
}
var ht = HitEffect.Instantiate<Transform>();
ht.position = position;
GetComponent<Prototype>().ReturnToPool();

View file

@ -1,14 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Random = UnityEngine.Random;
public class MineManager : InstantWeaponEffectManager
{
public Prototype ProjectilePrototype;
public bool InheritVelocity;
public override void Fire(InstantWeapon weapon, EquippedItem item, EntityInstance source, EntityInstance target)
{
@ -21,11 +18,9 @@ public class MineManager : InstantWeaponEffectManager
p.GridObject.Velocity = Quaternion.Euler(
Random.Range(-angle, angle),
Random.Range(-angle, angle),
Random.Range(-angle, angle)) *
Random.Range(-angle, angle)) *
barrel.forward *
weapon.Velocity;
if(InheritVelocity)
p.GridObject.Velocity += new Vector3(source.Entity.Velocity.x, 0, source.Entity.Velocity.y);
p.Damage = weapon.Damage;
p.Range = weapon.Range;
p.DamageType = weapon.DamageType;

View file

@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ParticleChargeEffect : WeaponChargeEffect
@ -21,7 +19,7 @@ public class ParticleChargeEffect : WeaponChargeEffect
OverchargeEffect.Clear(true);
FailureEffect.Clear(true);
ChargeEffect.Play(true);
ChargeEffect.enableEmission = true;
SetEmission(ChargeEffect, true);
_overloaded = false;
_charge = 0;
@ -30,23 +28,23 @@ public class ParticleChargeEffect : WeaponChargeEffect
public override void StopCharging()
{
if (_overloaded)
OverchargeEffect.enableEmission = false;
SetEmission(OverchargeEffect, false);
else
ChargeEffect.enableEmission = false;
SetEmission(ChargeEffect, false);
StartCoroutine(Kill());
}
public override void Charged()
{
ChargeEffect.enableEmission = false;
SetEmission(ChargeEffect, false);
OverchargeEffect.Play(true);
OverchargeEffect.enableEmission = true;
SetEmission(OverchargeEffect, true);
_overloaded = true;
}
public override void Failed()
{
OverchargeEffect.enableEmission = false;
SetEmission(OverchargeEffect, false);
FailureEffect.Play(true);
}
@ -57,11 +55,18 @@ public class ParticleChargeEffect : WeaponChargeEffect
if (!_overloaded)
{
ChargeEffect.playbackSpeed = _charge;
var main = ChargeEffect.main;
main.simulationSpeed = _charge;
}
}
IEnumerator Kill()
private static void SetEmission(ParticleSystem particleSystem, bool enabled)
{
var emission = particleSystem.emission;
emission.enabled = enabled;
}
private IEnumerator Kill()
{
while (ChargeEffect.particleCount > 0 || OverchargeEffect.particleCount > 0 || FailureEffect.particleCount > 0)
{

View file

@ -1,8 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using UnityEngine;
using Random = UnityEngine.Random;
@ -28,7 +26,9 @@ public class Projectile : MonoBehaviour
public float Spread { get; set; }
public DamageType DamageType { get; set; }
public Entity SourceEntity { get; set; }
public EntityInstance TargetInstance { get; set; }
public float Range { get; set; }
public float YmirWorldTime { get; private set; }
// Start is called before the first frame update
void Start()
@ -51,68 +51,73 @@ public class Projectile : MonoBehaviour
var t = transform;
var position = t.position;
Velocity -= Vector3.up * (Gravity * Time.deltaTime);
Velocity *= max(0, 1 - Drag * Time.deltaTime);
Velocity *= Mathf.Max(0, 1 - Drag * Time.deltaTime);
var forward = Velocity.normalized;
t.forward = forward;
var ray = new Ray(position, Velocity);
foreach (var hit in Physics.RaycastAll(ray, Velocity.magnitude * Time.deltaTime, 1 | (1 << 17)))
if (!AetheriaYmirPhysicsBridge.Instance.TryStepProjectile(this, Time.deltaTime, out var ymirStep))
{
var shield = hit.collider.GetComponent<ShieldManager>();
var hull = hit.collider.GetComponent<HullCollider>();
if (shield)
{
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
if (shield.Entity != SourceEntity)
{
shield.Entity.Shield.TakeHit(DamageType, Damage*DirectHitDamageMultiplier);
shield.ShowHit(hit.point, sqrt(Damage * DirectHitDamageMultiplier));
}
}
else if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
{
if (hull.Entity != SourceEntity)
{
hull.SendHit(Damage*DirectHitDamageMultiplier, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, forward);
transform.position = hit.point;
StartCoroutine(Kill());
}
}
else
{
StartCoroutine(Kill());
return;
}
if (HitEffect != null)
{
var ht = HitEffect.Instantiate<Transform>();
ht.SetParent(hit.collider.transform);
ht.position = hit.point;
return;
}
Debug.LogWarning("Ymir projectile step unavailable; projectile killed instead of falling back to Unity physics.");
StartCoroutine(Kill());
return;
}
transform.position += Velocity * Time.deltaTime;
YmirWorldTime += Time.deltaTime;
Velocity = ymirStep.Velocity;
t.position = ymirStep.Position;
if (ymirStep.HasHit)
{
ApplyYmirHit(ymirStep, forward);
return;
}
var distanceTraveled = (transform.position - StartPosition).magnitude;
if(distanceTraveled > Range)
StartCoroutine(Kill());
if (AirburstRange > 1 && distanceTraveled > AirburstDistance)
{
StartCoroutine(Kill());
var ht = HitEffect.Instantiate<Transform>();
ht.position = t.position;
foreach (var collider in Physics.OverlapSphere(t.position, AirburstRange, 1))
if (HitEffect != null)
{
var hull = collider.GetComponent<HullCollider>();
if (hull)
var ht = HitEffect.Instantiate<Transform>();
ht.position = t.position;
}
if (AetheriaYmirPhysicsBridge.Instance.TryOverlapTargetHulls(TargetInstance, t.position, AirburstRange, out var hits))
{
foreach (var hit in hits)
{
hull.SendSplash(Damage, DamageType, SourceEntity, (collider.transform.position - t.position).normalized);
var direction = (hit.Hull.transform.position - t.position).normalized;
}
}
}
}
}
private void ApplyYmirHit(AetheriaYmirProjectileStep step, Vector3 forward)
{
var hull = step.Hit.Hull;
if (hull == null || hull.Entity == SourceEntity)
return;
if (hull.Entity.Shield != null &&
hull.Entity.Shield.Item.Active.Value)
{
}
else
{
}
transform.position = step.Hit.Point;
if (HitEffect != null)
{
var ht = HitEffect.Instantiate<Transform>();
ht.SetParent(hull.transform);
ht.position = step.Hit.Point;
}
StartCoroutine(Kill());
}
IEnumerator Kill()
{
_alive = false;

View file

@ -1,8 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Random = UnityEngine.Random;
public class ProjectileManager : InstantWeaponEffectManager
@ -17,6 +15,7 @@ public class ProjectileManager : InstantWeaponEffectManager
var barrel = source.GetBarrel(hp);
var angle = weapon.Spread / 2;
p.SourceEntity = source.Entity;
p.TargetInstance = target;
p.Velocity = Quaternion.Euler(
Random.Range(-angle, angle),
Random.Range(-angle, angle),
@ -25,14 +24,16 @@ public class ProjectileManager : InstantWeaponEffectManager
weapon.Velocity;
p.StartPosition = p.transform.position = barrel.position + p.Velocity * (Random.value * Time.deltaTime);
if(InheritVelocity)
p.Velocity += new Vector3(source.Entity.Velocity.x, 0, source.Entity.Velocity.y);
p.Velocity += (Vector3)AetheriaMath.ToUnityXZ(source.Entity.CultVelocity);
p.Damage = weapon.Damage;
p.Range = weapon.Range;
p.Penetration = weapon.Penetration;
p.Spread = weapon.DamageSpread;
p.DamageType = weapon.DamageType;
p.Zone = source.Entity.Zone;
p.AirburstDistance = target != null ? length(source.Entity.Position - target.Entity.Position) : (weapon.Range * .75f);
p.AirburstDistance = target != null
? CultMath.math.length(source.Entity.CultPosition - target.Entity.CultPosition)
: (weapon.Range * .75f);
p.Trail.Clear();
}
}

View file

@ -24,7 +24,7 @@ public class Gravity : MonoBehaviour
if (_instance != null)
return _instance;
_instance = (Gravity)FindObjectOfType(typeof(Gravity));
_instance = FindAnyObjectByType<Gravity>();
return _instance;
}
}
@ -47,7 +47,6 @@ public class Gravity : MonoBehaviour
if (Instance.ForceMode == GravityForceMode.Sign)
{
var hits = Instance.GravityObjects.Where(go => (go.transform.position.Flatland() - position).sqrMagnitude < go.transform.lossyScale.x)
// var hits = Physics.RaycastAll(new Ray(new Vector3(position.x, 10, position.y), Vector3.down), 20, LayerMask.GetMask("Displacement"))
.Where(h => !h.CompareTag("IgnoreGravity"));
Vector2 force = Vector2.zero;
@ -130,4 +129,4 @@ public enum GravityForceMode
{
Sign,
Gradient
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static CultMath.math;
[Serializable]
public class ExponentialCurve

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more