Sync daemon authority migration
This commit is contained in:
parent
030b4b5d74
commit
39a0dc8737
295 changed files with 25121 additions and 7360 deletions
|
|
@ -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"": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
184
Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs
Normal file
184
Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 66b38a2f45120a041941eae4412f7da1
|
||||
158
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs
Normal file
158
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta
Normal file
2
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4f5008a2876ee014fb9e442f1f6103e0
|
||||
81
Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs
Normal file
81
Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8fa370b1ea2904d40ae295007ac76b48
|
||||
294
Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs
Normal file
294
Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9cba277ef259bee438796ab4c0f3df64
|
||||
177
Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs
Normal file
177
Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0038a222765ff82468c22a516cfcdbc1
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 146aa8d6d00cc1a44b44a5f83d69edea
|
||||
49
Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs
Normal file
49
Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f6b3b3dc2e2a29245a24b772e2cb87b4
|
||||
248
Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs
Normal file
248
Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 48740dbd37e79954ca00a3f6bd7ccef1
|
||||
220
Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs
Normal file
220
Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8988c7d796df504dab64fd2c64eb868
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
Assets/Scripts/Gameplay/Physics.meta
Normal file
8
Assets/Scripts/Gameplay/Physics.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: baa9b1a7f453a7a4aafe9c44c9fba7c3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
865
Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs
Normal file
865
Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs
Normal 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; }
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8d2521ff2dd1835498a2c00656d4bff6
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5d5539a23b814254b038a16f32f1d8a9
|
||||
timeCreated: 1618453298
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"name": "Aetheria.Shared.Unity",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"CultMath",
|
||||
"Unity.Mathematics",
|
||||
"UniRx",
|
||||
"Ink",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,78 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using CultMath;
|
||||
using static CultMath.math;
|
||||
using double2 = CultMath.double2;
|
||||
using double3 = CultMath.double3;
|
||||
using float2 = CultMath.float2;
|
||||
using float3 = CultMath.float3;
|
||||
using unityfloat2 = Unity.Mathematics.float2;
|
||||
using unityfloat3 = Unity.Mathematics.float3;
|
||||
|
||||
public static class AetheriaMath
|
||||
{
|
||||
public const float Deg2Rad = 0.01745329f;
|
||||
public const float Rad2Deg = 57.29578f;
|
||||
|
||||
public static float2 ToCult(unityfloat2 value) => float2(value.x, value.y);
|
||||
public static float3 ToCult(unityfloat3 value) => float3(value.x, value.y, value.z);
|
||||
public static float2 ToCultXZ(unityfloat3 value) => float2(value.x, value.z);
|
||||
public static unityfloat2 ToUnity(float2 value) => new(value.x, value.y);
|
||||
public static unityfloat3 ToUnity(float3 value) => new(value.x, value.y, value.z);
|
||||
public static unityfloat3 ToUnityXZ(float2 value) => new(value.x, 0, value.y);
|
||||
|
||||
public static float AngleDegrees(float2 from, float2 to)
|
||||
{
|
||||
return AngleRadians(from, to) * Rad2Deg;
|
||||
}
|
||||
|
||||
public static float AngleDegrees(float3 from, float3 to)
|
||||
{
|
||||
return AngleRadians(from, to) * Rad2Deg;
|
||||
}
|
||||
|
||||
public static float AngleDegrees(unityfloat2 from, unityfloat2 to)
|
||||
{
|
||||
return AngleDegrees(ToCult(from), ToCult(to));
|
||||
}
|
||||
|
||||
public static float AngleDegrees(unityfloat3 from, unityfloat3 to)
|
||||
{
|
||||
return AngleDegrees(ToCult(from), ToCult(to));
|
||||
}
|
||||
|
||||
public static float AngleRadians(float2 from, float2 to)
|
||||
{
|
||||
var denom = sqrt(lengthsq(from) * lengthsq(to));
|
||||
if (denom <= 0.000001f)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return acos(clamp(dot(from, to) / denom, -1.0f, 1.0f));
|
||||
}
|
||||
|
||||
public static float AngleRadians(float3 from, float3 to)
|
||||
{
|
||||
var denom = sqrt(lengthsq(from) * lengthsq(to));
|
||||
if (denom <= 0.000001f)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return acos(clamp(dot(from, to) / denom, -1.0f, 1.0f));
|
||||
}
|
||||
|
||||
public static float AngleRadians(unityfloat2 from, unityfloat2 to)
|
||||
{
|
||||
return AngleRadians(ToCult(from), ToCult(to));
|
||||
}
|
||||
|
||||
public static float AngleRadians(unityfloat3 from, unityfloat3 to)
|
||||
{
|
||||
return AngleRadians(ToCult(from), ToCult(to));
|
||||
}
|
||||
|
||||
public static float Decay(float source, float lambda, float dt)
|
||||
{
|
||||
return source * exp(-lambda * dt);
|
||||
|
|
@ -18,10 +83,20 @@ public static class AetheriaMath
|
|||
return source * exp(-lambda * dt);
|
||||
}
|
||||
|
||||
public static unityfloat3 Decay(unityfloat3 source, float lambda, float dt)
|
||||
{
|
||||
return ToUnity(Decay(ToCult(source), lambda, dt));
|
||||
}
|
||||
|
||||
public static float3 Decay(float3 source, float3 lambda, float dt)
|
||||
{
|
||||
return source * exp(-lambda * dt);
|
||||
}
|
||||
|
||||
public static unityfloat3 Decay(unityfloat3 source, unityfloat3 lambda, float dt)
|
||||
{
|
||||
return ToUnity(Decay(ToCult(source), ToCult(lambda), dt));
|
||||
}
|
||||
|
||||
public static float Damp(float a, float b, float lambda, float dt)
|
||||
{
|
||||
|
|
@ -32,11 +107,21 @@ public static class AetheriaMath
|
|||
{
|
||||
return lerp(a, b, 1 - exp(-lambda * dt));
|
||||
}
|
||||
|
||||
public static unityfloat2 Damp(unityfloat2 a, unityfloat2 b, float lambda, float dt)
|
||||
{
|
||||
return ToUnity(Damp(ToCult(a), ToCult(b), lambda, dt));
|
||||
}
|
||||
|
||||
public static float3 Damp(float3 a, float3 b, float lambda, float dt)
|
||||
{
|
||||
return lerp(a, b, 1 - exp(-lambda * dt));
|
||||
}
|
||||
|
||||
public static unityfloat3 Damp(unityfloat3 a, unityfloat3 b, float lambda, float dt)
|
||||
{
|
||||
return ToUnity(Damp(ToCult(a), ToCult(b), lambda, dt));
|
||||
}
|
||||
|
||||
public static double Damp(double a, double b, double lambda, double dt)
|
||||
{
|
||||
|
|
@ -72,6 +157,22 @@ public static class AetheriaMath
|
|||
);
|
||||
return targetPosition + t*targetRelativeVelocity;
|
||||
}
|
||||
|
||||
public static unityfloat3 FirstOrderIntercept
|
||||
(
|
||||
unityfloat3 shooterPosition,
|
||||
unityfloat3 shooterVelocity,
|
||||
float shotSpeed,
|
||||
unityfloat3 targetPosition,
|
||||
unityfloat3 targetVelocity
|
||||
) {
|
||||
return ToUnity(FirstOrderIntercept(
|
||||
ToCult(shooterPosition),
|
||||
ToCult(shooterVelocity),
|
||||
shotSpeed,
|
||||
ToCult(targetPosition),
|
||||
ToCult(targetVelocity)));
|
||||
}
|
||||
|
||||
//first-order intercept using relative target position
|
||||
public static float FirstOrderInterceptTime
|
||||
|
|
@ -112,6 +213,15 @@ public static class AetheriaMath
|
|||
else //determinant = 0; one intercept path, pretty much never happens
|
||||
return max(-b/(2f*a), 0f); //don't shoot back in time
|
||||
}
|
||||
|
||||
public static float FirstOrderInterceptTime
|
||||
(
|
||||
float shotSpeed,
|
||||
unityfloat3 targetRelativePosition,
|
||||
unityfloat3 targetRelativeVelocity
|
||||
) {
|
||||
return FirstOrderInterceptTime(shotSpeed, ToCult(targetRelativePosition), ToCult(targetRelativeVelocity));
|
||||
}
|
||||
|
||||
// http://csharphelper.com/blog/2016/09/find-the-shortest-distance-between-a-point-and-a-line-segment-in-c/
|
||||
// Calculate the distance between
|
||||
|
|
@ -240,4 +350,38 @@ public static class AetheriaMath
|
|||
x = saturate(x);
|
||||
return x * x * x * (x * (x * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
public static float2 Rotate(float2 value, float radians)
|
||||
{
|
||||
var s = (float)System.Math.Sin(radians);
|
||||
var c = (float)System.Math.Cos(radians);
|
||||
return float2(value.x * c - value.y * s, value.x * s + value.y * c);
|
||||
}
|
||||
|
||||
public static float2 Rotate(float2 value, ItemRotation rotation)
|
||||
{
|
||||
switch (rotation)
|
||||
{
|
||||
case ItemRotation.None:
|
||||
return value;
|
||||
case ItemRotation.CounterClockwise:
|
||||
return float2(-value.y, value.x);
|
||||
case ItemRotation.Reversed:
|
||||
return float2(-value.x, -value.y);
|
||||
case ItemRotation.Clockwise:
|
||||
return float2(value.y, -value.x);
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException(nameof(rotation), rotation, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static unityfloat2 Rotate(unityfloat2 value, float radians)
|
||||
{
|
||||
return ToUnity(Rotate(ToCult(value), radians));
|
||||
}
|
||||
|
||||
public static unityfloat2 Rotate(unityfloat2 value, ItemRotation rotation)
|
||||
{
|
||||
return ToUnity(Rotate(ToCult(value), rotation));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using cfloat3 = CultMath.float3;
|
||||
|
||||
public interface INamedEntry
|
||||
{
|
||||
|
|
@ -26,5 +26,5 @@ public class AetheriaRuntimeItemReference
|
|||
|
||||
public interface ITintInspector
|
||||
{
|
||||
float3 TintColor { get; }
|
||||
cfloat3 TintColor { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniRx;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using static CultMath.math;
|
||||
using cfloat2 = CultMath.float2;
|
||||
|
||||
public class Agent
|
||||
{
|
||||
|
|
@ -55,26 +54,27 @@ public class Agent
|
|||
}
|
||||
}
|
||||
|
||||
public void Accelerate(float2 targetVelocity, bool noTurn = false)
|
||||
public void Accelerate(cfloat2 targetVelocity, bool noTurn = false)
|
||||
{
|
||||
var deltaV = targetVelocity - Ship.Velocity;
|
||||
var deltaV = targetVelocity - Ship.CultVelocity;
|
||||
var deltaVMag = length(deltaV);
|
||||
var deltaVDirection = normalize(deltaV);
|
||||
// If Delta V is above the threshold, direct the ship towards the delta and use only main thrusters
|
||||
if (!noTurn && deltaVMag > FORWARD_DELTA_THRESHOLD)
|
||||
{
|
||||
Ship.LookDirection = float3(deltaVDirection.x, 0, deltaVDirection.y);
|
||||
Ship.MovementDirection = float2(0, pow(dot(Ship.Direction, deltaVDirection), 2));
|
||||
Ship.CultLookDirectionXZ = deltaVDirection;
|
||||
Ship.MovementDirection = new cfloat2(0, pow(dot(Ship.CultDirection, deltaVDirection), 2));
|
||||
}
|
||||
// If Delta V is low, direct the ship towards the target and use all thrusters
|
||||
else if(deltaVMag > THRUST_DELTA_THRESHOLD)
|
||||
{
|
||||
var right = Ship.Direction.Rotate(ItemRotation.Clockwise);
|
||||
Ship.MovementDirection = float2(dot(right, deltaVDirection), dot(Ship.Direction, deltaVDirection));
|
||||
var shipDirection = Ship.CultDirection;
|
||||
var right = AetheriaMath.Rotate(shipDirection, ItemRotation.Clockwise);
|
||||
Ship.MovementDirection = new cfloat2(dot(right, deltaVDirection), dot(shipDirection, deltaVDirection));
|
||||
}
|
||||
else
|
||||
{
|
||||
Ship.MovementDirection = float2.zero;
|
||||
Ship.MovementDirection = cfloat2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniRx;
|
||||
using static Unity.Mathematics.math;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using float3 = Unity.Mathematics.float3;
|
||||
using float2x2 = Unity.Mathematics.float2x2;
|
||||
using static CultMath.math;
|
||||
using cfloat3 = CultMath.float3;
|
||||
|
||||
public class CombatState : BaseState
|
||||
{
|
||||
|
|
@ -13,7 +11,7 @@ public class CombatState : BaseState
|
|||
private float _optimumRange;
|
||||
private readonly List<(int index, float dps)> _availableGroups = new List<(int index, float dps)>();
|
||||
private readonly List<LockWeapon> _availableLockingWeapons = new List<LockWeapon>();
|
||||
|
||||
|
||||
public CombatState(Agent agent) : base(agent)
|
||||
{
|
||||
SampleDps();
|
||||
|
|
@ -31,7 +29,9 @@ public class CombatState : BaseState
|
|||
_availableGroups.Clear();
|
||||
_availableLockingWeapons.Clear();
|
||||
|
||||
var toTarget = target.Position - _agent.Ship.Position;
|
||||
var shipPosition = _agent.Ship.CultPosition;
|
||||
var targetPosition = target.CultPosition;
|
||||
var toTarget = targetPosition - shipPosition;
|
||||
var targetDistance = length(toTarget);
|
||||
for (var i = 0; i < _agent.Ship.WeaponGroups.Length; i++)
|
||||
{
|
||||
|
|
@ -66,16 +66,17 @@ public class CombatState : BaseState
|
|||
}
|
||||
}
|
||||
|
||||
var targetRight = target.Direction.Rotate(ItemRotation.Clockwise);
|
||||
var targetDirection = target.CultDirection;
|
||||
var targetRight = AetheriaMath.Rotate(targetDirection, ItemRotation.Clockwise);
|
||||
var optimumRangeDelta = abs(_optimumRange - targetDistance);
|
||||
var directionToTarget = normalize(toTarget.xz);
|
||||
var targetPortAlignment = dot(targetRight, directionToTarget);
|
||||
var targetForeAlignment = dot(-target.Direction, directionToTarget);
|
||||
var targetForeAlignment = dot(-targetDirection, directionToTarget);
|
||||
var forwardness = saturate(optimumRangeDelta / _agent.Settings.AgentMaxForwardDistance) * _agent.Settings.AgentForwardLerp;
|
||||
if (targetForeAlignment > 0) forwardness = lerp(forwardness, 1, pow(targetPortAlignment, 2));
|
||||
|
||||
var movementDirection = normalize(lerp(
|
||||
directionToTarget.Rotate(targetPortAlignment > 0 ? ItemRotation.Clockwise : ItemRotation.CounterClockwise),
|
||||
AetheriaMath.Rotate(directionToTarget, targetPortAlignment > 0 ? ItemRotation.Clockwise : ItemRotation.CounterClockwise),
|
||||
targetDistance > _optimumRange ? directionToTarget : -directionToTarget, forwardness));
|
||||
|
||||
if (selectedGroup >= 0)
|
||||
|
|
@ -84,17 +85,16 @@ public class CombatState : BaseState
|
|||
if(testWeapon.Velocity > 1)
|
||||
{
|
||||
var targetHull = _agent.ItemManager.GetRuntimeItem(target.Hull);
|
||||
var targetVelocity = float3(target.Velocity.x, 0, target.Velocity.y);
|
||||
var shipVelocity = float3(_agent.Ship.Velocity.x, 0, _agent.Ship.Velocity.y);
|
||||
var targetVelocity = new cfloat3(target.CultVelocity.x, 0, target.CultVelocity.y);
|
||||
var predictedPosition = AetheriaMath.FirstOrderIntercept(
|
||||
_agent.Ship.Position,
|
||||
float3.zero,
|
||||
shipPosition,
|
||||
cfloat3.zero,
|
||||
testWeapon.Velocity,
|
||||
target.Position,
|
||||
targetPosition,
|
||||
targetVelocity
|
||||
);
|
||||
predictedPosition.y = _agent.Ship.Zone.GetHeight(predictedPosition.xz) + (float)(targetHull?.HullGridOffset ?? 0);
|
||||
toTarget = normalize(predictedPosition - _agent.Ship.Position);
|
||||
toTarget = normalize(predictedPosition - shipPosition);
|
||||
}
|
||||
|
||||
var shouldFire = dot(
|
||||
|
|
@ -108,7 +108,7 @@ public class CombatState : BaseState
|
|||
weapon.Deactivate();
|
||||
}
|
||||
}
|
||||
_agent.Ship.LookDirection = toTarget;
|
||||
_agent.Ship.CultLookDirection = toTarget;
|
||||
_agent.Accelerate(movementDirection * _agent.TopSpeed, true);//selectedGroup >= 0);
|
||||
|
||||
// Fire charged guns!
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using static CultMath.math;
|
||||
using cfloat2 = CultMath.float2;
|
||||
|
||||
public abstract class MatchVelocityState : BaseState
|
||||
{
|
||||
protected abstract float2 TargetVelocity { get; }
|
||||
protected abstract cfloat2 TargetVelocity { get; }
|
||||
|
||||
protected MatchVelocityState(Agent agent) : base(agent) { }
|
||||
|
||||
|
|
@ -13,12 +12,12 @@ public abstract class MatchVelocityState : BaseState
|
|||
_agent.Accelerate(TargetVelocity);
|
||||
}
|
||||
|
||||
public float2 MatchDistanceTime
|
||||
public cfloat2 MatchDistanceTime
|
||||
{
|
||||
get
|
||||
{
|
||||
var velocity = length(_agent.Ship.Velocity);
|
||||
var deltaV = TargetVelocity - _agent.Ship.Velocity;
|
||||
var velocity = length(_agent.Ship.CultVelocity);
|
||||
var deltaV = TargetVelocity - _agent.Ship.CultVelocity;
|
||||
|
||||
var stoppingTime = length(deltaV) / (_agent.Ship.ForwardThrust / _agent.Ship.Mass);
|
||||
var stoppingDistance = stoppingTime * (velocity / 2);
|
||||
|
|
@ -26,7 +25,7 @@ public abstract class MatchVelocityState : BaseState
|
|||
var turnaroundTime = _agent.Ship.TurnTime(deltaV);
|
||||
var turnaroundDistance = turnaroundTime * velocity;
|
||||
|
||||
return float2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime);
|
||||
return new cfloat2(stoppingDistance + turnaroundDistance, stoppingTime + turnaroundTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using static CultMath.math;
|
||||
using cfloat2 = CultMath.float2;
|
||||
|
||||
public abstract class MoveToState : BaseState
|
||||
{
|
||||
private VelocityLimit _velocityLimit;
|
||||
protected abstract float2 TargetPosition { get; }
|
||||
protected abstract cfloat2 TargetPosition { get; }
|
||||
public float Distance { get; private set; }
|
||||
|
||||
protected MoveToState(Agent agent) : base(agent)
|
||||
|
|
@ -16,13 +15,13 @@ public abstract class MoveToState : BaseState
|
|||
|
||||
public override void Update(float delta)
|
||||
{
|
||||
var diff = TargetPosition - _agent.Ship.Position.xz;
|
||||
var diff = TargetPosition - _agent.Ship.CultPositionXZ;
|
||||
var dir = normalize(diff);
|
||||
Distance = length(diff);
|
||||
|
||||
// We want to go top speed in the direction of our target
|
||||
var desiredVelocity = dir * _agent.TopSpeed;
|
||||
_agent.Ship.LookDirection = float3(dir.x, 0, dir.y);
|
||||
_agent.Ship.CultLookDirectionXZ = dir;
|
||||
_agent.Accelerate(desiredVelocity);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +29,7 @@ public abstract class MoveToState : BaseState
|
|||
public class MoveToEntityState : MoveToState
|
||||
{
|
||||
public Entity TargetEntity { get; set; }
|
||||
protected override float2 TargetPosition => TargetEntity?.Position.xz ?? float2.zero;
|
||||
protected override cfloat2 TargetPosition => TargetEntity != null ? TargetEntity.CultPositionXZ : cfloat2.zero;
|
||||
|
||||
public MoveToEntityState(Agent agent) : base(agent) { }
|
||||
}
|
||||
|
|
@ -40,5 +39,5 @@ public class MoveToOrbitState : MoveToState
|
|||
public string OrbitKey { get; set; } = "";
|
||||
public MoveToOrbitState(Agent agent) : base(agent) { }
|
||||
|
||||
protected override float2 TargetPosition => _agent.Ship.Zone.GetOrbitPosition(OrbitKey);
|
||||
protected override cfloat2 TargetPosition => AetheriaMath.ToCult(_agent.Ship.Zone.GetOrbitPosition(OrbitKey));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
using cfloat2 = CultMath.float2;
|
||||
using cfloat3 = CultMath.float3;
|
||||
|
||||
public class AetherDrive : Behavior
|
||||
{
|
||||
private readonly float3 _rotorDiameter;
|
||||
private readonly float3 _rotorMass;
|
||||
private readonly cfloat3 _rotorDiameter;
|
||||
private readonly cfloat3 _rotorMass;
|
||||
private readonly PerformanceStat _maximumRpm;
|
||||
private readonly float3 _couplingLambda;
|
||||
private readonly cfloat3 _couplingLambda;
|
||||
private readonly PerformanceStat _lambdaMultiplier;
|
||||
private readonly PerformanceStat _couplingEfficiency;
|
||||
private readonly PerformanceStat _torque;
|
||||
|
|
@ -19,15 +20,15 @@ public class AetherDrive : Behavior
|
|||
private readonly PerformanceStat _passiveCoupling;
|
||||
private readonly uint _rpmAudioParameter;
|
||||
private readonly uint _torqueRatioAudioParameter;
|
||||
private float3 _axis;
|
||||
private cfloat3 _axis;
|
||||
|
||||
public float3 Thrust { get; private set; }
|
||||
public float3 Rpm { get; private set; }
|
||||
public cfloat3 Thrust { get; private set; }
|
||||
public cfloat3 Rpm { get; private set; }
|
||||
public float MaximumRpm { get; private set; }
|
||||
public float2 ThrustDirection { get; private set; }
|
||||
public cfloat2 ThrustDirection { get; private set; }
|
||||
public string Particles { get; }
|
||||
|
||||
public float3 Axis
|
||||
public cfloat3 Axis
|
||||
{
|
||||
get => _axis;
|
||||
set => _axis = clamp(value, -1, 1);
|
||||
|
|
@ -83,16 +84,17 @@ public class AetherDrive : Behavior
|
|||
{
|
||||
var rotorSpeed = Rpm * _rotorDiameter / 100;
|
||||
|
||||
var forward = normalize(Entity.Direction);
|
||||
var right = forward.Rotate(ItemRotation.Clockwise);
|
||||
var forward = normalize(Entity.CultDirection);
|
||||
var right = AetheriaMath.Rotate(Entity.CultDirection, ItemRotation.Clockwise);
|
||||
var velocity = Entity.CultVelocity;
|
||||
|
||||
var speed = float2(dot(Entity.Velocity, forward), dot(Entity.Velocity, right));
|
||||
var speed = float2(dot(velocity, forward), dot(velocity, right));
|
||||
var couplingEfficiency = Evaluate(_couplingEfficiency);
|
||||
var efficiency = float3(saturate(1 - speed / max(rotorSpeed.xy, 1) * sign(_axis.xy)) * couplingEfficiency, 1);
|
||||
|
||||
Thrust = (Rpm - AetheriaMath.Decay(Rpm, _couplingLambda, dt)) * _rotorMass * efficiency;
|
||||
|
||||
var couplingLambda = _couplingLambda * Item.Evaluate(_lambdaMultiplier) * max(abs(_axis), Evaluate(_passiveCoupling));
|
||||
var couplingLambda = _couplingLambda * Evaluate(_lambdaMultiplier) * max(abs(_axis), Evaluate(_passiveCoupling));
|
||||
var previousRpm = Rpm;
|
||||
Rpm = AetheriaMath.Decay(Rpm, couplingLambda, dt);
|
||||
var rpmLoss = previousRpm - Rpm;
|
||||
|
|
@ -102,13 +104,14 @@ public class AetherDrive : Behavior
|
|||
AddHeat((heat.x + heat.y + heat.z)*ItemManager.GameplaySettings.AetherHeatMultiplier);
|
||||
|
||||
ThrustDirection = forward * (_axis.x * force.x / Entity.Mass) + right * (_axis.y * force.y / Entity.Mass);
|
||||
Entity.Velocity += ThrustDirection;
|
||||
Entity.CultVelocity += ThrustDirection;
|
||||
|
||||
Entity.Direction = mul(Entity.Direction,
|
||||
Unity.Mathematics.float2x2.Rotate(force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass));
|
||||
Entity.CultDirection = AetheriaMath.Rotate(
|
||||
Entity.CultDirection,
|
||||
force.z * _axis.z * ItemManager.GameplaySettings.AetherTorqueMultiplier / Entity.Mass);
|
||||
|
||||
if(float.IsNaN(Entity.Velocity.x))
|
||||
ItemManager.Log("FUCK FUCK FUCK FUCK");
|
||||
if(float.IsNaN(Entity.CultVelocity.x))
|
||||
ItemManager.Log("AetherDrive produced NaN velocity.");
|
||||
|
||||
MaximumRpm = Evaluate(_maximumRpm);
|
||||
var torqueProfile = float3(
|
||||
|
|
@ -136,11 +139,11 @@ public class AetherDrive : Behavior
|
|||
}
|
||||
|
||||
public void RestoreRuntimeState(
|
||||
float3 axis,
|
||||
float3 thrust,
|
||||
float3 rpm,
|
||||
cfloat3 axis,
|
||||
cfloat3 thrust,
|
||||
cfloat3 rpm,
|
||||
float maximumRpm,
|
||||
float2 thrustDirection)
|
||||
cfloat2 thrustDirection)
|
||||
{
|
||||
Axis = axis;
|
||||
Thrust = thrust;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,3 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class AutoWeapon : InstantWeapon
|
||||
{
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static Unity.Mathematics.noise;
|
||||
using static CultMath.math;
|
||||
using cfloat3 = CultMath.float3;
|
||||
using cfloat4 = CultMath.float4;
|
||||
|
||||
public abstract class Behavior
|
||||
{
|
||||
|
|
@ -23,7 +23,7 @@ public abstract class Behavior
|
|||
protected Entity Entity => Item?.Entity ?? Consumable.Entity;
|
||||
public float Temperature => Item?.Temperature ?? Consumable.Entity.MaxTemp;
|
||||
|
||||
public float3 Direction
|
||||
public cfloat3 Direction
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -36,12 +36,13 @@ public abstract class Behavior
|
|||
}
|
||||
else
|
||||
{
|
||||
var itemDirection = Entity.Direction.Rotate(Item.EquippableItem.Rotation);
|
||||
return float3(itemDirection.x, 0, itemDirection.y);
|
||||
var itemDirection = AetheriaMath.Rotate(Entity.CultDirection, Item.EquippableItem.Rotation);
|
||||
return new cfloat3(itemDirection.x, 0, itemDirection.y);
|
||||
}
|
||||
}
|
||||
|
||||
return float3(Entity.Direction.x, 0, Entity.Direction.y);
|
||||
var entityDirection = Entity.CultDirection;
|
||||
return new cfloat3(entityDirection.x, 0, entityDirection.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +66,18 @@ public abstract class Behavior
|
|||
|
||||
public float Evaluate(PerformanceStat stat) => Item?.Evaluate(stat) ?? Consumable.Evaluate(stat);
|
||||
|
||||
public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value) =>
|
||||
Item?.Evaluate(stat, condition, value) ?? Consumable.Evaluate(stat, condition, value);
|
||||
|
||||
public float Evaluate(
|
||||
PerformanceStat stat,
|
||||
StatConditionMask firstCondition,
|
||||
float firstValue,
|
||||
StatConditionMask secondCondition,
|
||||
float secondValue) =>
|
||||
Item?.Evaluate(stat, firstCondition, firstValue, secondCondition, secondValue) ??
|
||||
Consumable.Evaluate(stat, firstCondition, firstValue, secondCondition, secondValue);
|
||||
|
||||
public bool TryGetPerformanceStat(string statName, out PerformanceStat stat)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(statName) &&
|
||||
|
|
@ -82,6 +95,7 @@ public abstract class Behavior
|
|||
if (!string.IsNullOrWhiteSpace(statName) && stat != null)
|
||||
{
|
||||
_performanceStats[statName] = stat;
|
||||
Item?.RegisterPerformanceStat(Kind, statName, stat);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +222,7 @@ public sealed class RuntimeBehaviorDefinition
|
|||
return _fields.TryGetValue(key, out var value) ? checked((uint)value.NumberValue) : fallback;
|
||||
}
|
||||
|
||||
public float3 Float3(int key, float3 fallback = default)
|
||||
public cfloat3 Float3(int key, cfloat3 fallback = default)
|
||||
{
|
||||
return _fields.TryGetValue(key, out var value) ? ToFloat3(value) : fallback;
|
||||
}
|
||||
|
|
@ -228,7 +242,9 @@ public sealed class RuntimeBehaviorDefinition
|
|||
|
||||
public PerformanceStat PerformanceStat(int key, PerformanceStat fallback)
|
||||
{
|
||||
return _fields.TryGetValue(key, out var value) ? ToPerformanceStat(value) : fallback;
|
||||
return _fields.TryGetValue(key, out var value)
|
||||
? AetheriaRuntimeBehaviorValueReader.ReadPerformanceStat(value)
|
||||
: fallback;
|
||||
}
|
||||
|
||||
public BezierCurve BezierCurve(int key, BezierCurve fallback)
|
||||
|
|
@ -238,15 +254,12 @@ public sealed class RuntimeBehaviorDefinition
|
|||
return fallback;
|
||||
}
|
||||
|
||||
return new BezierCurve
|
||||
{
|
||||
Keys = value.Children.Count > 0
|
||||
? value.Children[0].Children.Select(ToFloat4).ToArray()
|
||||
: Array.Empty<float4>()
|
||||
};
|
||||
return global::BezierCurve.FromKeys(value.Children.Count > 0
|
||||
? value.Children[0].Children.Select(ToBezierKey)
|
||||
: Enumerable.Empty<(float, float, float, float)>());
|
||||
}
|
||||
|
||||
public float4[] Float4Array(int key, float4[] fallback)
|
||||
public cfloat4[] Float4Array(int key, cfloat4[] fallback)
|
||||
{
|
||||
return _fields.TryGetValue(key, out var value)
|
||||
? value.Children.Select(ToFloat4).ToArray()
|
||||
|
|
@ -260,49 +273,29 @@ public sealed class RuntimeBehaviorDefinition
|
|||
return fallback;
|
||||
}
|
||||
|
||||
return new StatReference
|
||||
{
|
||||
Target = ChildString(value, 1),
|
||||
Stat = ChildString(value, 2)
|
||||
};
|
||||
return AetheriaRuntimeBehaviorValueReader.ReadStatReference(value);
|
||||
}
|
||||
|
||||
public T Enum<T>(int key, T fallback) where T : struct
|
||||
{
|
||||
if (!_fields.TryGetValue(key, out var value))
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value.StringValue) &&
|
||||
System.Enum.TryParse(value.StringValue, true, out T parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return (T)System.Enum.ToObject(typeof(T), checked((int)value.NumberValue));
|
||||
return _fields.TryGetValue(key, out var value)
|
||||
? AetheriaRuntimeBehaviorValueReader.ReadEnum(value, fallback)
|
||||
: fallback;
|
||||
}
|
||||
|
||||
private static PerformanceStat ToPerformanceStat(AetheriaRuntimeBehaviorValue value)
|
||||
private static cfloat4 ToFloat4(AetheriaRuntimeBehaviorValue value)
|
||||
{
|
||||
return new PerformanceStat
|
||||
{
|
||||
Min = ChildFloat(value, 0),
|
||||
Max = ChildFloat(value, 1),
|
||||
HeatExponentMultiplier = ChildFloat(value, 2),
|
||||
DurabilityExponentMultiplier = ChildFloat(value, 3),
|
||||
QualityExponent = ChildFloat(value, 4)
|
||||
};
|
||||
return new cfloat4(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3));
|
||||
}
|
||||
|
||||
private static float4 ToFloat4(AetheriaRuntimeBehaviorValue value)
|
||||
private static (float, float, float, float) ToBezierKey(AetheriaRuntimeBehaviorValue value)
|
||||
{
|
||||
return new float4(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3));
|
||||
return (ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2), ChildFloat(value, 3));
|
||||
}
|
||||
|
||||
private static float3 ToFloat3(AetheriaRuntimeBehaviorValue value)
|
||||
private static cfloat3 ToFloat3(AetheriaRuntimeBehaviorValue value)
|
||||
{
|
||||
return new float3(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2));
|
||||
return new cfloat3(ChildFloat(value, 0), ChildFloat(value, 1), ChildFloat(value, 2));
|
||||
}
|
||||
|
||||
private static float ChildFloat(AetheriaRuntimeBehaviorValue value, int index)
|
||||
|
|
@ -314,4 +307,9 @@ public sealed class RuntimeBehaviorDefinition
|
|||
{
|
||||
return value.Children.Count > index ? value.Children[index].StringValue ?? "" : "";
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeBehaviorValue ChildValue(AetheriaRuntimeBehaviorValue value, int index)
|
||||
{
|
||||
return value != null && value.Children.Count > index ? value.Children[index] : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class Capacitor : Behavior
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class ChargedWeapon : InstantWeapon
|
||||
{
|
||||
|
|
@ -30,9 +29,9 @@ public class ChargedWeapon : InstantWeapon
|
|||
public override float DamagePerSecond => Damage * _chargeFiringDamageMultiplier / (Cooldown + ChargeTime);
|
||||
public override float RangeDamagePerSecond(float range)
|
||||
{
|
||||
return Damage *
|
||||
_chargeFiringDamageMultiplier *
|
||||
DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) /
|
||||
return EvaluateRangeDamage(range, StatConditionMask.Charge, saturate(_charge)) *
|
||||
lerp(1, _chargeFiringDamageMultiplier, saturate(_charge)) *
|
||||
_chargeFiringDamageMultiplier /
|
||||
(Cooldown + ChargeTime);
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +105,8 @@ public class ChargedWeapon : InstantWeapon
|
|||
ChargeTime = Evaluate(_chargeTime);
|
||||
ChargeEnergy = Evaluate(_chargeEnergy);
|
||||
ChargeHeat = Evaluate(_chargeHeat);
|
||||
Damage *= lerp(1, _chargeFiringDamageMultiplier, saturate(_charge));
|
||||
Damage = EvaluateDamage(StatConditionMask.Charge, saturate(_charge)) *
|
||||
lerp(1, _chargeFiringDamageMultiplier, saturate(_charge));
|
||||
Heat *= lerp(1, _chargeFiringHeatMultiplier, saturate(_charge));
|
||||
Spread *= lerp(1, _chargeFiringSpreadMultiplier, saturate(_charge));
|
||||
BurstCount *= lerp(1, _chargeFiringBurstCountMultiplier, saturate(_charge));
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class Cockpit : Behavior
|
||||
{
|
||||
public Cockpit(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
|
||||
{
|
||||
|
|
@ -30,7 +29,7 @@ public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
|
|||
public override float DamagePerSecond => Damage;
|
||||
public override float RangeDamagePerSecond(float range)
|
||||
{
|
||||
return Damage * DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range)));
|
||||
return EvaluateRangeDamage(range);
|
||||
}
|
||||
|
||||
public event Action OnReloadBegin;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class Cooldown : Behavior, IAlwaysUpdatedBehavior, IProgressBehavior
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class HeatStorage : Behavior
|
||||
{
|
||||
public HeatStorage(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
|
||||
{
|
||||
|
|
@ -29,7 +28,7 @@ public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
|
|||
public override float DamagePerSecond => Damage / Cooldown;
|
||||
public override float RangeDamagePerSecond(float range)
|
||||
{
|
||||
return Damage * DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) / Cooldown;
|
||||
return EvaluateRangeDamage(range) / Cooldown;
|
||||
}
|
||||
|
||||
public override int Ammo
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class LockWeapon : InstantWeapon
|
||||
{
|
||||
|
|
@ -82,10 +79,11 @@ public class LockWeapon : InstantWeapon
|
|||
DirectionImpact = Evaluate(_directionImpact);
|
||||
Decay = Evaluate(_decay);
|
||||
|
||||
var degrees = acos(dot(normalize(Entity.Target.Value.Position - Entity.Position), normalize(Entity.LookDirection))) * 57.2958f;
|
||||
if (degrees < LockAngle)
|
||||
var targetDirection = Entity.Target.Value.CultPosition - Entity.CultPosition;
|
||||
var angleDegrees = AetheriaMath.AngleDegrees(targetDirection, Entity.CultLookDirection);
|
||||
if (angleDegrees < LockAngle)
|
||||
{
|
||||
var lerp = 1 - unlerp(0, 90, degrees);
|
||||
var lerp = 1 - unlerp(0, 90, angleDegrees);
|
||||
_lock = saturate(_lock + pow(lerp, DirectionImpact) * dt * LockSpeed * pow(Entity.EntityInfoGathered[Entity.Target.Value], SensorImpact));
|
||||
}
|
||||
else _lock = saturate(_lock - dt * Decay);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class MiningTool : Behavior
|
||||
{
|
||||
|
|
@ -45,10 +42,11 @@ public class MiningTool : Behavior
|
|||
public override bool Execute(float dt)
|
||||
{
|
||||
Range = Evaluate(_range);
|
||||
if (Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt) &&
|
||||
!string.IsNullOrWhiteSpace(AsteroidBeltBodyKey) &&
|
||||
if (!string.IsNullOrWhiteSpace(AsteroidBeltBodyKey) &&
|
||||
Entity.Zone.TryGetAsteroidBelt(AsteroidBeltBodyKey, out var belt) &&
|
||||
Entity.Zone.AsteroidExists(AsteroidBeltBodyKey, Asteroid) &&
|
||||
length(Entity.Position.xz - belt.Transforms[Asteroid].xy) - belt.Transforms[Asteroid].w < Range)
|
||||
Entity.Zone.TryGetCultAsteroidTransform(AsteroidBeltBodyKey, Asteroid, out var asteroidPosition, out var asteroidRadius) &&
|
||||
length(Entity.CultPositionXZ - asteroidPosition) - asteroidRadius < Range)
|
||||
{
|
||||
Entity.Zone.MineAsteroid(
|
||||
Entity,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class Radiator : Behavior, IAlwaysUpdatedBehavior, IInitializableBehavior
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniRx;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class Reactor : Behavior, IOrderedBehavior, IDisposable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class Reflector : Behavior
|
||||
{
|
||||
private readonly PerformanceStat _crossSection;
|
||||
|
|
@ -23,7 +20,7 @@ public class Reflector : Behavior
|
|||
|
||||
public override bool Execute(float dt)
|
||||
{
|
||||
Entity.VisibilitySources[this] = Evaluate(_crossSection) * Entity.Zone.GetLight(Entity.Position.xz);
|
||||
Entity.VisibilitySources[this] = Evaluate(_crossSection) * Entity.Zone.GetLight(Entity.CultPositionXZ);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
|
||||
{
|
||||
|
|
@ -60,9 +58,8 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
|
|||
{
|
||||
if (Entity.Zone.TryGetAsteroidBelt(ScanTargetBodyKey, out var belt))
|
||||
{
|
||||
if (Asteroid > -1 &&
|
||||
belt.ContainsAsteroid(Asteroid) &&
|
||||
length(Entity.Position.xz - belt.Transforms[Asteroid].xy) < Range)
|
||||
if (Entity.Zone.TryGetCultAsteroidTransform(ScanTargetBodyKey, Asteroid, out var asteroidPosition, out _) &&
|
||||
length(Entity.CultPositionXZ - asteroidPosition) < Range)
|
||||
{
|
||||
_scanTime += dt;
|
||||
if (_scanTime > ScanDuration)
|
||||
|
|
@ -75,7 +72,7 @@ public class ResourceScanner : Behavior, IAlwaysUpdatedBehavior
|
|||
}
|
||||
else if (Entity.Zone.TryGetPlanet(ScanTargetBodyKey, out var planet))
|
||||
{
|
||||
if(length(Entity.Position.xz - Entity.Zone.GetOrbitPosition(planet.OrbitKey)) < Range)
|
||||
if(length(Entity.CultPositionXZ - AetheriaMath.ToCult(Entity.Zone.GetOrbitPosition(planet.OrbitKey))) < Range)
|
||||
{
|
||||
_scanTime += dt;
|
||||
if (_scanTime > ScanDuration)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class Sensor : Behavior, IEventBehavior
|
||||
{
|
||||
|
|
@ -117,12 +116,13 @@ public class Sensor : Behavior, IEventBehavior
|
|||
|
||||
// TODO: Handle Active Detection / Visibility From Reflected Radiance
|
||||
var forward = Direction.xz;
|
||||
var entityPosition = Entity.CultPositionXZ;
|
||||
foreach (var entity in Entity.Zone.Entities)
|
||||
{
|
||||
if (entity == Entity) continue;
|
||||
|
||||
var diff = entity.Position.xz - Entity.Position.xz;
|
||||
var angle = acos(dot(forward, normalize(diff)));
|
||||
var diff = entity.CultPositionXZ - entityPosition;
|
||||
var angle = AetheriaMath.AngleRadians(forward, diff);
|
||||
var dist = length(diff);
|
||||
float previous, next;
|
||||
Entity.EntityInfoGathered.TryGetValue(entity, out previous);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class Shield : Behavior, IProgressBehavior
|
||||
{
|
||||
public float Efficiency { get; private set; }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
|
||||
|
|
@ -14,14 +15,14 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
|
|||
private readonly StatModifierType _type;
|
||||
private readonly string _requiredBehaviorKind;
|
||||
|
||||
private PerformanceStat[] _stats;
|
||||
private StatModifierTarget[] _targets;
|
||||
|
||||
private bool _applied;
|
||||
private bool _executed;
|
||||
|
||||
public bool Applied => _applied;
|
||||
public bool Executed => _executed;
|
||||
public int TargetStatCount => _stats?.Length ?? 0;
|
||||
public int TargetStatCount => _targets?.Length ?? 0;
|
||||
|
||||
public StatModifier(RuntimeBehaviorDefinition definition, EquippedItem item) : base(definition, item)
|
||||
{
|
||||
|
|
@ -47,13 +48,19 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
|
|||
|
||||
public void Initialize()
|
||||
{
|
||||
_stats = Entity.Equipment
|
||||
.Where(HasRequiredBehavior)
|
||||
.SelectMany(gear => gear.Behaviors ?? Array.Empty<Behavior>())
|
||||
.Where(behavior => BehaviorKindMatches(behavior.Kind, _targetBehaviorKind))
|
||||
.Select(FindTargetStat)
|
||||
.Where(stat => stat != null)
|
||||
.ToArray();
|
||||
var targets = new List<StatModifierTarget>();
|
||||
foreach (var gear in Entity.Equipment.Where(HasRequiredBehavior))
|
||||
foreach (var behavior in gear.Behaviors ?? Array.Empty<Behavior>())
|
||||
{
|
||||
if (!BehaviorKindMatches(behavior.Kind, _targetBehaviorKind))
|
||||
continue;
|
||||
|
||||
var stat = FindTargetStat(behavior);
|
||||
if (stat != null)
|
||||
targets.Add(new StatModifierTarget(gear, stat));
|
||||
}
|
||||
|
||||
_targets = targets.ToArray();
|
||||
}
|
||||
|
||||
private PerformanceStat FindTargetStat(Behavior behavior)
|
||||
|
|
@ -91,17 +98,21 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
|
|||
private void ApplyModifier()
|
||||
{
|
||||
_applied = true;
|
||||
foreach (var stat in _stats)
|
||||
(_type == StatModifierType.Constant
|
||||
? stat.GetConstantModifiers(Entity)
|
||||
: stat.GetScaleModifiers(Entity))[this] = Evaluate(_modifier);
|
||||
RefreshModifier();
|
||||
}
|
||||
|
||||
private void RemoveModifier()
|
||||
{
|
||||
_applied = false;
|
||||
foreach (var stat in _stats)
|
||||
(_type == StatModifierType.Constant ? stat.GetConstantModifiers(Entity) : stat.GetScaleModifiers(Entity)).Remove(this);
|
||||
foreach (var target in _targets ?? Array.Empty<StatModifierTarget>())
|
||||
target.Item.RemoveStatModifier(target.Stat, this, _type);
|
||||
}
|
||||
|
||||
private void RefreshModifier()
|
||||
{
|
||||
var value = Evaluate(_modifier);
|
||||
foreach (var target in _targets ?? Array.Empty<StatModifierTarget>())
|
||||
target.Item.SetStatModifier(target.Stat, this, _type, value);
|
||||
}
|
||||
|
||||
public override bool Execute(float dt)
|
||||
|
|
@ -120,6 +131,8 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
|
|||
{
|
||||
if(_executed && !_applied)
|
||||
ApplyModifier();
|
||||
else if(_executed && _applied)
|
||||
RefreshModifier();
|
||||
if(!_executed && _applied)
|
||||
RemoveModifier();
|
||||
_executed = false;
|
||||
|
|
@ -127,14 +140,28 @@ public class StatModifier : Behavior, IInitializableBehavior, IDisposable, IAlwa
|
|||
|
||||
public void RestoreRuntimeState(bool applied, bool executed)
|
||||
{
|
||||
if (_stats == null)
|
||||
if (_targets == null)
|
||||
Initialize();
|
||||
if (applied && !_applied)
|
||||
ApplyModifier();
|
||||
if (applied && _applied)
|
||||
RefreshModifier();
|
||||
if (!applied && _applied)
|
||||
RemoveModifier();
|
||||
_executed = executed;
|
||||
}
|
||||
|
||||
private readonly struct StatModifierTarget
|
||||
{
|
||||
public StatModifierTarget(EquippedItem item, PerformanceStat stat)
|
||||
{
|
||||
Item = item;
|
||||
Stat = stat;
|
||||
}
|
||||
|
||||
public EquippedItem Item { get; }
|
||||
public PerformanceStat Stat { get; }
|
||||
}
|
||||
}
|
||||
|
||||
public enum StatModifierType
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
using cfloat2 = CultMath.float2;
|
||||
|
||||
public class Thruster : Behavior, IAnalogBehavior
|
||||
{
|
||||
|
|
@ -40,7 +40,7 @@ public class Thruster : Behavior, IAnalogBehavior
|
|||
var hullCenter = hullShape.CenterOfMass;
|
||||
var itemCenter = hullShape.Inset(itemShape, item.Position, item.EquippableItem.Rotation).CenterOfMass;
|
||||
var toCenter = hullCenter - itemCenter;
|
||||
Torque = -dot(normalize(toCenter), float2(1, 0).Rotate(item.EquippableItem.Rotation));
|
||||
Torque = -dot(normalize(toCenter), AetheriaMath.Rotate(new cfloat2(1, 0), item.EquippableItem.Rotation));
|
||||
Thrust = Evaluate(_thrust);
|
||||
}
|
||||
|
||||
|
|
@ -65,9 +65,10 @@ public class Thruster : Behavior, IAnalogBehavior
|
|||
if(_input > .01f && Entity.TryConsumeEnergy(_input * Evaluate(_energyUsage)))
|
||||
{
|
||||
Thrust = Evaluate(_thrust);
|
||||
Entity.Velocity -= Direction.xz * _input * Thrust / Entity.Mass * dt;
|
||||
Entity.Direction = mul(Entity.Direction,
|
||||
Unity.Mathematics.float2x2.Rotate(_input * Torque * Thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass * dt));
|
||||
Entity.CultVelocity -= Direction.xz * _input * Thrust / Entity.Mass * dt;
|
||||
Entity.CultDirection = AetheriaMath.Rotate(
|
||||
Entity.CultDirection,
|
||||
_input * Torque * Thrust * ItemManager.GameplaySettings.TorqueMultiplier / Entity.Mass * dt);
|
||||
AddHeat(_input * Evaluate(_heat) * dt);
|
||||
var vis = _input * Evaluate(_visibility);
|
||||
if (!Entity.VisibilitySources.TryGetValue(this, out var existingVisibility) || vis > existingVisibility)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniRx;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using float3 = Unity.Mathematics.float3;
|
||||
using static CultMath.math;
|
||||
using cfloat3 = CultMath.float3;
|
||||
|
||||
public class TurretController : Behavior, IInitializableBehavior
|
||||
{
|
||||
private List<Weapon> _weapons = new List<Weapon>();
|
||||
|
|
@ -44,27 +44,29 @@ public class TurretController : Behavior, IInitializableBehavior
|
|||
{
|
||||
if (Entity.Target.Value != null)
|
||||
{
|
||||
var diff = Entity.Target.Value.Position - Entity.Position;
|
||||
var entityPosition = Entity.CultPosition;
|
||||
var targetPosition = Entity.Target.Value.CultPosition;
|
||||
var diff = targetPosition - entityPosition;
|
||||
if (_predictShots)
|
||||
{
|
||||
var targetHull = Entity.ItemManager.GetRuntimeItem(Entity.Target.Value.Hull);
|
||||
var targetVelocity = float3(Entity.Target.Value.Velocity.x, 0, Entity.Target.Value.Velocity.y);
|
||||
var targetVelocity = new cfloat3(Entity.Target.Value.CultVelocity.x, 0, Entity.Target.Value.CultVelocity.y);
|
||||
var predictedPosition = AetheriaMath.FirstOrderIntercept(
|
||||
Entity.Position, float3.zero, _shotSpeed,
|
||||
Entity.Target.Value.Position, targetVelocity
|
||||
entityPosition, cfloat3.zero, _shotSpeed,
|
||||
targetPosition, targetVelocity
|
||||
);
|
||||
predictedPosition.y = Entity.Zone.GetHeight(predictedPosition.xz) + (float)(targetHull?.HullGridOffset ?? 0);
|
||||
Entity.LookDirection = normalize(predictedPosition - Entity.Position);
|
||||
Entity.CultLookDirection = normalize(predictedPosition - entityPosition);
|
||||
}
|
||||
else
|
||||
Entity.LookDirection = normalize(diff);
|
||||
Entity.CultLookDirection = normalize(diff);
|
||||
var dist = length(diff);
|
||||
|
||||
foreach (var x in _weapons)
|
||||
{
|
||||
var fire = dot(
|
||||
x.Direction,
|
||||
Entity.LookDirection) > .99f;
|
||||
Entity.CultLookDirection) > .99f;
|
||||
if (x.EvaluateRange() > dist && fire)
|
||||
{
|
||||
x.Activate();
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class VelocityConversion : Behavior
|
||||
{
|
||||
private readonly PerformanceStat _lambda;
|
||||
|
|
@ -22,7 +22,8 @@ public class VelocityConversion : Behavior
|
|||
|
||||
public override bool Execute(float dt)
|
||||
{
|
||||
Entity.Velocity = AetheriaMath.Damp(Entity.Velocity, Entity.Direction * length(Entity.Velocity), Evaluate(_lambda), dt);
|
||||
var velocity = Entity.CultVelocity;
|
||||
Entity.CultVelocity = AetheriaMath.Damp(velocity, Entity.CultDirection * length(velocity), Evaluate(_lambda), dt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
public class VelocityLimit : Behavior
|
||||
{
|
||||
|
|
@ -28,8 +27,9 @@ public class VelocityLimit : Behavior
|
|||
public override bool Execute(float dt)
|
||||
{
|
||||
Limit = Evaluate(_topSpeed);
|
||||
if (length(Entity.Velocity) > Limit)
|
||||
Entity.Velocity = normalize(Entity.Velocity) * Limit;
|
||||
var velocity = Entity.CultVelocity;
|
||||
if (length(velocity) > Limit)
|
||||
Entity.CultVelocity = normalize(velocity) * Limit;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
using float4 = CultMath.float4;
|
||||
|
||||
public abstract class Weapon : Behavior, IActivatedBehavior
|
||||
{
|
||||
|
|
@ -156,6 +156,25 @@ public abstract class Weapon : Behavior, IActivatedBehavior
|
|||
Velocity = Evaluate(_velocity);
|
||||
}
|
||||
|
||||
protected float EvaluateRangeDamage(float range)
|
||||
{
|
||||
var normalizedRange = saturate(unlerp(MinRange, Range, range));
|
||||
return Evaluate(_damage, StatConditionMask.Range, normalizedRange) *
|
||||
DamageCurve.Evaluate(normalizedRange);
|
||||
}
|
||||
|
||||
protected float EvaluateRangeDamage(float range, StatConditionMask condition, float value)
|
||||
{
|
||||
var normalizedRange = saturate(unlerp(MinRange, Range, range));
|
||||
return Evaluate(_damage, StatConditionMask.Range, normalizedRange, condition, value) *
|
||||
DamageCurve.Evaluate(normalizedRange);
|
||||
}
|
||||
|
||||
protected float EvaluateDamage(StatConditionMask condition, float value)
|
||||
{
|
||||
return Evaluate(_damage, condition, value);
|
||||
}
|
||||
|
||||
public override bool Execute(float dt)
|
||||
{
|
||||
UpdateStats();
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class Wear : Behavior
|
||||
{
|
||||
private readonly bool _perSecond;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
public class BezierCurve
|
||||
{
|
||||
|
|
@ -13,6 +14,16 @@ public class BezierCurve
|
|||
|
||||
private float? _maximum;
|
||||
|
||||
public static BezierCurve FromKeys(IEnumerable<(float time, float value, float inTangent, float outTangent)> keys)
|
||||
{
|
||||
return new BezierCurve
|
||||
{
|
||||
Keys = keys
|
||||
.Select(key => float4(key.time, key.value, key.inTangent, key.outTangent))
|
||||
.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public float Maximum
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
using cfloat3 = CultMath.float3;
|
||||
using cfloat4 = CultMath.float4;
|
||||
|
||||
// Thanks to Ian Taylor at https://www.chilliant.com/rgb2hsv.html
|
||||
public static class ColorMath
|
||||
{
|
||||
public static float3 HueToRgb(in float h)
|
||||
public static cfloat3 HueToRgb(in float h)
|
||||
{
|
||||
float r = abs(h * 6 - 3) - 1;
|
||||
float g = 2 - abs(h * 6 - 2);
|
||||
|
|
@ -18,40 +17,40 @@ public static class ColorMath
|
|||
return saturate(float3(r,g,b));
|
||||
}
|
||||
|
||||
public static float3 HsvToRgb(in float3 hsv)
|
||||
public static cfloat3 HsvToRgb(in cfloat3 hsv)
|
||||
{
|
||||
float3 rgb = HueToRgb(hsv.x);
|
||||
cfloat3 rgb = HueToRgb(hsv.x);
|
||||
return ((rgb - 1) * hsv.y + 1) * hsv.z;
|
||||
}
|
||||
|
||||
public static float3 HslToRgb(in float3 hsl)
|
||||
|
||||
public static cfloat3 HslToRgb(in cfloat3 hsl)
|
||||
{
|
||||
float3 rgb = HueToRgb(hsl.x);
|
||||
cfloat3 rgb = HueToRgb(hsl.x);
|
||||
float c = (1 - abs(2 * hsl.z - 1)) * hsl.y;
|
||||
return (rgb - 0.5f) * c + hsl.z;
|
||||
}
|
||||
|
||||
|
||||
const float Epsilon = 1e-10f;
|
||||
public static float3 RgbToHcv(in float3 rgb)
|
||||
public static cfloat3 RgbToHcv(in cfloat3 rgb)
|
||||
{
|
||||
// Based on work by Sam Hocevar and Emil Persson
|
||||
float4 p = (rgb.y < rgb.z) ? float4(rgb.zy, -1.0f, 2.0f/3.0f) : float4(rgb.zy, 0.0f, -1.0f/3.0f);
|
||||
float4 q = (rgb.x < p.x) ? float4(p.xyw, rgb.x) : float4(rgb.x, p.yzx);
|
||||
cfloat4 p = (rgb.y < rgb.z) ? float4(rgb.zy, -1.0f, 2.0f/3.0f) : float4(rgb.zy, 0.0f, -1.0f/3.0f);
|
||||
cfloat4 q = (rgb.x < p.x) ? float4(p.xyw, rgb.x) : float4(rgb.x, p.yzx);
|
||||
float c = q.x - min(q.w, q.y);
|
||||
float h = abs((q.w - q.y) / (6 * c + Epsilon) + q.z);
|
||||
return float3(h, c, q.x);
|
||||
}
|
||||
|
||||
public static float3 RgbToHsv(in float3 rgb)
|
||||
|
||||
public static cfloat3 RgbToHsv(in cfloat3 rgb)
|
||||
{
|
||||
float3 hcv = RgbToHcv(rgb);
|
||||
cfloat3 hcv = RgbToHcv(rgb);
|
||||
float s = hcv.y / (hcv.z + Epsilon);
|
||||
return float3(hcv.x, s, hcv.z);
|
||||
}
|
||||
|
||||
public static float3 RgbToHsl(in float3 rgb)
|
||||
|
||||
public static cfloat3 RgbToHsl(in cfloat3 rgb)
|
||||
{
|
||||
float3 hcv = RgbToHcv(rgb);
|
||||
cfloat3 hcv = RgbToHcv(rgb);
|
||||
float l = hcv.z - hcv.y * 0.5f;
|
||||
float s = hcv.y / (1 - abs(l * 2 - 1) + Epsilon);
|
||||
return float3(hcv.x, s, l);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using float3 = CultMath.float3;
|
||||
|
||||
public class Faction : INamedEntry
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,10 +7,36 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using UniRx;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using bool2 = CultMath.bool2;
|
||||
using cfloat2 = CultMath.float2;
|
||||
using cfloat3 = CultMath.float3;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using float3 = Unity.Mathematics.float3;
|
||||
using int2 = Unity.Mathematics.int2;
|
||||
|
||||
internal static class AetheriaScalar
|
||||
{
|
||||
public static float Clamp(float value, float min, float max)
|
||||
{
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
public static float Lerp(float from, float to, float t)
|
||||
{
|
||||
return from + (to - from) * t;
|
||||
}
|
||||
|
||||
public static float Unlerp(float from, float to, float value)
|
||||
{
|
||||
return Math.Abs(to - from) <= 0.000001f ? 0 : (value - from) / (to - from);
|
||||
}
|
||||
|
||||
public static float Saturate(float value)
|
||||
{
|
||||
return Clamp(value, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Entity
|
||||
{
|
||||
public Zone Zone;
|
||||
|
|
@ -19,8 +45,38 @@ public abstract class Entity
|
|||
public EquippedItem EquippedHull;
|
||||
|
||||
public float3 Position;
|
||||
public float2 Direction = float2(0,1);
|
||||
public float2 Direction = new float2(0,1);
|
||||
public float2 Velocity;
|
||||
|
||||
public cfloat3 CultPosition
|
||||
{
|
||||
get => AetheriaMath.ToCult(Position);
|
||||
set => Position = AetheriaMath.ToUnity(value);
|
||||
}
|
||||
|
||||
public cfloat2 CultPositionXZ
|
||||
{
|
||||
get => AetheriaMath.ToCultXZ(Position);
|
||||
set => Position.xz = AetheriaMath.ToUnity(value);
|
||||
}
|
||||
|
||||
public float CultPositionY
|
||||
{
|
||||
get => Position.y;
|
||||
set => Position.y = value;
|
||||
}
|
||||
|
||||
public cfloat2 CultDirection
|
||||
{
|
||||
get => AetheriaMath.ToCult(Direction);
|
||||
set => Direction = AetheriaMath.ToUnity(value);
|
||||
}
|
||||
|
||||
public cfloat2 CultVelocity
|
||||
{
|
||||
get => AetheriaMath.ToCult(Velocity);
|
||||
set => Velocity = AetheriaMath.ToUnity(value);
|
||||
}
|
||||
|
||||
public float[,] Temperature;
|
||||
public float[,] NewTemperature;
|
||||
|
|
@ -40,6 +96,18 @@ public abstract class Entity
|
|||
public ReactiveProperty<Entity> Target = new ReactiveProperty<Entity>((Entity)null);
|
||||
|
||||
public float3 LookDirection;
|
||||
|
||||
public cfloat3 CultLookDirection
|
||||
{
|
||||
get => AetheriaMath.ToCult(LookDirection);
|
||||
set => LookDirection = AetheriaMath.ToUnity(value);
|
||||
}
|
||||
|
||||
public cfloat2 CultLookDirectionXZ
|
||||
{
|
||||
get => AetheriaMath.ToCultXZ(LookDirection);
|
||||
set => LookDirection = AetheriaMath.ToUnityXZ(value);
|
||||
}
|
||||
|
||||
public string Name;
|
||||
|
||||
|
|
@ -47,9 +115,9 @@ public abstract class Entity
|
|||
|
||||
public readonly Dictionary<string, float> Messages = new Dictionary<string, float>();
|
||||
public readonly Dictionary<object, float> VisibilitySources = new Dictionary<object, float>();
|
||||
public readonly ReactiveDictionary<Entity, float> EntityInfoGathered = new ReactiveDictionary<Entity, float>();
|
||||
public readonly Dictionary<HardpointData, (float3 position, float3 direction)> HardpointTransforms =
|
||||
new Dictionary<HardpointData, (float3 position, float3 direction)>();
|
||||
public readonly ReactiveDictionary<Entity, float> EntityInfoGathered = new ReactiveDictionary<Entity, float>();
|
||||
public readonly Dictionary<HardpointData, (cfloat3 position, cfloat3 direction)> HardpointTransforms =
|
||||
new Dictionary<HardpointData, (cfloat3 position, cfloat3 direction)>();
|
||||
|
||||
public (List<Weapon> weapons, List<EquippedItem> items)[] WeaponGroups;
|
||||
|
||||
|
|
@ -95,6 +163,11 @@ public abstract class Entity
|
|||
{
|
||||
get => _active;
|
||||
}
|
||||
|
||||
public void RestoreActiveState(bool active)
|
||||
{
|
||||
_active = active;
|
||||
}
|
||||
|
||||
public IEnumerable<Weapon> Weapons
|
||||
{
|
||||
|
|
@ -317,8 +390,8 @@ public abstract class Entity
|
|||
|
||||
public void RestoreThermalExposure(float heatstroke, float hypothermia)
|
||||
{
|
||||
Heatstroke = saturate(heatstroke);
|
||||
Hypothermia = saturate(hypothermia);
|
||||
Heatstroke = AetheriaScalar.Saturate(heatstroke);
|
||||
Hypothermia = AetheriaScalar.Saturate(hypothermia);
|
||||
}
|
||||
|
||||
public void RestoreActiveConsumable(ConsumableItem item, float remainingDuration, float duration)
|
||||
|
|
@ -436,7 +509,7 @@ public abstract class Entity
|
|||
throw new InvalidOperationException($"Unable to map entity {Name}: missing typed hull row for {Hull?.ItemKey}");
|
||||
|
||||
var hullShape = GetHullShape(typedHull);
|
||||
EquippedHull = new EquippedItem(ItemManager, Hull, int2.zero, this);
|
||||
EquippedHull = new EquippedItem(ItemManager, Hull, new int2(0, 0), this);
|
||||
Equipment.Add(EquippedHull);
|
||||
Mass = ItemManager.GetMass(Hull);
|
||||
Temperature = new float[hullShape.Width, hullShape.Height];
|
||||
|
|
@ -546,7 +619,7 @@ public abstract class Entity
|
|||
|
||||
if (itemInstance is SimpleCommodity simpleCommodity)
|
||||
{
|
||||
var targetQuantity = min(simpleCommodity.Quantity, quantity - quantityTransferred);
|
||||
var targetQuantity = Math.Min(simpleCommodity.Quantity, quantity - quantityTransferred);
|
||||
if (!target.CargoBays.Any(c => originInventory.TryTransferItem(c, simpleCommodity, targetQuantity)))
|
||||
{
|
||||
quantityTransferred += targetQuantity - simpleCommodity.Quantity;
|
||||
|
|
@ -714,7 +787,7 @@ public abstract class Entity
|
|||
if (cells == null) return shape;
|
||||
|
||||
foreach (var cell in cells)
|
||||
shape[new int2(cell.X, cell.Y)] = true;
|
||||
shape.SetCell(cell.X, cell.Y, true);
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
|
@ -801,14 +874,14 @@ public abstract class Entity
|
|||
// Don't allow equipping while deployed
|
||||
if (_active)
|
||||
{
|
||||
hullCoord = int2.zero;
|
||||
hullCoord = new int2(0, 0);
|
||||
return false;
|
||||
}
|
||||
var typedItem = ItemManager.GetRuntimeItem(item);
|
||||
var typedHull = ItemManager.GetRuntimeItem(Hull);
|
||||
if (typedItem == null || typedHull == null)
|
||||
{
|
||||
hullCoord = int2.zero;
|
||||
hullCoord = new int2(0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -850,7 +923,7 @@ public abstract class Entity
|
|||
}
|
||||
}
|
||||
|
||||
hullCoord = int2.zero;
|
||||
hullCoord = new int2(0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -989,7 +1062,7 @@ public abstract class Entity
|
|||
{
|
||||
if(cap.Charge > 0.01f)
|
||||
{
|
||||
var chargeRemoved = min(chargeToRemove / chargedCapacitors, cap.Charge);
|
||||
var chargeRemoved = Math.Min(chargeToRemove / chargedCapacitors, cap.Charge);
|
||||
cap.AddCharge(-chargeRemoved);
|
||||
energy -= chargeRemoved;
|
||||
}
|
||||
|
|
@ -1058,9 +1131,9 @@ public abstract class Entity
|
|||
|
||||
public virtual void Update(float delta)
|
||||
{
|
||||
TargetRange = Target.Value == null ? -1 : length(Position - Target.Value.Position);
|
||||
TargetRange = Target.Value == null ? -1 : CultMath.math.length(CultPosition - Target.Value.CultPosition);
|
||||
|
||||
var localSecurityLevel = Zone.GetSecurityLevel(Position.xz);
|
||||
var localSecurityLevel = Zone.GetSecurityLevel(CultPositionXZ);
|
||||
if (CurrentSecurityLevel.Value != localSecurityLevel) CurrentSecurityLevel.Value = localSecurityLevel;
|
||||
|
||||
foreach (var v in VisibilitySources.Keys.ToArray())
|
||||
|
|
@ -1090,9 +1163,9 @@ public abstract class Entity
|
|||
if (cockpitTemp > ItemManager.GameplaySettings.HeatstrokeTemperature)
|
||||
{
|
||||
var previous = Heatstroke;
|
||||
Heatstroke = saturate(
|
||||
Heatstroke = AetheriaScalar.Saturate(
|
||||
Heatstroke +
|
||||
pow(cockpitTemp - ItemManager.GameplaySettings.HeatstrokeTemperature, ItemManager.GameplaySettings.HeatstrokeExponent) *
|
||||
MathF.Pow(cockpitTemp - ItemManager.GameplaySettings.HeatstrokeTemperature, ItemManager.GameplaySettings.HeatstrokeExponent) *
|
||||
ItemManager.GameplaySettings.HeatstrokeMultiplier * delta);
|
||||
if(previous < ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold && Heatstroke > ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold)
|
||||
HeatstrokeRisk.OnNext(Unit.Default);
|
||||
|
|
@ -1104,15 +1177,15 @@ public abstract class Entity
|
|||
}
|
||||
else
|
||||
{
|
||||
Heatstroke = saturate(Heatstroke - ItemManager.GameplaySettings.HeatstrokeRecoverySpeed * delta);
|
||||
Heatstroke = AetheriaScalar.Saturate(Heatstroke - ItemManager.GameplaySettings.HeatstrokeRecoverySpeed * delta);
|
||||
}
|
||||
|
||||
if (cockpitTemp < ItemManager.GameplaySettings.HypothermiaTemperature)
|
||||
{
|
||||
var previous = Hypothermia;
|
||||
Hypothermia = saturate(
|
||||
Hypothermia = AetheriaScalar.Saturate(
|
||||
Hypothermia +
|
||||
pow(ItemManager.GameplaySettings.HypothermiaTemperature - cockpitTemp, ItemManager.GameplaySettings.HypothermiaExponent) *
|
||||
MathF.Pow(ItemManager.GameplaySettings.HypothermiaTemperature - cockpitTemp, ItemManager.GameplaySettings.HypothermiaExponent) *
|
||||
ItemManager.GameplaySettings.HypothermiaMultiplier * delta);
|
||||
if(previous < ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold && Heatstroke > ItemManager.GameplaySettings.SevereHeatstrokeRiskThreshold)
|
||||
HypothermiaRisk.OnNext(Unit.Default);
|
||||
|
|
@ -1124,7 +1197,7 @@ public abstract class Entity
|
|||
}
|
||||
else
|
||||
{
|
||||
Hypothermia = saturate(Hypothermia - ItemManager.GameplaySettings.HypothermiaRecoverySpeed * delta);
|
||||
Hypothermia = AetheriaScalar.Saturate(Hypothermia - ItemManager.GameplaySettings.HypothermiaRecoverySpeed * delta);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1151,10 +1224,10 @@ public abstract class Entity
|
|||
|
||||
if (Parent != null)
|
||||
{
|
||||
Position = Parent.Position;
|
||||
Velocity = Parent.Velocity;
|
||||
CultPosition = Parent.CultPosition;
|
||||
CultVelocity = Parent.CultVelocity;
|
||||
}
|
||||
else Position.y = Zone.GetHeight(Position.xz) + (float)(ItemManager.GetRuntimeItem(Hull)?.HullGridOffset ?? 0);
|
||||
else CultPositionY = Zone.GetHeight(CultPositionXZ) + (float)(ItemManager.GetRuntimeItem(Hull)?.HullGridOffset ?? 0);
|
||||
}
|
||||
|
||||
private void UpdateTemperature(float delta)
|
||||
|
|
@ -1175,7 +1248,7 @@ public abstract class Entity
|
|||
var totalTemp = temp / ItemManager.GameplaySettings.HeatConductionMultiplier;
|
||||
var totalConductivity = 1f / ItemManager.GameplaySettings.HeatConductionMultiplier;
|
||||
|
||||
if (hullShape[int2(v.x - 1, v.y)])
|
||||
if (hullShape.GetCell(v.x - 1, v.y))
|
||||
{
|
||||
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
|
||||
(GearOccupancy[v.x - 1, v.y]?.Conductivity ?? 1) *
|
||||
|
|
@ -1185,7 +1258,7 @@ public abstract class Entity
|
|||
totalTemp += Temperature[v.x - 1, v.y] * conductivity;
|
||||
}
|
||||
|
||||
if (hullShape[int2(v.x + 1, v.y)])
|
||||
if (hullShape.GetCell(v.x + 1, v.y))
|
||||
{
|
||||
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
|
||||
(GearOccupancy[v.x + 1, v.y]?.Conductivity ?? 1) *
|
||||
|
|
@ -1196,7 +1269,7 @@ public abstract class Entity
|
|||
}
|
||||
|
||||
|
||||
if (hullShape[int2(v.x, v.y - 1)])
|
||||
if (hullShape.GetCell(v.x, v.y - 1))
|
||||
{
|
||||
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
|
||||
(GearOccupancy[v.x, v.y - 1]?.Conductivity ?? 1) *
|
||||
|
|
@ -1207,7 +1280,7 @@ public abstract class Entity
|
|||
}
|
||||
|
||||
|
||||
if (hullShape[int2(v.x, v.y + 1)])
|
||||
if (hullShape.GetCell(v.x, v.y + 1))
|
||||
{
|
||||
var conductivity = (GearOccupancy[v.x, v.y]?.Conductivity ?? 1) *
|
||||
(GearOccupancy[v.x, v.y + 1]?.Conductivity ?? 1) *
|
||||
|
|
@ -1223,7 +1296,7 @@ public abstract class Entity
|
|||
// For all cells on the border of the entity, radiate some heat into space, increasing the visibility of the ship
|
||||
if (Parent==null && !hullInterior[v])
|
||||
{
|
||||
var rad = pow(NewTemperature[v.x, v.y], ItemManager.GameplaySettings.HeatRadiationExponent) *
|
||||
var rad = MathF.Pow(NewTemperature[v.x, v.y], ItemManager.GameplaySettings.HeatRadiationExponent) *
|
||||
ItemManager.GameplaySettings.HeatRadiationMultiplier;
|
||||
NewTemperature[v.x, v.y] -= rad * delta;
|
||||
r += rad;
|
||||
|
|
@ -1278,7 +1351,7 @@ public class ConsumableItemEffect
|
|||
_duration = duration.GetValueOrDefault(RuntimeItem?.Duration > 0 ? (float)RuntimeItem.Duration : 0);
|
||||
_effectiveness = CreateEffectivenessCurve(RuntimeItem);
|
||||
RemainingDuration = remainingDuration.HasValue
|
||||
? clamp(remainingDuration.Value, 0, Duration)
|
||||
? AetheriaScalar.Clamp(remainingDuration.Value, 0, Duration)
|
||||
: _duration;
|
||||
|
||||
Behaviors = entity.ItemManager.CreateRuntimeBehaviors(this);
|
||||
|
|
@ -1300,37 +1373,101 @@ public class ConsumableItemEffect
|
|||
|
||||
public float Evaluate(PerformanceStat stat)
|
||||
{
|
||||
if (stat == null)
|
||||
return 0;
|
||||
|
||||
var duration = Math.Max(_duration, float.Epsilon);
|
||||
var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration);
|
||||
var quality = pow(Item.Quality, stat.QualityExponent);
|
||||
|
||||
var result = lerp(stat.Min, stat.Max, effectiveness * quality);
|
||||
var result = stat.EvaluateConsumableBaseline(new StatEvaluationContext(
|
||||
quality: Item.Quality,
|
||||
durability: effectiveness,
|
||||
heat: 1));
|
||||
|
||||
if (float.IsNaN(result))
|
||||
return stat.Min;
|
||||
return result;
|
||||
}
|
||||
|
||||
public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value)
|
||||
{
|
||||
if (stat == null)
|
||||
return 0;
|
||||
|
||||
if ((GetDependencyMask(stat) & condition) == 0)
|
||||
return Evaluate(stat);
|
||||
|
||||
var duration = Math.Max(_duration, float.Epsilon);
|
||||
var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration);
|
||||
var context = new StatEvaluationContext(
|
||||
quality: Item.Quality,
|
||||
durability: effectiveness,
|
||||
heat: 1).WithCondition(condition, value);
|
||||
|
||||
var result = stat.EvaluateRecipeOrLegacy(
|
||||
context,
|
||||
durabilityExponent: 1,
|
||||
thermalExponent: 0,
|
||||
includeHeat: false);
|
||||
|
||||
if (float.IsNaN(result))
|
||||
return stat.Min;
|
||||
return result;
|
||||
}
|
||||
|
||||
public float Evaluate(
|
||||
PerformanceStat stat,
|
||||
StatConditionMask firstCondition,
|
||||
float firstValue,
|
||||
StatConditionMask secondCondition,
|
||||
float secondValue)
|
||||
{
|
||||
if (stat == null)
|
||||
return 0;
|
||||
|
||||
var dependencyMask = GetDependencyMask(stat);
|
||||
if ((dependencyMask & (firstCondition | secondCondition)) == 0)
|
||||
return Evaluate(stat);
|
||||
|
||||
var duration = Math.Max(_duration, float.Epsilon);
|
||||
var effectiveness = _effectiveness.Evaluate((duration - RemainingDuration) / duration);
|
||||
var context = new StatEvaluationContext(
|
||||
quality: Item.Quality,
|
||||
durability: effectiveness,
|
||||
heat: 1)
|
||||
.WithCondition(firstCondition, firstValue)
|
||||
.WithCondition(secondCondition, secondValue);
|
||||
|
||||
var result = stat.EvaluateRecipeOrLegacy(
|
||||
context,
|
||||
durabilityExponent: 1,
|
||||
thermalExponent: 0,
|
||||
includeHeat: false);
|
||||
|
||||
if (float.IsNaN(result))
|
||||
return stat.Min;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static StatConditionMask GetDependencyMask(PerformanceStat stat)
|
||||
{
|
||||
return stat?.GetDependencyMask(includeHeat: false) ?? StatConditionMask.None;
|
||||
}
|
||||
|
||||
private static BezierCurve CreateEffectivenessCurve(AetheriaRuntimeCatalogItem item)
|
||||
{
|
||||
if (item?.EffectivenessCurveKeys != null && item.EffectivenessCurveKeys.Count > 0)
|
||||
{
|
||||
return new BezierCurve
|
||||
{
|
||||
Keys = item.EffectivenessCurveKeys
|
||||
.Select(key => new float4(
|
||||
return BezierCurve.FromKeys(
|
||||
item.EffectivenessCurveKeys
|
||||
.Select(key => (
|
||||
(float)key.Time,
|
||||
(float)key.Value,
|
||||
(float)key.InTangent,
|
||||
(float)key.OutTangent))
|
||||
.ToArray()
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
return new BezierCurve
|
||||
{
|
||||
Keys = new[] { new float4(0, 1, 0, 0), new float4(1, 1, 0, 0) }
|
||||
};
|
||||
return BezierCurve.FromKeys(new[] { (0f, 1f, 0f, 0f), (1f, 1f, 0f, 0f) });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1371,6 +1508,26 @@ public class EquippedItem
|
|||
|
||||
private readonly BezierCurve _thermalPerformanceCurve;
|
||||
private readonly EquippedItemAudioStatBinding[] _audioStats;
|
||||
private readonly Dictionary<PerformanceStat, EquippedItemBaseStatCacheEntry> _baseStatCache =
|
||||
new Dictionary<PerformanceStat, EquippedItemBaseStatCacheEntry>();
|
||||
private readonly Dictionary<PerformanceStat, EquippedItemStatCacheEntry> _statCache =
|
||||
new Dictionary<PerformanceStat, EquippedItemStatCacheEntry>();
|
||||
private int _qualityVersion;
|
||||
private int _durabilityVersion;
|
||||
private int _thermalVersion;
|
||||
private int _chargeVersion;
|
||||
private int _ammoVersion;
|
||||
private int _rangeVersion;
|
||||
private int _integrityVersion;
|
||||
private int _pilotSkillVersion;
|
||||
private int _environmentVersion;
|
||||
private float _lastQuality;
|
||||
private float _statCharge;
|
||||
private float _statAmmo;
|
||||
private float _statRange;
|
||||
private float _statIntegrity;
|
||||
private float _statPilotSkill;
|
||||
private float _statEnvironment;
|
||||
private float oldTemperature;
|
||||
public float Temperature
|
||||
{
|
||||
|
|
@ -1465,14 +1622,15 @@ public class EquippedItem
|
|||
ThermalResilience = RuntimeItem?.ThermalResilience > 0 ? (float)RuntimeItem.ThermalResilience : 1;
|
||||
_thermalPerformanceCurve = CreateThermalPerformanceCurve(RuntimeItem);
|
||||
_audioStats = CreateAudioStatBindings(RuntimeItem);
|
||||
ThermalExponent = lerp(
|
||||
ThermalExponent = AetheriaScalar.Lerp(
|
||||
ItemManager.GameplaySettings.ThermalQualityMin,
|
||||
ItemManager.GameplaySettings.ThermalQualityMax,
|
||||
pow(item.Quality, ItemManager.GameplaySettings.ThermalQualityExponent));
|
||||
DurabilityExponent = lerp(
|
||||
MathF.Pow(item.Quality, ItemManager.GameplaySettings.ThermalQualityExponent));
|
||||
DurabilityExponent = AetheriaScalar.Lerp(
|
||||
ItemManager.GameplaySettings.DurabilityQualityMin,
|
||||
ItemManager.GameplaySettings.DurabilityQualityMax,
|
||||
pow(item.Quality, ItemManager.GameplaySettings.DurabilityQualityExponent));
|
||||
MathF.Pow(item.Quality, ItemManager.GameplaySettings.DurabilityQualityExponent));
|
||||
_lastQuality = item.Quality;
|
||||
var hullShape = ItemManager.GetRuntimeShape(entity.Hull);
|
||||
var itemShape = ItemManager.GetRuntimeShape(item);
|
||||
InsetShape = hullShape.Inset(itemShape, position, item.Rotation);
|
||||
|
|
@ -1503,25 +1661,183 @@ public class EquippedItem
|
|||
}
|
||||
}
|
||||
|
||||
internal void RegisterPerformanceStat(string behaviorKind, string statName, PerformanceStat stat)
|
||||
{
|
||||
if (stat == null || string.IsNullOrWhiteSpace(statName))
|
||||
return;
|
||||
|
||||
if (!_statCache.TryGetValue(stat, out var entry))
|
||||
{
|
||||
_statCache[stat] = new EquippedItemStatCacheEntry(stat, GetDependencyMask(stat));
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Stat = stat;
|
||||
entry.DependencyMask = GetDependencyMask(stat);
|
||||
entry.Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public float Evaluate(string behaviorKind, string statName, PerformanceStat stat)
|
||||
{
|
||||
RegisterPerformanceStat(behaviorKind, statName, stat);
|
||||
return Evaluate(stat);
|
||||
}
|
||||
|
||||
public float Evaluate(PerformanceStat stat)
|
||||
{
|
||||
var heat = pow(ThermalPerformance, ThermalExponent * stat.HeatExponentMultiplier);
|
||||
var durability = pow(DurabilityPerformance, DurabilityExponent * stat.DurabilityExponentMultiplier);
|
||||
var quality = pow(EquippableItem.Quality, stat.QualityExponent);
|
||||
if (stat == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var scaleModifier = 1.0f;
|
||||
var scaleModifiers = stat.GetScaleModifiers(Entity).Values;
|
||||
foreach (var value in scaleModifiers) scaleModifier *= value;
|
||||
if (_statCache.TryGetValue(stat, out var entry))
|
||||
{
|
||||
return EvaluateCachedStat(entry);
|
||||
}
|
||||
|
||||
float constantModifier = 0;
|
||||
foreach (var value in stat.GetConstantModifiers(Entity).Values) constantModifier += value;
|
||||
return EvaluateUncached(stat);
|
||||
}
|
||||
|
||||
var result = lerp(stat.Min, stat.Max, durability * quality * heat) * scaleModifier + constantModifier;
|
||||
public float Evaluate(PerformanceStat stat, StatConditionMask condition, float value)
|
||||
{
|
||||
if (stat == null)
|
||||
return 0;
|
||||
|
||||
var dependencyMask = GetDependencyMask(stat);
|
||||
if ((dependencyMask & condition) == 0)
|
||||
return Evaluate(stat);
|
||||
|
||||
return EvaluateWithContext(stat, CreateStatEvaluationContext().WithCondition(condition, value));
|
||||
}
|
||||
|
||||
public float Evaluate(
|
||||
PerformanceStat stat,
|
||||
StatConditionMask firstCondition,
|
||||
float firstValue,
|
||||
StatConditionMask secondCondition,
|
||||
float secondValue)
|
||||
{
|
||||
if (stat == null)
|
||||
return 0;
|
||||
|
||||
var dependencyMask = GetDependencyMask(stat);
|
||||
if ((dependencyMask & (firstCondition | secondCondition)) == 0)
|
||||
return Evaluate(stat);
|
||||
|
||||
var context = CreateStatEvaluationContext()
|
||||
.WithCondition(firstCondition, firstValue)
|
||||
.WithCondition(secondCondition, secondValue);
|
||||
return EvaluateWithContext(stat, context);
|
||||
}
|
||||
|
||||
private float EvaluateCachedStat(EquippedItemStatCacheEntry entry)
|
||||
{
|
||||
var stamp = BuildCacheStamp(entry.DependencyMask);
|
||||
if (!entry.Dirty && entry.InputStamp == stamp)
|
||||
{
|
||||
return entry.Value;
|
||||
}
|
||||
|
||||
var value = EvaluateUncached(entry.Stat);
|
||||
entry.Value = value;
|
||||
entry.InputStamp = stamp;
|
||||
entry.Dirty = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
private float EvaluateUncached(PerformanceStat stat)
|
||||
{
|
||||
var dependencyMask = GetDependencyMask(stat);
|
||||
var stamp = BuildCacheStamp(dependencyMask);
|
||||
if (!_baseStatCache.TryGetValue(stat, out var baseEntry) || baseEntry.InputStamp != stamp)
|
||||
{
|
||||
float baseValue;
|
||||
baseValue = stat.EvaluateRecipeOrLegacy(
|
||||
CreateStatEvaluationContext(),
|
||||
DurabilityExponent,
|
||||
ThermalExponent,
|
||||
includeHeat: true);
|
||||
_baseStatCache[stat] = new EquippedItemBaseStatCacheEntry(baseValue, stamp);
|
||||
}
|
||||
var cachedBaseValue = _baseStatCache[stat].Value;
|
||||
|
||||
var result = stat.ApplyEntityModifiers(Entity, cachedBaseValue);
|
||||
if (float.IsNaN(result))
|
||||
return stat.Min;
|
||||
return result;
|
||||
}
|
||||
|
||||
private float EvaluateWithContext(PerformanceStat stat, in StatEvaluationContext context)
|
||||
{
|
||||
var baseValue = stat.EvaluateRecipeOrLegacy(
|
||||
context,
|
||||
DurabilityExponent,
|
||||
ThermalExponent,
|
||||
includeHeat: true);
|
||||
|
||||
var result = stat.ApplyEntityModifiers(Entity, baseValue);
|
||||
return float.IsNaN(result) ? stat.Min : result;
|
||||
}
|
||||
|
||||
internal void SetStatModifier(
|
||||
PerformanceStat stat,
|
||||
Behavior source,
|
||||
StatModifierType type,
|
||||
float value)
|
||||
{
|
||||
if (stat == null || source == null)
|
||||
return;
|
||||
|
||||
var modifiers = type == StatModifierType.Constant
|
||||
? stat.GetConstantModifiers(Entity)
|
||||
: stat.GetScaleModifiers(Entity);
|
||||
|
||||
if (modifiers.TryGetValue(source, out var current) && MathF.Abs(current - value) < 0.00001f)
|
||||
return;
|
||||
|
||||
modifiers[source] = value;
|
||||
InvalidateStat(stat);
|
||||
}
|
||||
|
||||
internal void RemoveStatModifier(PerformanceStat stat, Behavior source, StatModifierType type)
|
||||
{
|
||||
if (stat == null || source == null)
|
||||
return;
|
||||
|
||||
var hasModifiers = type == StatModifierType.Constant
|
||||
? stat.TryGetConstantModifiers(Entity, out var modifiers)
|
||||
: stat.TryGetScaleModifiers(Entity, out modifiers);
|
||||
|
||||
if (hasModifiers && modifiers.Remove(source))
|
||||
InvalidateStat(stat);
|
||||
}
|
||||
|
||||
public void SetStatConditionValue(StatConditionMask condition, float value)
|
||||
{
|
||||
switch (condition)
|
||||
{
|
||||
case StatConditionMask.Charge:
|
||||
SetTrackedStatCondition(ref _statCharge, ref _chargeVersion, value);
|
||||
break;
|
||||
case StatConditionMask.Ammo:
|
||||
SetTrackedStatCondition(ref _statAmmo, ref _ammoVersion, value);
|
||||
break;
|
||||
case StatConditionMask.Range:
|
||||
SetTrackedStatCondition(ref _statRange, ref _rangeVersion, value);
|
||||
break;
|
||||
case StatConditionMask.Integrity:
|
||||
SetTrackedStatCondition(ref _statIntegrity, ref _integrityVersion, value);
|
||||
break;
|
||||
case StatConditionMask.PilotSkill:
|
||||
SetTrackedStatCondition(ref _statPilotSkill, ref _pilotSkillVersion, value);
|
||||
break;
|
||||
case StatConditionMask.Environment:
|
||||
SetTrackedStatCondition(ref _statEnvironment, ref _environmentVersion, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddHeat(float heat, bool ignoreThermalMass = false)
|
||||
{
|
||||
foreach(var hullCoord in InsetShape.Coordinates)
|
||||
|
|
@ -1531,12 +1847,28 @@ public class EquippedItem
|
|||
public void UpdatePerformance()
|
||||
{
|
||||
var temp = Temperature;
|
||||
ThermalPerformance = EvaluateThermalPerformance(temp);
|
||||
var deltaTemp = math.abs(temp - oldTemperature);
|
||||
DurabilityPerformance = EquippableItem.Durability / MaxDurability;
|
||||
var nextThermalPerformance = EvaluateThermalPerformance(temp);
|
||||
var deltaTemp = MathF.Abs(temp - oldTemperature);
|
||||
var nextDurabilityPerformance = EquippableItem.Durability / MaxDurability;
|
||||
var nextQuality = EquippableItem.Quality;
|
||||
if (MathF.Abs(nextThermalPerformance - ThermalPerformance) > 0.00001f)
|
||||
{
|
||||
_thermalVersion++;
|
||||
}
|
||||
if (MathF.Abs(nextDurabilityPerformance - DurabilityPerformance) > 0.00001f)
|
||||
{
|
||||
_durabilityVersion++;
|
||||
}
|
||||
if (MathF.Abs(nextQuality - _lastQuality) > 0.00001f)
|
||||
{
|
||||
_qualityVersion++;
|
||||
}
|
||||
ThermalPerformance = nextThermalPerformance;
|
||||
DurabilityPerformance = nextDurabilityPerformance;
|
||||
_lastQuality = nextQuality;
|
||||
var performanceThreshold = Entity.Settings.ShutdownPerformance;
|
||||
Wear = (1 - pow(ThermalPerformance,
|
||||
(1 - pow(EquippableItem.Quality, ItemManager.GameplaySettings.QualityWearExponent)) *
|
||||
Wear = (1 - MathF.Pow(ThermalPerformance,
|
||||
(1 - MathF.Pow(EquippableItem.Quality, ItemManager.GameplaySettings.QualityWearExponent)) *
|
||||
ItemManager.GameplaySettings.ThermalWearExponent) +
|
||||
deltaTemp * ItemManager.GameplaySettings.DeltaTempWearExponent
|
||||
) * MaxDurability / ThermalResilience;
|
||||
|
|
@ -1545,6 +1877,67 @@ public class EquippedItem
|
|||
oldTemperature = temp;
|
||||
}
|
||||
|
||||
private void InvalidateStat(PerformanceStat stat)
|
||||
{
|
||||
if (stat != null && _statCache.TryGetValue(stat, out var entry))
|
||||
entry.Dirty = true;
|
||||
}
|
||||
|
||||
private StatConditionMask GetDependencyMask(PerformanceStat stat)
|
||||
{
|
||||
return stat?.GetDependencyMask(includeHeat: true) ?? StatConditionMask.None;
|
||||
}
|
||||
|
||||
private int BuildCacheStamp(StatConditionMask dependencyMask)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var stamp = 17;
|
||||
if ((dependencyMask & StatConditionMask.Quality) != 0)
|
||||
stamp = stamp * 31 + _qualityVersion;
|
||||
if ((dependencyMask & StatConditionMask.Durability) != 0)
|
||||
stamp = stamp * 31 + _durabilityVersion;
|
||||
if ((dependencyMask & StatConditionMask.Heat) != 0)
|
||||
stamp = stamp * 31 + _thermalVersion;
|
||||
if ((dependencyMask & StatConditionMask.Charge) != 0)
|
||||
stamp = stamp * 31 + _chargeVersion;
|
||||
if ((dependencyMask & StatConditionMask.Ammo) != 0)
|
||||
stamp = stamp * 31 + _ammoVersion;
|
||||
if ((dependencyMask & StatConditionMask.Range) != 0)
|
||||
stamp = stamp * 31 + _rangeVersion;
|
||||
if ((dependencyMask & StatConditionMask.Integrity) != 0)
|
||||
stamp = stamp * 31 + _integrityVersion;
|
||||
if ((dependencyMask & StatConditionMask.PilotSkill) != 0)
|
||||
stamp = stamp * 31 + _pilotSkillVersion;
|
||||
if ((dependencyMask & StatConditionMask.Environment) != 0)
|
||||
stamp = stamp * 31 + _environmentVersion;
|
||||
return stamp;
|
||||
}
|
||||
}
|
||||
|
||||
private StatEvaluationContext CreateStatEvaluationContext()
|
||||
{
|
||||
return new StatEvaluationContext(
|
||||
quality: EquippableItem.Quality,
|
||||
durability: DurabilityPerformance,
|
||||
heat: ThermalPerformance,
|
||||
charge: _statCharge,
|
||||
ammo: _statAmmo,
|
||||
range: _statRange,
|
||||
integrity: _statIntegrity,
|
||||
pilotSkill: _statPilotSkill,
|
||||
environment: _statEnvironment);
|
||||
}
|
||||
|
||||
private static void SetTrackedStatCondition(ref float current, ref int version, float next)
|
||||
{
|
||||
if (MathF.Abs(current - next) <= 0.00001f)
|
||||
return;
|
||||
|
||||
current = next;
|
||||
version++;
|
||||
}
|
||||
|
||||
private float EvaluateThermalPerformance(float temperature)
|
||||
{
|
||||
if (_thermalPerformanceCurve == null ||
|
||||
|
|
@ -1554,8 +1947,8 @@ public class EquippedItem
|
|||
return 1;
|
||||
}
|
||||
|
||||
var t = unlerp((float)RuntimeItem.MinimumTemperature, (float)RuntimeItem.MaximumTemperature, temperature);
|
||||
return saturate(_thermalPerformanceCurve.Evaluate(t));
|
||||
var t = AetheriaScalar.Unlerp((float)RuntimeItem.MinimumTemperature, (float)RuntimeItem.MaximumTemperature, temperature);
|
||||
return AetheriaScalar.Saturate(_thermalPerformanceCurve.Evaluate(t));
|
||||
}
|
||||
|
||||
private static BezierCurve CreateThermalPerformanceCurve(AetheriaRuntimeCatalogItem item)
|
||||
|
|
@ -1565,12 +1958,9 @@ public class EquippedItem
|
|||
return null;
|
||||
}
|
||||
|
||||
return new BezierCurve
|
||||
{
|
||||
Keys = item.ThermalPerformanceCurveKeys
|
||||
.Select(key => new float4((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent))
|
||||
.ToArray()
|
||||
};
|
||||
return BezierCurve.FromKeys(
|
||||
item.ThermalPerformanceCurveKeys
|
||||
.Select(key => ((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent)));
|
||||
}
|
||||
|
||||
public void Update(float delta)
|
||||
|
|
@ -1626,10 +2016,50 @@ public class EquippedItem
|
|||
Max = (float)stat.Max,
|
||||
HeatExponentMultiplier = (float)stat.HeatExponentMultiplier,
|
||||
DurabilityExponentMultiplier = (float)stat.DurabilityExponentMultiplier,
|
||||
QualityExponent = (float)stat.QualityExponent
|
||||
QualityExponent = (float)stat.QualityExponent,
|
||||
Recipe = CreateStatRecipe(stat.Recipe)
|
||||
};
|
||||
}
|
||||
|
||||
private static StatRecipe CreateStatRecipe(AetheriaRuntimeStatRecipe recipe)
|
||||
{
|
||||
if (recipe == null)
|
||||
return null;
|
||||
|
||||
return new StatRecipe
|
||||
{
|
||||
BaseValue = (float)recipe.BaseValue,
|
||||
Modifiers = recipe.Modifiers
|
||||
.Select(CreateStatRecipeModifier)
|
||||
.Where(modifier => modifier != null)
|
||||
.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private static StatRecipeModifier CreateStatRecipeModifier(AetheriaRuntimeStatRecipeModifier modifier)
|
||||
{
|
||||
if (modifier == null)
|
||||
return null;
|
||||
|
||||
return new StatRecipeModifier
|
||||
{
|
||||
Condition = StatRecipeTokens.ToConditionMask(modifier.Condition),
|
||||
Operation = StatRecipeTokens.ToModifierOperation(modifier.Operation),
|
||||
Amount = (float)modifier.Amount,
|
||||
Curve = CreateCurve(modifier.CurveKeys),
|
||||
Enabled = modifier.Enabled
|
||||
};
|
||||
}
|
||||
|
||||
private static BezierCurve CreateCurve(IReadOnlyList<AetheriaRuntimeCurveKey> keys)
|
||||
{
|
||||
if (keys == null || keys.Count == 0)
|
||||
return null;
|
||||
|
||||
return BezierCurve.FromKeys(
|
||||
keys.Select(key => ((float)key.Time, (float)key.Value, (float)key.InTangent, (float)key.OutTangent)));
|
||||
}
|
||||
|
||||
private readonly struct EquippedItemAudioStatBinding
|
||||
{
|
||||
public EquippedItemAudioStatBinding(uint parameter, PerformanceStat stat)
|
||||
|
|
@ -1641,6 +2071,34 @@ public class EquippedItem
|
|||
public uint Parameter { get; }
|
||||
public PerformanceStat Stat { get; }
|
||||
}
|
||||
|
||||
private sealed class EquippedItemStatCacheEntry
|
||||
{
|
||||
public EquippedItemStatCacheEntry(PerformanceStat stat, StatConditionMask dependencyMask)
|
||||
{
|
||||
Stat = stat;
|
||||
DependencyMask = dependencyMask;
|
||||
Dirty = true;
|
||||
}
|
||||
|
||||
public PerformanceStat Stat { get; set; }
|
||||
public StatConditionMask DependencyMask { get; set; }
|
||||
public float Value { get; set; }
|
||||
public int InputStamp { get; set; }
|
||||
public bool Dirty { get; set; }
|
||||
}
|
||||
|
||||
private readonly struct EquippedItemBaseStatCacheEntry
|
||||
{
|
||||
public EquippedItemBaseStatCacheEntry(float value, int inputStamp)
|
||||
{
|
||||
Value = value;
|
||||
InputStamp = inputStamp;
|
||||
}
|
||||
|
||||
public float Value { get; }
|
||||
public int InputStamp { get; }
|
||||
}
|
||||
}
|
||||
|
||||
public class EquippedCargoBay : EquippedItem
|
||||
|
|
@ -1735,7 +2193,7 @@ public class EquippedCargoBay : EquippedItem
|
|||
if (cargoCommodity.Quantity >= maxStack) continue;
|
||||
|
||||
// Subtract remaining space in existing stack from remaining quantity
|
||||
remainingQuantity -= min(maxStack - cargoCommodity.Quantity, remainingQuantity);
|
||||
remainingQuantity -= Math.Min(maxStack - cargoCommodity.Quantity, remainingQuantity);
|
||||
positions.Add(Cargo[cargoItem]);
|
||||
|
||||
// If we've moved all of the items into existing stacks, no need to search for empty space!
|
||||
|
|
@ -1769,7 +2227,7 @@ public class EquippedCargoBay : EquippedItem
|
|||
}
|
||||
}
|
||||
|
||||
position = int2.zero;
|
||||
position = new int2(0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1978,7 +2436,7 @@ public class EquippedCargoBay : EquippedItem
|
|||
if (cells == null) return shape;
|
||||
|
||||
foreach (var cell in cells)
|
||||
shape[new int2(cell.X, cell.Y)] = true;
|
||||
shape.SetCell(cell.X, cell.Y, true);
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
|
@ -2025,7 +2483,7 @@ public class EquippedDockingBay : EquippedCargoBay
|
|||
var typedDockingBay = ItemManager.GetRuntimeItem(EquippableItem);
|
||||
MaxSize = typedDockingBay != null
|
||||
? new int2(typedDockingBay.DockingMaxSizeX, typedDockingBay.DockingMaxSizeY)
|
||||
: int2.zero;
|
||||
: new int2(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using float3 = Unity.Mathematics.float3;
|
||||
using int2 = Unity.Mathematics.int2;
|
||||
|
||||
public static class EntityConstructionBlueprintProjector
|
||||
{
|
||||
|
|
@ -19,8 +19,8 @@ public static class EntityConstructionBlueprintProjector
|
|||
else if (entity is Ship ship)
|
||||
blueprint = new ShipConstructionBlueprint
|
||||
{
|
||||
Position = ship.Position,
|
||||
Direction = ship.Direction,
|
||||
Position = AetheriaMath.ToUnity(ship.CultPosition),
|
||||
Direction = AetheriaMath.ToUnity(ship.CultDirection),
|
||||
IsPlayerShip = ship.IsPlayerShip
|
||||
};
|
||||
else throw new ArgumentException("Attempted to capture an instance of abstract class Entity!");
|
||||
|
|
@ -41,30 +41,40 @@ public static class EntityConstructionBlueprintProjector
|
|||
return blueprint;
|
||||
}
|
||||
|
||||
public static Entity InstantiateFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint, bool instantiate = false)
|
||||
public static Entity InstantiateAuthoritativeFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint)
|
||||
{
|
||||
return BuildFromBlueprint(itemManager, zone, blueprint, false);
|
||||
}
|
||||
|
||||
public static Entity ProjectObservedFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint)
|
||||
{
|
||||
return BuildFromBlueprint(itemManager, zone, blueprint, true);
|
||||
}
|
||||
|
||||
private static Entity BuildFromBlueprint(ItemManager itemManager, Zone zone, EntityConstructionBlueprint blueprint, bool instantiate)
|
||||
{
|
||||
blueprint.Settings ??= itemManager.GameplaySettings.DefaultEntitySettings.Copy();
|
||||
return blueprint switch
|
||||
{
|
||||
ShipConstructionBlueprint shipBlueprint => InstantiateFromBlueprint(itemManager, zone, shipBlueprint, instantiate),
|
||||
OrbitalEntityConstructionBlueprint orbitalBlueprint => InstantiateFromBlueprint(itemManager, zone, orbitalBlueprint, instantiate),
|
||||
ShipConstructionBlueprint shipBlueprint => BuildFromBlueprint(itemManager, zone, shipBlueprint, instantiate),
|
||||
OrbitalEntityConstructionBlueprint orbitalBlueprint => BuildFromBlueprint(itemManager, zone, orbitalBlueprint, instantiate),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static Ship InstantiateFromBlueprint(ItemManager itemManager, Zone zone, ShipConstructionBlueprint blueprint, bool instantiate = false)
|
||||
private static Ship BuildFromBlueprint(ItemManager itemManager, Zone zone, ShipConstructionBlueprint blueprint, bool instantiate)
|
||||
{
|
||||
|
||||
var entity = new Ship(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(blueprint.Hull) : blueprint.Hull, blueprint.Settings);
|
||||
Restore(itemManager, zone, blueprint, entity, instantiate);
|
||||
entity.Position = blueprint.Position;
|
||||
entity.Direction = blueprint.Direction;
|
||||
entity.CultPosition = AetheriaMath.ToCult(blueprint.Position);
|
||||
entity.CultDirection = AetheriaMath.ToCult(blueprint.Direction);
|
||||
entity.IsPlayerShip = blueprint.IsPlayerShip;
|
||||
return entity;
|
||||
}
|
||||
|
||||
private static OrbitalEntity InstantiateFromBlueprint(ItemManager itemManager, Zone zone, OrbitalEntityConstructionBlueprint blueprint, bool instantiate = false)
|
||||
private static OrbitalEntity BuildFromBlueprint(ItemManager itemManager, Zone zone, OrbitalEntityConstructionBlueprint blueprint, bool instantiate)
|
||||
{
|
||||
var entity = new OrbitalEntity(itemManager, zone, instantiate ? (EquippableItem) itemManager.Instantiate(blueprint.Hull) : blueprint.Hull, blueprint.OrbitKey, blueprint.Settings);
|
||||
Restore(itemManager, zone, blueprint, entity, instantiate);
|
||||
|
|
@ -80,7 +90,7 @@ public static class EntityConstructionBlueprintProjector
|
|||
entity.Faction = ResolveFaction(zone?.Galaxy, blueprint);
|
||||
entity.Children = blueprint.Children.Select(c =>
|
||||
{
|
||||
var child = InstantiateFromBlueprint(itemManager, zone, c, instantiate);
|
||||
var child = BuildFromBlueprint(itemManager, zone, c, instantiate);
|
||||
child.Parent = entity;
|
||||
return child;
|
||||
}).ToList();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static Unity.Mathematics.noise;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using float3 = Unity.Mathematics.float3;
|
||||
using unitynoise = Unity.Mathematics.noise;
|
||||
|
||||
[Serializable]
|
||||
public class ZoneEnvironment
|
||||
|
|
@ -78,8 +77,8 @@ public abstract class Brush
|
|||
|
||||
float powerPulse( float x, float power )
|
||||
{
|
||||
x = saturate(abs(x))-.001f;
|
||||
return pow((x + 1.0f) * (1.0f - x), power);
|
||||
x = Saturate(MathF.Abs(x))-.001f;
|
||||
return MathF.Pow((x + 1.0f) * (1.0f - x), power);
|
||||
}
|
||||
|
||||
protected abstract float Evaluate(float2 world, float2 uv);
|
||||
|
|
@ -87,10 +86,26 @@ public abstract class Brush
|
|||
public float Evaluate(float2 world, float2 pos, float2 radius)
|
||||
{
|
||||
var uv = (world - pos) / radius;
|
||||
float dist = length(uv)*2;
|
||||
float envelope = min(Cutoff, powerPulse(dist,EnvelopeExponent)) * smoothstep(1, .95f, dist);
|
||||
float dist = Length(uv)*2;
|
||||
float envelope = MathF.Min(Cutoff, powerPulse(dist,EnvelopeExponent)) * SmoothStep(1, .95f, dist);
|
||||
return Depth * Evaluate(world, uv) * envelope;
|
||||
}
|
||||
|
||||
private static float Length(float2 value)
|
||||
{
|
||||
return MathF.Sqrt(value.x * value.x + value.y * value.y);
|
||||
}
|
||||
|
||||
private static float Saturate(float value)
|
||||
{
|
||||
return value < 0 ? 0 : value > 1 ? 1 : value;
|
||||
}
|
||||
|
||||
private static float SmoothStep(float edge0, float edge1, float x)
|
||||
{
|
||||
var t = Saturate((x - edge0) / (edge1 - edge0));
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
|
|
@ -124,8 +139,8 @@ public class SimplexBrush : TextureBrush
|
|||
|
||||
protected override float Evaluate(float2 world, float2 uv)
|
||||
{
|
||||
var noise = snoise(world * Frequency + Phase);
|
||||
return AbsoluteValue ? abs(noise) : noise;
|
||||
var noise = unitynoise.snoise(world * Frequency + Phase);
|
||||
return AbsoluteValue ? MathF.Abs(noise) : noise;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,8 +151,8 @@ public class AnimatedSimplexBrush : AnimatedBrush
|
|||
|
||||
protected override float Evaluate(float2 world, float2 uv)
|
||||
{
|
||||
var noise = snoise(float3(float2(world * Frequency + Phase), AnimationSpeed * Time));
|
||||
return AbsoluteValue ? abs(noise) : noise;
|
||||
var noise = unitynoise.snoise(new float3(world * Frequency + Phase, AnimationSpeed * Time));
|
||||
return AbsoluteValue ? MathF.Abs(noise) : noise;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,8 +163,8 @@ public class RadialWaveBrush : TextureBrush
|
|||
|
||||
protected override float Evaluate(float2 world, float2 uv)
|
||||
{
|
||||
float dist = length(uv);
|
||||
float ang = atan2(uv.y,uv.x);
|
||||
return cos((ang + Phase.x) * Frequency.x * PI + (pow(dist, WaveExponent) + Phase.y) * Frequency.y);
|
||||
float dist = MathF.Sqrt(uv.x * uv.x + uv.y * uv.y);
|
||||
float ang = MathF.Atan2(uv.y,uv.x);
|
||||
return MathF.Cos((ang + Phase.x) * Frequency.x * MathF.PI + (MathF.Pow(dist, WaveExponent) + Phase.y) * Frequency.y);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
|
||||
[Serializable]
|
||||
public class ExponentialCurve
|
||||
|
|
|
|||
|
|
@ -6,11 +6,8 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
using Unity.Tiny;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using float2 = CultMath.float2;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
|
|
@ -79,13 +76,13 @@ public static class Extensions
|
|||
switch (rotation)
|
||||
{
|
||||
case ItemRotation.None:
|
||||
return float2(0, 1);
|
||||
return new float2(0, 1);
|
||||
case ItemRotation.CounterClockwise:
|
||||
return float2(-1, 0);
|
||||
return new float2(-1, 0);
|
||||
case ItemRotation.Reversed:
|
||||
return float2(0, -1);
|
||||
return new float2(0, -1);
|
||||
case ItemRotation.Clockwise:
|
||||
return float2(1, 0);
|
||||
return new float2(1, 0);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null);
|
||||
}
|
||||
|
|
@ -98,11 +95,11 @@ public static class Extensions
|
|||
case ItemRotation.None:
|
||||
return v;
|
||||
case ItemRotation.CounterClockwise:
|
||||
return float2(-v.y, v.x);
|
||||
return new float2(-v.y, v.x);
|
||||
case ItemRotation.Reversed:
|
||||
return float2(-v.x, -v.y);
|
||||
return new float2(-v.x, -v.y);
|
||||
case ItemRotation.Clockwise:
|
||||
return float2(v.y, -v.x);
|
||||
return new float2(v.y, -v.x);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(rotation), rotation, null);
|
||||
}
|
||||
|
|
@ -112,9 +109,10 @@ public static class Extensions
|
|||
//private static Random Random => (Random) (_random ??= new Random((uint) (DateTime.Now.Ticks%uint.MaxValue)));
|
||||
// public static T RandomElement<T>(this IEnumerable<T> enumerable) => enumerable.ElementAt(Random.NextInt(0, enumerable.Count()));
|
||||
public static float NextPowerDistribution(this ref Random random, float min, float max, float exp, float randexp) =>
|
||||
pow((pow(max, exp + 1) - pow(min, exp + 1)) * pow(random.NextFloat(), randexp) + pow(min, exp + 1), 1 / (exp + 1));
|
||||
MathF.Pow((MathF.Pow(max, exp + 1) - MathF.Pow(min, exp + 1)) * MathF.Pow(random.NextFloat(), randexp) + MathF.Pow(min, exp + 1), 1 / (exp + 1));
|
||||
public static float NextUnbounded(this ref Random random) => 1 / (1 - random.NextFloat()) - 1;
|
||||
public static float NextUnbounded(this ref Random random, float bias, float power, float ceiling) => 1 / (1 - pow(min(random.NextFloat(), ceiling), 1 - pow(clamp(bias,0,.99f), 1 / power))) - 1;
|
||||
public static float NextUnbounded(this ref Random random, float bias, float power, float ceiling) =>
|
||||
1 / (1 - MathF.Pow(MathF.Min(random.NextFloat(), ceiling), 1 - MathF.Pow(Clamp(bias, 0, .99f), 1 / power))) - 1;
|
||||
|
||||
public static bool IsDefault<T>(this T value) where T : struct
|
||||
{
|
||||
|
|
@ -130,8 +128,23 @@ public static class Extensions
|
|||
|
||||
public static float Angle(this float2 from, float2 to)
|
||||
{
|
||||
var num = sqrt(lengthsq(from) * lengthsq(to));
|
||||
return num < 1.00000000362749E-15 ? 0.0f : acos(clamp(dot(from, to) / num, -1f, 1f)) * 57.29578f;
|
||||
var num = MathF.Sqrt(LengthSquared(from) * LengthSquared(to));
|
||||
return num < 1.00000000362749E-15 ? 0.0f : MathF.Acos(Clamp(Dot(from, to) / num, -1f, 1f)) * 57.29578f;
|
||||
}
|
||||
|
||||
private static float Clamp(float value, float min, float max)
|
||||
{
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
private static float Dot(float2 a, float2 b)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
private static float LengthSquared(float2 value)
|
||||
{
|
||||
return Dot(value, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ using Ink.Runtime;
|
|||
using MIConvexHull;
|
||||
using JM.LinqFaster;
|
||||
using UniRx;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
|
||||
public class Galaxy
|
||||
|
|
@ -85,11 +84,144 @@ public class Galaxy
|
|||
|
||||
CalculateFactionInfluence(progressCallback);
|
||||
|
||||
GenerateNames(nameGeneratorSettings, ref random, progressCallback);
|
||||
var nameRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue));
|
||||
GenerateNames(nameGeneratorSettings, ref nameRandom, progressCallback);
|
||||
|
||||
progressCallback?.Invoke("Done!");
|
||||
if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao
|
||||
}
|
||||
|
||||
public static Galaxy ProjectObservedDaemonRun(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
SectorBackgroundSettings background,
|
||||
AetheriaRuntimeCatalogSnapshot runtimeCatalog,
|
||||
Action<string> log)
|
||||
{
|
||||
return new Galaxy(run, background, runtimeCatalog, log);
|
||||
}
|
||||
|
||||
private Galaxy(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
SectorBackgroundSettings background,
|
||||
AetheriaRuntimeCatalogSnapshot runtimeCatalog,
|
||||
Action<string> log)
|
||||
{
|
||||
if (run == null) throw new ArgumentNullException(nameof(run));
|
||||
|
||||
_runtimeCatalog = runtimeCatalog ?? throw new InvalidOperationException("Daemon-observed galaxy requires the typed Aetheria runtime catalog.");
|
||||
_allFactions = ProjectFactions(_runtimeCatalog);
|
||||
IsPrelude = run.IsTutorial;
|
||||
Background = background;
|
||||
Log = log;
|
||||
GenerationSeed = run.GenerationSeed;
|
||||
NameGeneratorSettings = null;
|
||||
Factions = ResolveDaemonFactions(run);
|
||||
foreach (var faction in Factions)
|
||||
FactionRelationships[faction] = ResolveDaemonFactionRelationship(run, faction);
|
||||
|
||||
Zones = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
|
||||
.Where(zone => zone != null && zone.ZoneIndex >= 0)
|
||||
.OrderBy(zone => zone.ZoneIndex)
|
||||
.Select(zone => new GalaxyZone
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(zone.Name) ? $"Daemon Zone {zone.ZoneIndex}" : zone.Name,
|
||||
Position = new float2((float)zone.PositionX, (float)zone.PositionY),
|
||||
Factions = ResolveDaemonZoneFactions(zone),
|
||||
Owner = ResolveDaemonFactionByIndex(zone.OwnerFactionIndex),
|
||||
NamedZone = !string.IsNullOrWhiteSpace(zone.Name)
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
var zonesByIndex = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
|
||||
.Where(zone => zone != null && zone.ZoneIndex >= 0)
|
||||
.OrderBy(zone => zone.ZoneIndex)
|
||||
.Select((zone, ordinal) => new { zone.ZoneIndex, Zone = Zones[ordinal] })
|
||||
.ToDictionary(pair => pair.ZoneIndex, pair => pair.Zone);
|
||||
|
||||
foreach (var source in run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
|
||||
{
|
||||
if (source == null || !zonesByIndex.TryGetValue(source.ZoneIndex, out var sourceZone))
|
||||
continue;
|
||||
|
||||
foreach (var adjacentIndex in source.AdjacentZoneIndices ?? Array.Empty<int>())
|
||||
{
|
||||
if (zonesByIndex.TryGetValue(adjacentIndex, out var adjacentZone) &&
|
||||
!sourceZone.AdjacentZones.Contains(adjacentZone))
|
||||
{
|
||||
sourceZone.AdjacentZones.Add(adjacentZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Entrance = ResolveDaemonZone(run.EntranceZoneIndex) ?? ResolveDaemonZone(run.CurrentZoneIndex) ?? Zones.FirstOrDefault();
|
||||
Exit = ResolveDaemonZone(run.ExitZoneIndex);
|
||||
foreach (var zone in run.DiscoveredZoneIndices ?? Array.Empty<int>())
|
||||
{
|
||||
var discovered = ResolveDaemonZone(zone);
|
||||
if (discovered != null)
|
||||
DiscoveredZones.Add(discovered);
|
||||
}
|
||||
if (DiscoveredZones.Count == 0 && Entrance != null)
|
||||
DiscoveredZones.Add(Entrance);
|
||||
|
||||
CalculateDistanceMatrix();
|
||||
|
||||
GalaxyZone ResolveDaemonZone(int zoneIndex)
|
||||
{
|
||||
return zonesByIndex.TryGetValue(zoneIndex, out var zone) ? zone : null;
|
||||
}
|
||||
}
|
||||
|
||||
private Faction[] ResolveDaemonFactions(AetheriaRuntimeRunCheckpointCommit run)
|
||||
{
|
||||
var factionKeys = new HashSet<string>(
|
||||
(run.FactionRelationships ?? Array.Empty<AetheriaRuntimeFactionRelationshipCommit>())
|
||||
.Select(relationship => relationship?.FactionKey)
|
||||
.Where(key => !string.IsNullOrWhiteSpace(key)),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var factionIndices = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
|
||||
.Where(zone => zone != null)
|
||||
.SelectMany(zone => (zone.FactionIndices ?? Array.Empty<int>()).Concat(new[] { zone.OwnerFactionIndex }))
|
||||
.Where(index => index >= 0)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
foreach (var index in factionIndices)
|
||||
{
|
||||
if (index >= 0 && index < _allFactions.Length)
|
||||
factionKeys.Add(_allFactions[index].FactionKey);
|
||||
}
|
||||
|
||||
var factions = _allFactions
|
||||
.Where(faction => factionKeys.Contains(faction.FactionKey))
|
||||
.ToArray();
|
||||
return factions.Length == 0 ? _allFactions : factions;
|
||||
}
|
||||
|
||||
private Faction[] ResolveDaemonZoneFactions(AetheriaRuntimeZoneSnapshotCommit zone)
|
||||
{
|
||||
return (zone.FactionIndices ?? Array.Empty<int>())
|
||||
.Select(ResolveDaemonFactionByIndex)
|
||||
.Where(faction => faction != null)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private Faction ResolveDaemonFactionByIndex(int index)
|
||||
{
|
||||
return index >= 0 && index < _allFactions.Length ? _allFactions[index] : null;
|
||||
}
|
||||
|
||||
private FactionRelationship ResolveDaemonFactionRelationship(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
Faction faction)
|
||||
{
|
||||
var relationship = (run.FactionRelationships ?? Array.Empty<AetheriaRuntimeFactionRelationshipCommit>())
|
||||
.FirstOrDefault(candidate => string.Equals(candidate?.FactionKey ?? "", faction?.FactionKey ?? "", StringComparison.OrdinalIgnoreCase));
|
||||
return Enum.TryParse<FactionRelationship>(relationship?.Relationship ?? "", out var parsed)
|
||||
? parsed
|
||||
: FactionRelationship.Neutral;
|
||||
}
|
||||
|
||||
public Faction ResolveFaction(string name)
|
||||
{
|
||||
|
|
@ -161,15 +293,15 @@ public class Galaxy
|
|||
.MaxBy(z => ConnectedRegion(z, antagonistFaction.InfluenceDistance).Count * z.Distance[HomeZones[protagonistFaction]]);
|
||||
|
||||
HomeZones[protagonistFaction] = Zones
|
||||
.MaxBy(z => ConnectedRegion(z, protagonistFaction.InfluenceDistance).Count * sqrt(z.Distance[HomeZones[antagonistFaction]]));
|
||||
.MaxBy(z => ConnectedRegion(z, protagonistFaction.InfluenceDistance).Count * MathF.Sqrt(z.Distance[HomeZones[antagonistFaction]]));
|
||||
|
||||
// var antagonistRegion = ConnectedRegion(HomeZones[antagonistFaction], antagonistFaction.InfluenceDistance);
|
||||
// var protagonistRegion = ConnectedRegion(HomeZones[protagonistFaction], protagonistFaction.InfluenceDistance);
|
||||
|
||||
// Place the buffer faction in a zone where it has equal distance to the pro/antagonist HQs and where it can control the most territory
|
||||
var bufferDistance = Zones.Min(z => abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]));
|
||||
var bufferDistance = Zones.Min(z => Math.Abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]));
|
||||
var potentialBufferZones = Zones
|
||||
.Where(z => abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]) == bufferDistance);
|
||||
.Where(z => Math.Abs(z.Distance[HomeZones[antagonistFaction]] - z.Distance[HomeZones[protagonistFaction]]) == bufferDistance);
|
||||
HomeZones[bufferFaction] = potentialBufferZones.MaxBy(z => ConnectedRegion(z, bufferFaction.InfluenceDistance).Count);
|
||||
|
||||
// Place neutral headquarters away from existing factions while also maximizing territory
|
||||
|
|
@ -177,7 +309,7 @@ public class Galaxy
|
|||
{
|
||||
HomeZones[faction] = Zones.MaxBy(z =>
|
||||
ConnectedRegion(z, faction.InfluenceDistance).Count *
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])));
|
||||
}
|
||||
|
||||
CalculateFactionInfluence(progressCallback);
|
||||
|
|
@ -201,7 +333,8 @@ public class Galaxy
|
|||
|
||||
CalculateFactionInfluence(progressCallback);
|
||||
|
||||
GenerateNames(nameGeneratorSettings, ref random, progressCallback);
|
||||
var nameRandom = new CultMath.Random((uint)random.NextInt(1, int.MaxValue));
|
||||
GenerateNames(nameGeneratorSettings, ref nameRandom, progressCallback);
|
||||
|
||||
// progressCallback?.Invoke("Weaving Narrative");
|
||||
// var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random, Log);
|
||||
|
|
@ -216,7 +349,7 @@ public class Galaxy
|
|||
var outputSamples = WeightedSampleElimination.GeneratePoints(zoneCount,
|
||||
ref random,
|
||||
Background.CloudDensity,
|
||||
v => (.2f - lengthsq(v - float2(.5f))) * 4,
|
||||
v => (.2f - LengthSquared(v - new float2(.5f, .5f))) * 4,
|
||||
progressCallback);
|
||||
return outputSamples.Select(v => new GalaxyZone {Position = v}).ToArray();
|
||||
}
|
||||
|
|
@ -305,17 +438,17 @@ public class Galaxy
|
|||
HomeZones[mega] = ConnectedRegion(BossZones[mega], mega.InfluenceDistance)
|
||||
.MaxBy(z =>
|
||||
ConnectedRegion(z, mega.InfluenceDistance).Count *
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])));
|
||||
}
|
||||
|
||||
// Place remaining headquarters away from existing megas while also maximizing territory
|
||||
foreach (var mega in Factions.Where(m => !bossMegas.Contains(m)))
|
||||
{
|
||||
HomeZones[mega] = Zones.MaxBy(z =>
|
||||
pow(ConnectedRegion(z, mega.InfluenceDistance).Count, HomeZones.Count) *
|
||||
MathF.Pow(ConnectedRegion(z, mega.InfluenceDistance).Count, HomeZones.Count) *
|
||||
Exit.Distance[z] * Entrance.Distance[z] *
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])) *
|
||||
BossZones.Values.Aggregate(1f, (i, os) => i * sqrt(os.Distance[z])));
|
||||
HomeZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])) *
|
||||
BossZones.Values.Aggregate(1f, (i, os) => i * MathF.Sqrt(os.Distance[z])));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,7 +476,7 @@ public class Galaxy
|
|||
}
|
||||
|
||||
private void GenerateNames(NameGeneratorSettings nameGeneratorSettings,
|
||||
ref Random random,
|
||||
ref CultMath.Random random,
|
||||
Action<string> progressCallback = null)
|
||||
{
|
||||
for (var i = 0; i < Factions.Length; i++)
|
||||
|
|
@ -416,8 +549,8 @@ public class Galaxy
|
|||
|
||||
float LinkWeight((GalaxyZone, GalaxyZone) link)
|
||||
{
|
||||
return 1 / saturate(Background.CloudDensity((link.Item1.Position + link.Item2.Position) / 2)) *
|
||||
lengthsq(link.Item1.Position - link.Item2.Position) *
|
||||
return 1 / Saturate(Background.CloudDensity((link.Item1.Position + link.Item2.Position) / 2)) *
|
||||
LengthSquared(link.Item1.Position - link.Item2.Position) *
|
||||
(link.Item1.AdjacentZones.Count - 1) * (link.Item2.AdjacentZones.Count - 1);
|
||||
}
|
||||
|
||||
|
|
@ -583,12 +716,22 @@ public class Galaxy
|
|||
if (!bestFirst) zonesToSearch = zonesToSearch.Where(z => !searched.Contains(z));
|
||||
foreach (var dijkstraStar in zonesToSearch
|
||||
// Cost is parent cost plus distance squared
|
||||
.Select(zone => new DijkstraVertex {Parent = s, Zone = zone, Cost = s.Cost + lengthsq(s.Zone.Position - zone.Position)}))
|
||||
.Select(zone => new DijkstraVertex {Parent = s, Zone = zone, Cost = s.Cost + LengthSquared(s.Zone.Position - zone.Position)}))
|
||||
// Add new member to list, sorted by cost plus optional heuristic
|
||||
unsearchedNodes.PushObj(dijkstraStar, bestFirst ? dijkstraStar.Cost + lengthsq(dijkstraStar.Zone.Position - target.Position) : dijkstraStar.Cost);
|
||||
unsearchedNodes.PushObj(dijkstraStar, bestFirst ? dijkstraStar.Cost + LengthSquared(dijkstraStar.Zone.Position - target.Position) : dijkstraStar.Cost);
|
||||
searched.Add(s.Zone);
|
||||
}
|
||||
}
|
||||
|
||||
private static float LengthSquared(float2 value)
|
||||
{
|
||||
return value.x * value.x + value.y * value.y;
|
||||
}
|
||||
|
||||
private static float Saturate(float value)
|
||||
{
|
||||
return value < 0 ? 0 : value > 1 ? 1 : value;
|
||||
}
|
||||
}
|
||||
|
||||
public class GalaxyZone
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public abstract class ItemInstance
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
using Random = CultMath.Random;
|
||||
using JM.LinqFaster;
|
||||
using UniRx;
|
||||
|
||||
|
|
@ -157,7 +155,7 @@ public class ItemManager
|
|||
if (cells == null) return shape;
|
||||
|
||||
foreach (var cell in cells)
|
||||
shape[new int2(cell.X, cell.Y)] = true;
|
||||
shape.SetCell(cell.X, cell.Y, true);
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
|
@ -219,19 +217,30 @@ public class ItemManager
|
|||
maxDurability = Math.Max(item.Durability, 1f);
|
||||
}
|
||||
|
||||
var quality = pow(item.Quality, stat.QualityExponent);
|
||||
var durabilityExponent = lerp(
|
||||
var durabilityRatio = item.Durability / maxDurability;
|
||||
var durabilityExponent = Lerp(
|
||||
GameplaySettings.DurabilityQualityMin,
|
||||
GameplaySettings.DurabilityQualityMax,
|
||||
pow(item.Quality, GameplaySettings.DurabilityQualityExponent));
|
||||
var durability = pow(item.Durability / maxDurability, durabilityExponent * stat.DurabilityExponentMultiplier);
|
||||
var result = lerp(stat.Min, stat.Max, quality * durability);
|
||||
MathF.Pow(item.Quality, GameplaySettings.DurabilityQualityExponent));
|
||||
var result = stat.EvaluateRecipeOrLegacy(
|
||||
new StatEvaluationContext(
|
||||
quality: item.Quality,
|
||||
durability: durabilityRatio,
|
||||
heat: 1),
|
||||
durabilityExponent,
|
||||
thermalExponent: 0,
|
||||
includeHeat: false);
|
||||
if (float.IsNaN(result))
|
||||
throw new InvalidOperationException($"Performance Stat on {typedItem?.Name ?? item.ItemKey} evaluating as NaN: input data is invalid! Durability: {item.Durability} / {maxDurability}");
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private static float Lerp(float from, float to, float t)
|
||||
{
|
||||
return from + (to - from) * t;
|
||||
}
|
||||
|
||||
public int GetPrice(CraftedItemInstance item)
|
||||
{
|
||||
var typedItem = GetRuntimeItem(item);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
public class LoadoutGenerator
|
||||
{
|
||||
|
|
@ -170,8 +168,8 @@ public class LoadoutGenerator
|
|||
? allegiance / ManufacturerDistancePenalty(manufacturerKey)
|
||||
: 0;
|
||||
return allegianceWeight *
|
||||
pow(item.OccupiedCells, sizeExponent) / // Prioritize larger items
|
||||
pow(item.Price, PriceExponent); // Penalize item price to a controllable degree
|
||||
MathF.Pow(item.OccupiedCells, sizeExponent) / // Prioritize larger items
|
||||
MathF.Pow(item.Price, PriceExponent); // Penalize item price to a controllable degree
|
||||
},
|
||||
count
|
||||
);
|
||||
|
|
@ -289,7 +287,7 @@ public class LoadoutGenerator
|
|||
var shape = new Shape(Math.Max(width, 1), Math.Max(height, 1));
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
shape[int2(cell.X, cell.Y)] = true;
|
||||
shape.SetCell(cell.X, cell.Y, true);
|
||||
}
|
||||
|
||||
return shape;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
using static CultMath.math;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
//Generates random names based on the statistical weight of letter sequences
|
||||
//in a collection of sample names
|
||||
|
|
@ -128,4 +128,4 @@ public class MarkovNameGenerator
|
|||
int n = _random.NextInt(0,letters.Count);
|
||||
return letters[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Linq;
|
|||
using Ink;
|
||||
using Ink.Parsed;
|
||||
using Path = System.IO.Path;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
using Random = CultMath.Random;
|
||||
using Story = Ink.Runtime.Story;
|
||||
|
||||
public class StoryProcessor : IZoneResolver, IFactionResolver
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Random = Unity.Mathematics.Random;
|
||||
using Random = CultMath.Random;
|
||||
|
||||
public abstract class ZoneSelector
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
public static class Noise1D
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue