Merge cuts loot-1-present and loot-2 (Soul verdicts cut-loot-1-present.s1, cut-loot-2.s2)
Conflicts: Behaviors.cs keeps Union(48, PickupData) and Union(49, LaunchSuppressorData); Zone.cs keeps the departure record on _entities with master's Visibility (emissions-heard); ContactRevealTests.cs keeps master's removal of the emission-departure test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
fbe05c7992
84 changed files with 1881 additions and 657 deletions
|
|
@ -23,6 +23,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4094535758000554889}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
|
|
@ -30,7 +31,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 9133920759869389018}
|
||||
m_Father: {fileID: 8489385781787202308}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &5284461483464579928
|
||||
GameObject:
|
||||
|
|
@ -44,7 +44,6 @@ GameObject:
|
|||
- component: {fileID: 2667259809042191123}
|
||||
- component: {fileID: 2093307059618178533}
|
||||
- component: {fileID: 8815906420840494942}
|
||||
- component: {fileID: 8842807112802394818}
|
||||
- component: {fileID: 8689774408369415534}
|
||||
- component: {fileID: 5705448000916375656}
|
||||
- component: {fileID: -5496574226213385675}
|
||||
|
|
@ -62,6 +61,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5284461483464579928}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 2.76403, y: 0, z: 0}
|
||||
m_LocalScale: {x: 3, y: 3, z: 3}
|
||||
|
|
@ -69,7 +69,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 6463386671854692106}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &2667259809042191123
|
||||
MeshFilter:
|
||||
|
|
@ -96,6 +95,11 @@ MeshRenderer:
|
|||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
|
|
@ -118,9 +122,11 @@ MeshRenderer:
|
|||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!135 &8815906420840494942
|
||||
SphereCollider:
|
||||
|
|
@ -130,29 +136,19 @@ SphereCollider:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5284461483464579928}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
serializedVersion: 3
|
||||
m_Radius: 0.91187096
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &8842807112802394818
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5284461483464579928}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 737f47c158214f0995ba2835f040ed96, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
GridAttraction: 10
|
||||
Gravity: 1
|
||||
Drag: 0.05
|
||||
LaunchDrag: 0.2
|
||||
--- !u!54 &8689774408369415534
|
||||
Rigidbody:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -160,10 +156,21 @@ Rigidbody:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5284461483464579928}
|
||||
serializedVersion: 2
|
||||
serializedVersion: 5
|
||||
m_Mass: 1
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_LinearDamping: 0
|
||||
m_AngularDamping: 0.05
|
||||
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ImplicitCom: 1
|
||||
m_ImplicitTensor: 1
|
||||
m_UseGravity: 0
|
||||
m_IsKinematic: 1
|
||||
m_Interpolate: 0
|
||||
|
|
@ -181,6 +188,8 @@ MonoBehaviour:
|
|||
m_Script: {fileID: 11500000, guid: 0c18f67d85af44c2b5afc8ecd15ce960, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
LabelFadeDuration: 0.5
|
||||
LabelPersistDuration: 3
|
||||
LabelDisplayAngle: 15
|
||||
|
|
@ -204,6 +213,7 @@ PrefabInstance:
|
|||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 6463386671854692106}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2945231343692618236, guid: e7877477dc889804ea9d04f7ffd08e9b,
|
||||
|
|
@ -327,6 +337,9 @@ PrefabInstance:
|
|||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: e7877477dc889804ea9d04f7ffd08e9b, type: 3}
|
||||
--- !u!114 &3355762817961458026 stripped
|
||||
MonoBehaviour:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3193517496555256095}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
|
|
@ -30,7 +31,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 7638477100053754924}
|
||||
m_Father: {fileID: 3419130779256825702}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &7643266616531142933
|
||||
GameObject:
|
||||
|
|
@ -44,7 +44,6 @@ GameObject:
|
|||
- component: {fileID: 3269639735116279291}
|
||||
- component: {fileID: 7427774873968414741}
|
||||
- component: {fileID: 674324171579366365}
|
||||
- component: {fileID: 7449398549621940518}
|
||||
- component: {fileID: 4507103896627202189}
|
||||
- component: {fileID: 648083621841515580}
|
||||
- component: {fileID: -5443764149998352794}
|
||||
|
|
@ -62,6 +61,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7643266616531142933}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 3, y: 3, z: 3}
|
||||
|
|
@ -69,7 +69,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 3601651121922051457}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!33 &3269639735116279291
|
||||
MeshFilter:
|
||||
|
|
@ -96,6 +95,11 @@ MeshRenderer:
|
|||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
|
|
@ -118,9 +122,11 @@ MeshRenderer:
|
|||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!135 &674324171579366365
|
||||
SphereCollider:
|
||||
|
|
@ -130,29 +136,19 @@ SphereCollider:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7643266616531142933}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
serializedVersion: 3
|
||||
m_Radius: 0.8
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &7449398549621940518
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7643266616531142933}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 737f47c158214f0995ba2835f040ed96, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
GridAttraction: 10
|
||||
Gravity: 1
|
||||
Drag: 0.05
|
||||
LaunchDrag: 0.2
|
||||
--- !u!114 &4507103896627202189
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -165,6 +161,8 @@ MonoBehaviour:
|
|||
m_Script: {fileID: 11500000, guid: 0c18f67d85af44c2b5afc8ecd15ce960, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
LabelFadeDuration: 0.5
|
||||
LabelPersistDuration: 3
|
||||
LabelDisplayAngle: 15
|
||||
|
|
@ -178,10 +176,21 @@ Rigidbody:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7643266616531142933}
|
||||
serializedVersion: 2
|
||||
serializedVersion: 5
|
||||
m_Mass: 1
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_LinearDamping: 0
|
||||
m_AngularDamping: 0.05
|
||||
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ImplicitCom: 1
|
||||
m_ImplicitTensor: 1
|
||||
m_UseGravity: 0
|
||||
m_IsKinematic: 1
|
||||
m_Interpolate: 0
|
||||
|
|
@ -204,6 +213,7 @@ PrefabInstance:
|
|||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 3601651121922051457}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2945231343692618236, guid: e7877477dc889804ea9d04f7ffd08e9b,
|
||||
|
|
@ -327,6 +337,9 @@ PrefabInstance:
|
|||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: e7877477dc889804ea9d04f7ffd08e9b, type: 3}
|
||||
--- !u!114 &4202270197902620060 stripped
|
||||
MonoBehaviour:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6430764275721745537}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.7069834, y: -0, z: -0, w: 0.7072302}
|
||||
m_LocalPosition: {x: 0, y: -0.9858858, z: 0.000344038}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
|
|
@ -30,7 +31,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 5373456011921669618}
|
||||
m_Father: {fileID: 7548484292876150345}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &7167547457198401877
|
||||
GameObject:
|
||||
|
|
@ -44,7 +44,6 @@ GameObject:
|
|||
- component: {fileID: 7390838957595368880}
|
||||
- component: {fileID: 6960669510133879758}
|
||||
- component: {fileID: 3507902894264904200}
|
||||
- component: {fileID: 8747099621875196907}
|
||||
- component: {fileID: 1545255963217739444}
|
||||
- component: {fileID: -5409019571952974707}
|
||||
- component: {fileID: -2501508196100734606}
|
||||
|
|
@ -62,6 +61,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7167547457198401877}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.7069834, y: 0, z: 0, w: 0.7072302}
|
||||
m_LocalPosition: {x: -0, y: 0, z: -2.9576573}
|
||||
m_LocalScale: {x: 3, y: 3, z: 3}
|
||||
|
|
@ -69,7 +69,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 4002800527770282731}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: -89.98, y: 0, z: 0}
|
||||
--- !u!33 &7390838957595368880
|
||||
MeshFilter:
|
||||
|
|
@ -96,6 +95,11 @@ MeshRenderer:
|
|||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
|
|
@ -118,9 +122,11 @@ MeshRenderer:
|
|||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!135 &3507902894264904200
|
||||
SphereCollider:
|
||||
|
|
@ -130,29 +136,19 @@ SphereCollider:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7167547457198401877}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
serializedVersion: 3
|
||||
m_Radius: 0.8303433
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &8747099621875196907
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7167547457198401877}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 737f47c158214f0995ba2835f040ed96, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
GridAttraction: 10
|
||||
Gravity: 1
|
||||
Drag: 0.05
|
||||
LaunchDrag: 0.2
|
||||
--- !u!114 &1545255963217739444
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -165,6 +161,8 @@ MonoBehaviour:
|
|||
m_Script: {fileID: 11500000, guid: 0c18f67d85af44c2b5afc8ecd15ce960, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
LabelFadeDuration: 0.5
|
||||
LabelPersistDuration: 3
|
||||
LabelDisplayAngle: 15
|
||||
|
|
@ -178,10 +176,21 @@ Rigidbody:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7167547457198401877}
|
||||
serializedVersion: 2
|
||||
serializedVersion: 5
|
||||
m_Mass: 1
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_LinearDamping: 0
|
||||
m_AngularDamping: 0.05
|
||||
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ImplicitCom: 1
|
||||
m_ImplicitTensor: 1
|
||||
m_UseGravity: 0
|
||||
m_IsKinematic: 1
|
||||
m_Interpolate: 0
|
||||
|
|
@ -204,6 +213,7 @@ PrefabInstance:
|
|||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 4002800527770282731}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2945231343692618236, guid: e7877477dc889804ea9d04f7ffd08e9b,
|
||||
|
|
@ -327,6 +337,9 @@ PrefabInstance:
|
|||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: e7877477dc889804ea9d04f7ffd08e9b, type: 3}
|
||||
--- !u!114 &1928241908402748482 stripped
|
||||
MonoBehaviour:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3932408193141279893}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.7069834, y: -0, z: -0, w: 0.7072302}
|
||||
m_LocalPosition: {x: 0, y: 0.9264834, z: -0.0003232956}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
|
|
@ -30,7 +31,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 7372978312502119311}
|
||||
m_Father: {fileID: 5807113421007930924}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &8555821447211356933
|
||||
GameObject:
|
||||
|
|
@ -44,7 +44,6 @@ GameObject:
|
|||
- component: {fileID: 4899765339662518078}
|
||||
- component: {fileID: 1691511938472729356}
|
||||
- component: {fileID: 3657818610818556929}
|
||||
- component: {fileID: 946329779588188460}
|
||||
- component: {fileID: 7390748043139687254}
|
||||
- component: {fileID: -4611973708976329780}
|
||||
- component: {fileID: -8908388773057984787}
|
||||
|
|
@ -62,6 +61,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8555821447211356933}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.7069834, y: 0, z: 0, w: 0.7072302}
|
||||
m_LocalPosition: {x: -0, y: 0, z: 2.7794502}
|
||||
m_LocalScale: {x: 3, y: 3, z: 3}
|
||||
|
|
@ -69,7 +69,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 2388751103296260666}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: -89.98, y: 0, z: 0}
|
||||
--- !u!33 &4899765339662518078
|
||||
MeshFilter:
|
||||
|
|
@ -96,6 +95,11 @@ MeshRenderer:
|
|||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
|
|
@ -118,9 +122,11 @@ MeshRenderer:
|
|||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!135 &3657818610818556929
|
||||
SphereCollider:
|
||||
|
|
@ -130,29 +136,19 @@ SphereCollider:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8555821447211356933}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
serializedVersion: 3
|
||||
m_Radius: 0.75
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &946329779588188460
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8555821447211356933}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 737f47c158214f0995ba2835f040ed96, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
GridAttraction: 10
|
||||
Gravity: 1
|
||||
Drag: 0.05
|
||||
LaunchDrag: 0.2
|
||||
--- !u!114 &7390748043139687254
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -165,6 +161,8 @@ MonoBehaviour:
|
|||
m_Script: {fileID: 11500000, guid: 0c18f67d85af44c2b5afc8ecd15ce960, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
LabelFadeDuration: 0.5
|
||||
LabelPersistDuration: 3
|
||||
LabelDisplayAngle: 15
|
||||
|
|
@ -178,10 +176,21 @@ Rigidbody:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8555821447211356933}
|
||||
serializedVersion: 2
|
||||
serializedVersion: 5
|
||||
m_Mass: 1
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_LinearDamping: 0
|
||||
m_AngularDamping: 0.05
|
||||
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ImplicitCom: 1
|
||||
m_ImplicitTensor: 1
|
||||
m_UseGravity: 0
|
||||
m_IsKinematic: 1
|
||||
m_Interpolate: 0
|
||||
|
|
@ -204,6 +213,7 @@ PrefabInstance:
|
|||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 2388751103296260666}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2945231343692618236, guid: e7877477dc889804ea9d04f7ffd08e9b,
|
||||
|
|
@ -327,6 +337,9 @@ PrefabInstance:
|
|||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: e7877477dc889804ea9d04f7ffd08e9b, type: 3}
|
||||
--- !u!114 &3891726626588969535 stripped
|
||||
MonoBehaviour:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2238948151922859802}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.7069834, y: -0, z: -0, w: 0.7072302}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
|
|
@ -30,7 +31,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 3300919539099916088}
|
||||
m_Father: {fileID: 8414659055235772867}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &7767746557817481743
|
||||
GameObject:
|
||||
|
|
@ -44,7 +44,6 @@ GameObject:
|
|||
- component: {fileID: 2868629223358676395}
|
||||
- component: {fileID: 6959931328227918623}
|
||||
- component: {fileID: 4540815661693075899}
|
||||
- component: {fileID: 4008709970769624704}
|
||||
- component: {fileID: 7436534270673427838}
|
||||
- component: {fileID: -6589342969031261525}
|
||||
- component: {fileID: 5724758014884646323}
|
||||
|
|
@ -62,6 +61,7 @@ Transform:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7767746557817481743}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0.7069834, y: 0, z: 0, w: 0.7072302}
|
||||
m_LocalPosition: {x: -0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 3, y: 3, z: 3}
|
||||
|
|
@ -69,7 +69,6 @@ Transform:
|
|||
m_Children:
|
||||
- {fileID: 7682789761269032435}
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: -89.98, y: 0, z: 0}
|
||||
--- !u!33 &2868629223358676395
|
||||
MeshFilter:
|
||||
|
|
@ -96,6 +95,11 @@ MeshRenderer:
|
|||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 2
|
||||
m_RayTraceProcedural: 0
|
||||
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||
m_RayTracingAccelStructBuildFlags: 1
|
||||
m_SmallMeshCulling: 1
|
||||
m_ForceMeshLod: -1
|
||||
m_MeshLodSelectionBias: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
|
|
@ -118,9 +122,11 @@ MeshRenderer:
|
|||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_GlobalIlluminationMeshLod: 0
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_MaskInteraction: 0
|
||||
m_AdditionalVertexStreams: {fileID: 0}
|
||||
--- !u!135 &4540815661693075899
|
||||
SphereCollider:
|
||||
|
|
@ -130,29 +136,19 @@ SphereCollider:
|
|||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7767746557817481743}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
serializedVersion: 3
|
||||
m_Radius: 0.60371214
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &4008709970769624704
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7767746557817481743}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 737f47c158214f0995ba2835f040ed96, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
GridAttraction: 10
|
||||
Gravity: 1
|
||||
Drag: 0.05
|
||||
LaunchDrag: 0.2
|
||||
--- !u!114 &7436534270673427838
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -165,6 +161,8 @@ MonoBehaviour:
|
|||
m_Script: {fileID: 11500000, guid: 0c18f67d85af44c2b5afc8ecd15ce960, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
RotationSpeed: 1
|
||||
GridOffset: 0
|
||||
LabelFadeDuration: 0.5
|
||||
LabelPersistDuration: 3
|
||||
LabelDisplayAngle: 15
|
||||
|
|
@ -178,10 +176,21 @@ Rigidbody:
|
|||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7767746557817481743}
|
||||
serializedVersion: 2
|
||||
serializedVersion: 5
|
||||
m_Mass: 1
|
||||
m_Drag: 0
|
||||
m_AngularDrag: 0.05
|
||||
m_LinearDamping: 0
|
||||
m_AngularDamping: 0.05
|
||||
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ImplicitCom: 1
|
||||
m_ImplicitTensor: 1
|
||||
m_UseGravity: 0
|
||||
m_IsKinematic: 1
|
||||
m_Interpolate: 0
|
||||
|
|
@ -204,6 +213,7 @@ PrefabInstance:
|
|||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 7682789761269032435}
|
||||
m_Modifications:
|
||||
- target: {fileID: 2945231343692618236, guid: e7877477dc889804ea9d04f7ffd08e9b,
|
||||
|
|
@ -327,6 +337,9 @@ PrefabInstance:
|
|||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: e7877477dc889804ea9d04f7ffd08e9b, type: 3}
|
||||
--- !u!224 &3300919539099916088 stripped
|
||||
RectTransform:
|
||||
|
|
|
|||
|
|
@ -16404,13 +16404,13 @@ MonoBehaviour:
|
|||
LODHandler: {fileID: 1009503736}
|
||||
SlimeRenderer: {fileID: 1254851254}
|
||||
SlimeGravityCamera: {fileID: 1066823975}
|
||||
SimpleCommodityPickup: {fileID: 4008709970769624704, guid: 521bfeece7dd5fd4e8f166d38493f18c,
|
||||
SimpleCommodityPickup: {fileID: 7436534270673427838, guid: 521bfeece7dd5fd4e8f166d38493f18c,
|
||||
type: 3}
|
||||
CompoundCommodityPickup: {fileID: 7449398549621940518, guid: 366909b6a01d7254d8eef01c333f8e00,
|
||||
CompoundCommodityPickup: {fileID: 4507103896627202189, guid: 366909b6a01d7254d8eef01c333f8e00,
|
||||
type: 3}
|
||||
GearPickup: {fileID: 946329779588188460, guid: d2b7e9a9cd7733041b0553682e4b61af,
|
||||
GearPickup: {fileID: 7390748043139687254, guid: d2b7e9a9cd7733041b0553682e4b61af,
|
||||
type: 3}
|
||||
WeaponPickup: {fileID: 8842807112802394818, guid: d2cec1d8c3d94074e9d2e6088e1c02d7,
|
||||
WeaponPickup: {fileID: 5705448000916375656, guid: d2cec1d8c3d94074e9d2e6088e1c02d7,
|
||||
type: 3}
|
||||
MinePrefab: {fileID: 7283754118921506074, guid: b04728b2b65e057429eb6e8529299c8a,
|
||||
type: 3}
|
||||
|
|
|
|||
227
Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs
Normal file
227
Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GameCult.Caching;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using float2 = CultMath.float2;
|
||||
|
||||
// Batch proof that ZoneRenderer draws Zone.FloatingItems and nothing else, in a play session with no scene of its own:
|
||||
// the pickup prefabs are the ones ARPG.unity's ZoneRenderer names (so the wiring is read here too), a zone is loaded
|
||||
// into a fresh ZoneRenderer, and the zone's own Release, step, expiry and TryPickUp are what move and remove the
|
||||
// pickups. Zone.Release adds exactly one ItemPickup, placed at the body's position; stepping the zone moves it with the
|
||||
// body; expiry and TryPickUp each destroy it; an item already in the zone when the renderer loads it is drawn too.
|
||||
//
|
||||
// Unity -batchmode -nographics -projectPath <project> -executeMethod FloatingItemsDrawnSmoke.Run -logFile <log>
|
||||
//
|
||||
// Do not pass -quit: Run enters play mode (a domain reload), the check runs there, and the Editor exits itself, 0 on
|
||||
// success and 1 on any failure. Destroy only takes effect in a play session, which is why this is not an edit-mode check.
|
||||
[InitializeOnLoad]
|
||||
public static class FloatingItemsDrawnSmoke
|
||||
{
|
||||
private const string Flag = "FloatingItemsDrawnSmoke.running";
|
||||
private const string Wiring = "FloatingItemsDrawnSmoke.wiring";
|
||||
private static IEnumerator _check;
|
||||
private static int _frames;
|
||||
private static string _work;
|
||||
|
||||
static FloatingItemsDrawnSmoke()
|
||||
{
|
||||
if (SessionState.GetBool(Flag, false)) EditorApplication.playModeStateChanged += OnPlayMode;
|
||||
}
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
SessionState.SetString(Wiring, ReadWiring());
|
||||
SessionState.SetBool(Flag, true);
|
||||
EditorApplication.EnterPlaymode();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
EditorApplication.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnPlayMode(PlayModeStateChange change)
|
||||
{
|
||||
if (change != PlayModeStateChange.EnteredPlayMode) return;
|
||||
_work = Path.Combine(Path.GetTempPath(), $"floating-items-smoke-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_work);
|
||||
_check = Check();
|
||||
EditorApplication.update += Step;
|
||||
}
|
||||
|
||||
private static void Step()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (++_frames > 3000) throw new InvalidOperationException("The check did not finish in 3000 frames.");
|
||||
if (_check.MoveNext()) return;
|
||||
Finish(0);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
Finish(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Finish(int code)
|
||||
{
|
||||
EditorApplication.update -= Step;
|
||||
SessionState.SetBool(Flag, false);
|
||||
try { if (Directory.Exists(_work)) Directory.Delete(_work, true); } catch (Exception) { /* a temp directory */ }
|
||||
EditorApplication.Exit(code);
|
||||
}
|
||||
|
||||
private static void Require(bool condition, string what)
|
||||
{
|
||||
if (!condition) throw new InvalidOperationException(what);
|
||||
}
|
||||
|
||||
private static IEnumerator Check()
|
||||
{
|
||||
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
|
||||
?? throw new InvalidOperationException("No Settings.asset.");
|
||||
|
||||
// The wiring: the scene's ZoneRenderer names an ItemPickup prefab in each of the four fields, and none of those
|
||||
// prefabs keeps a script that no longer exists.
|
||||
var wired = SessionState.GetString(Wiring, "").Split('|').Select(entry => entry.Split('=')).Select(pair =>
|
||||
(Field: pair[0], Pickup: pair[1] == "" ? null : AssetDatabase.LoadAssetAtPath<GameObject>(pair[1])?.GetComponent<ItemPickup>())).ToArray();
|
||||
Require(wired.Length == 4, "The wiring was not carried into the play session.");
|
||||
foreach (var (name, pickup) in wired)
|
||||
{
|
||||
Require(pickup != null, $"ZoneRenderer.{name} is not an ItemPickup in ARPG.unity.");
|
||||
Require(pickup.ScanLabel != null && pickup.ScanLabelContainer != null, $"{name}'s prefab has no scan label.");
|
||||
Require(!pickup.GetComponentsInChildren<Component>(true).Any(c => c == null), $"{name}'s prefab has a missing script.");
|
||||
}
|
||||
|
||||
var shape = new Shape(5, 5);
|
||||
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
|
||||
var cargoShape = new Shape(4, 4);
|
||||
foreach (var cell in cargoShape.AllCoordinates) cargoShape[cell] = true;
|
||||
var oneCell = new Shape(1, 1);
|
||||
oneCell[new CultMath.int2(0, 0)] = true;
|
||||
var cache = AetheriaStores.Open(Path.Combine(_work, "smoke.cc"), catalogWritable: true);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.Upsert(new HullData { Name = "Smoke Hub", HullType = HullType.Station, Shape = shape, Durability = 1000, Mass = 1000 });
|
||||
cache.Upsert(new CargoBayData { Name = "Smoke Crate", Shape = new Shape(), InteriorShape = cargoShape, Durability = 4 });
|
||||
cache.Upsert(new GearData
|
||||
{
|
||||
Name = "Smoke Spare", Hardpoint = HardpointType.Tool, Shape = oneCell, Durability = 1,
|
||||
MinimumTemperature = -1000, MaximumTemperature = 1000, OptimalTemperature = 0, PlateauWidth = 2000
|
||||
});
|
||||
cache.Upsert(new SimpleCommodityData { Name = "Smoke Ore", Shape = oneCell, MaxStack = 100 });
|
||||
cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), null, null);
|
||||
EquippableItem Make(string name, int lot) => new EquippableItem
|
||||
{
|
||||
Data = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<EquippableItemData>(name)), Durability = 1, Lot = lot
|
||||
};
|
||||
|
||||
// A station with a crate takes the picked-up stack. It is not admitted: the zone holds no entity for the renderer to draw.
|
||||
var hubRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hub"));
|
||||
var taker = new OrbitalEntity(items, zone, new EquippableItem { Data = hubRef, Durability = 1000, Lot = 1 }, default, new EntitySettings());
|
||||
Require(taker.TryEquip(Make("Smoke Crate", 2), new CultMath.int2(1, 1)), "The smoke station took no crate.");
|
||||
|
||||
// An item already in the zone when the renderer loads it: drawn from the load, not only from the add.
|
||||
var early = zone.Release(Make("Smoke Spare", 3), new float2(5, 6), float2.zero);
|
||||
|
||||
// The renderer's side. Inactive while it is configured, so it runs no Awake or Start of the scene's own.
|
||||
var rootObject = new GameObject("Smoke Zone Root");
|
||||
var rendererObject = new GameObject("Smoke Zone Renderer");
|
||||
rendererObject.SetActive(false);
|
||||
var renderer = rendererObject.AddComponent<ZoneRenderer>();
|
||||
renderer.ZoneRoot = rootObject.transform;
|
||||
foreach (var (name, pickup) in wired)
|
||||
typeof(ZoneRenderer).GetField(name).SetValue(renderer, pickup);
|
||||
renderer.ItemManager = items;
|
||||
renderer.SectorBrushes = new GameObject("Smoke Brushes").transform;
|
||||
renderer.SlimeGravityCamera = new GameObject("Smoke Gravity Camera").AddComponent<Camera>();
|
||||
var slimeObject = new GameObject("Smoke Slime");
|
||||
slimeObject.SetActive(false); // the real Slime needs its compute shader; LoadZone only sets its radius
|
||||
renderer.SlimeRenderer = slimeObject.AddComponent<Slime>();
|
||||
renderer.LoadZone(zone);
|
||||
yield return null;
|
||||
|
||||
ItemPickup[] Pickups() => rootObject.GetComponentsInChildren<ItemPickup>(true).Where(p => p != null).ToArray();
|
||||
void At(ItemPickup pickup, FloatingItem floating, string what)
|
||||
{
|
||||
var at = floating.Body.Position;
|
||||
var want = new Vector3(at.x, zone.GetHeight(at), at.y);
|
||||
Require(Vector3.Distance(pickup.transform.position, want) < 1e-3f,
|
||||
$"{what}: the pickup is at {pickup.transform.position}, the body at {want}.");
|
||||
}
|
||||
|
||||
Require(Pickups().Length == 1, $"load: {Pickups().Length} pickups for one existing floating item.");
|
||||
At(Pickups()[0], early, "load");
|
||||
|
||||
// Release adds exactly one pickup, at the body's position, bound to its item.
|
||||
var ore = new SimpleCommodity { Data = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<SimpleCommodityData>("Smoke Ore")), Quantity = 7 };
|
||||
var released = zone.Release(ore, new float2(3, 4), new float2(10, 0));
|
||||
yield return null;
|
||||
Require(Pickups().Length == 2, $"release: {Pickups().Length} pickups for two floating items.");
|
||||
var drawn = Pickups().Single(p => p.Item == ore);
|
||||
At(drawn, released, "release");
|
||||
|
||||
// Stepping the zone moves the sim body; the pickup follows it on its next update, with nothing of its own.
|
||||
var before = drawn.transform.position;
|
||||
zone.Update(.5f);
|
||||
yield return null;
|
||||
Require(released.Body.Position.x > 3.5f, "the sim body did not drift.");
|
||||
At(drawn, released, "step");
|
||||
Require(drawn.transform.position != before, "the pickup did not move with the body.");
|
||||
|
||||
// Pickup destroys exactly the picked-up item's pickup.
|
||||
Require(zone.TryPickUp(taker, released.Id), "the station refused a one-cell stack.");
|
||||
yield return null;
|
||||
yield return null;
|
||||
Require(drawn == null, "pickup: the pickup is still there.");
|
||||
Require(Pickups().Length == 1 && Pickups()[0].Item == early.Item, "pickup: the wrong pickup went.");
|
||||
|
||||
// Expiry destroys the pickup of an item nobody took.
|
||||
zone.Update(3600f); // far past any lifetime
|
||||
yield return null;
|
||||
yield return null;
|
||||
Require(zone.FloatingItems.Count == 0, "expiry: the item did not expire.");
|
||||
Require(Pickups().Length == 0, $"expiry: {Pickups().Length} pickups left.");
|
||||
|
||||
// A later release is drawn again, and clearing the renderer takes what is left.
|
||||
zone.Release(Make("Smoke Spare", 4), new float2(1, 1), float2.zero);
|
||||
yield return null;
|
||||
Require(Pickups().Length == 1, "re-release: not drawn.");
|
||||
renderer.ClearZone();
|
||||
yield return null;
|
||||
yield return null;
|
||||
Require(Pickups().Length == 0, "clear: pickups left after ClearZone.");
|
||||
Debug.Log("FLOATING_ITEMS_SMOKE ok");
|
||||
}
|
||||
|
||||
// What ARPG.unity's ZoneRenderer names in each pickup field, read from the saved scene before the play session (a
|
||||
// scene cannot be opened inside one) and carried across the domain reload as the prefabs' asset paths.
|
||||
private static string ReadWiring()
|
||||
{
|
||||
var scene = EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Additive);
|
||||
try
|
||||
{
|
||||
var found = UnityEngine.Object.FindObjectsByType<ZoneRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None)
|
||||
.Where(r => r.gameObject.scene == scene).ToArray();
|
||||
Require(found.Length == 1, $"ARPG.unity has {found.Length} ZoneRenderers.");
|
||||
var wired = found[0];
|
||||
return string.Join("|", new[]
|
||||
{
|
||||
("SimpleCommodityPickup", wired.SimpleCommodityPickup), ("CompoundCommodityPickup", wired.CompoundCommodityPickup),
|
||||
("GearPickup", wired.GearPickup), ("WeaponPickup", wired.WeaponPickup)
|
||||
}.Select(f => f.Item1 + "=" + (f.Item2 == null ? "" : AssetDatabase.GetAssetPath(f.Item2))));
|
||||
}
|
||||
finally { EditorSceneManager.CloseScene(scene, true); }
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs.meta
Normal file
2
Assets/Scripts/Editor/FloatingItemsDrawnSmoke.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 67544a932f794a2458741c731ef04d11
|
||||
|
|
@ -64,10 +64,8 @@ public static class ImpactPresenterSmoke
|
|||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
|
||||
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
|
||||
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
|
||||
zone.Entities.Add(ship);
|
||||
zone.Entities.Add(other);
|
||||
ship.Activate();
|
||||
other.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
zone.Admit(other, piloted: false);
|
||||
|
||||
// The scene's side: the tuning object with a pooled effect, and the game manager that names the player's ship.
|
||||
var feedbackObject = new GameObject("Smoke Impact Feedback");
|
||||
|
|
|
|||
75
Assets/Scripts/Editor/WireFloatingItems.cs
Normal file
75
Assets/Scripts/Editor/WireFloatingItems.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
// Strips the deleted integrator component from the pickup prefabs and points ZoneRenderer's four pickup fields in ARPG.unity at
|
||||
// each prefab's ItemPickup, then saves the prefabs and the scene. Idempotent: a prefab with no missing script is left as
|
||||
// it is, and the fields are set to the same values again.
|
||||
//
|
||||
// Unity -batchmode -projectPath <project> -executeMethod WireFloatingItems.Run -logFile <log>
|
||||
//
|
||||
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure. The menu item runs the same wiring.
|
||||
public static class WireFloatingItems
|
||||
{
|
||||
private const string ScenePath = "Assets/Scenes/ARPG.unity";
|
||||
private const string PickupFolder = "Assets/Prefabs/RPG/Pickups/";
|
||||
// The field each prefab filled before this cut (read from ARPG.unity at the cut's base). Icosahedron fills none.
|
||||
private static readonly (string Field, string Prefab)[] Fields =
|
||||
{
|
||||
("SimpleCommodityPickup", "Tetrahedron"),
|
||||
("CompoundCommodityPickup", "Hexahedron"),
|
||||
("GearPickup", "Octahedron"),
|
||||
("WeaponPickup", "Dodecahedron"),
|
||||
};
|
||||
private static readonly string[] Prefabs = { "Tetrahedron", "Hexahedron", "Octahedron", "Dodecahedron", "Icosahedron" };
|
||||
|
||||
[MenuItem("Aetheria/Wire Floating Items")]
|
||||
public static void Menu() => Wire();
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
Wire();
|
||||
EditorApplication.Exit(0);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
EditorApplication.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Wire()
|
||||
{
|
||||
foreach (var name in Prefabs)
|
||||
{
|
||||
var path = PickupFolder + name + ".prefab";
|
||||
var contents = PrefabUtility.LoadPrefabContents(path);
|
||||
try
|
||||
{
|
||||
var removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(contents);
|
||||
if (contents.GetComponent<ItemPickup>() == null) throw new InvalidOperationException($"{path} has no ItemPickup.");
|
||||
if (removed > 0) PrefabUtility.SaveAsPrefabAsset(contents, path);
|
||||
Debug.Log($"WIRE_FLOATING_ITEMS {name} removed={removed}");
|
||||
}
|
||||
finally { PrefabUtility.UnloadPrefabContents(contents); }
|
||||
}
|
||||
|
||||
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
|
||||
var renderer = UnityEngine.Object.FindFirstObjectByType<ZoneRenderer>();
|
||||
if (renderer == null) throw new InvalidOperationException("ARPG.unity has no ZoneRenderer.");
|
||||
var serialized = new SerializedObject(renderer);
|
||||
foreach (var (field, prefab) in Fields)
|
||||
{
|
||||
var pickup = AssetDatabase.LoadAssetAtPath<GameObject>(PickupFolder + prefab + ".prefab").GetComponent<ItemPickup>();
|
||||
var property = serialized.FindProperty(field) ?? throw new InvalidOperationException($"ZoneRenderer has no {field}.");
|
||||
property.objectReferenceValue = pickup;
|
||||
}
|
||||
serialized.ApplyModifiedProperties();
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
if (!EditorSceneManager.SaveScene(scene)) throw new InvalidOperationException("The scene did not save.");
|
||||
Debug.Log("WIRE_FLOATING_ITEMS ok");
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Editor/WireFloatingItems.cs.meta
Normal file
2
Assets/Scripts/Editor/WireFloatingItems.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9a56d7dc8471d224f9bed434b39f7c87
|
||||
|
|
@ -739,7 +739,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
if (CurrentEntity != null)
|
||||
{
|
||||
CurrentEntity.Deactivate();
|
||||
CurrentEntity.Zone.Entities.Remove(CurrentEntity);
|
||||
CurrentEntity.Zone.Remove(CurrentEntity);
|
||||
CurrentEntity.Zone = Zone;
|
||||
Zone.Admit(CurrentEntity, piloted: false);
|
||||
}
|
||||
|
|
@ -1332,9 +1332,7 @@ public class ActionGameManager : MonoBehaviour
|
|||
TargetShieldsFill.fillAmount = target.Shield == null ? 0 : lerp(.25f, .75f, target.Shield.Progress);
|
||||
}
|
||||
|
||||
var tractorPower = Input.Player.TractorBeam.ReadValue<float>();
|
||||
CurrentEntity.TractorPower =
|
||||
saturate(CurrentEntity.TractorPower + sign(tractorPower - CurrentEntity.TractorPower) * Time.deltaTime * 2);
|
||||
CurrentEntity.TractorCommand = Input.Player.TractorBeam.ReadValue<float>();
|
||||
}
|
||||
}
|
||||
// One clock steps the zone, paused or not: a paused frame feeds it no real time.
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ using UnityEngine;
|
|||
public class CapabilityPresentationBinder : MonoBehaviour
|
||||
{
|
||||
private readonly List<IDisposable> _subscriptions = new List<IDisposable>();
|
||||
private Func<int, Transform> _grabTargetResolver;
|
||||
private Func<int, Vector3> _grabInitialVelocityResolver;
|
||||
private Func<FloatingBodyId, Transform> _grabTargetResolver;
|
||||
private Func<FloatingBodyId, Vector3> _grabInitialVelocityResolver;
|
||||
|
||||
// Subscribes every presenter interface implemented by components on this object or its children to
|
||||
// the matching stream of `events`. `grabTargetResolver`/`grabInitialVelocityResolver` resolve a
|
||||
// GrabEvent's plain target handle to the live Transform/velocity a grab presenter needs; the owner
|
||||
// GrabEvent's target id to the live Transform/velocity a grab presenter needs; the owner
|
||||
// of `events` supplies them (the test scene today; the real pickup capability's caller later).
|
||||
public void Bind(CapabilityEvents events, Func<int, Transform> grabTargetResolver = null,
|
||||
Func<int, Vector3> grabInitialVelocityResolver = null)
|
||||
public void Bind(CapabilityEvents events, Func<FloatingBodyId, Transform> grabTargetResolver = null,
|
||||
Func<FloatingBodyId, Vector3> grabInitialVelocityResolver = null)
|
||||
{
|
||||
Unbind();
|
||||
if (events == null) return;
|
||||
|
|
@ -40,8 +40,8 @@ public class CapabilityPresentationBinder : MonoBehaviour
|
|||
var p = presenter;
|
||||
_subscriptions.Add(events.Grab.Subscribe(e =>
|
||||
{
|
||||
var target = _grabTargetResolver?.Invoke(e.TargetHandle);
|
||||
var velocity = _grabInitialVelocityResolver?.Invoke(e.TargetHandle) ?? Vector3.zero;
|
||||
var target = _grabTargetResolver?.Invoke(e.Target);
|
||||
var velocity = _grabInitialVelocityResolver?.Invoke(e.Target) ?? Vector3.zero;
|
||||
p.Grab(e, target, velocity);
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public interface IAbsorbPresenter
|
|||
}
|
||||
|
||||
// Grab needs a live Transform to animate, which the engine-free GrabEvent cannot carry (its
|
||||
// TargetHandle is a plain identity, not a Unity reference). The binder resolves the handle through a
|
||||
// Target is a plain identity, not a Unity reference). The binder resolves the id through a
|
||||
// delegate supplied by the event source's owner and hands the presenter the result, along with the
|
||||
// initial velocity the target was already moving with (also owner-supplied, for the same reason).
|
||||
public interface IGrabPresenter
|
||||
|
|
|
|||
|
|
@ -301,27 +301,6 @@ public class EntityInstance : MonoBehaviour
|
|||
{
|
||||
if (!this) return;
|
||||
_destroyed = true;
|
||||
foreach (var gear in Entity.Equipment)
|
||||
{
|
||||
if (gear != Entity.EquippedHull && Random.value < ZoneRenderer.Settings.LootDropProbability)
|
||||
{
|
||||
ZoneRenderer.DropItem(
|
||||
Entity.Position.ToUnity(),
|
||||
Random.onUnitSphere * ZoneRenderer.Settings.LootDropVelocity,
|
||||
gear.EquippableItem);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var cargo in Entity.CargoBays)
|
||||
{
|
||||
foreach (var item in cargo.Cargo.Keys)
|
||||
{
|
||||
ZoneRenderer.DropItem(
|
||||
Entity.Position.ToUnity(),
|
||||
Random.onUnitSphere * ZoneRenderer.Settings.LootDropVelocity,
|
||||
item);
|
||||
}
|
||||
}
|
||||
if (DestroyEffect != null)
|
||||
{
|
||||
var t = Instantiate(DestroyEffect).transform;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ using UnityEngine;
|
|||
public class GameSettings : ScriptableObject
|
||||
{
|
||||
public string StartingHullName = "Longinus";
|
||||
public float PickupLifetime = 30;
|
||||
public float LootDropProbability = .25f;
|
||||
public float LootDropVelocity = 25;
|
||||
public float HeatstrokePhasingFloor = 0;
|
||||
public float HeatstrokePhasingFrequency = 5;
|
||||
public float WormholeDistanceRatio;
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
using System;
|
||||
using CultMath;
|
||||
using CultMath.UnityBridge;
|
||||
using UnityEngine;
|
||||
using static CultMath.math;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
public class GridObject : MonoBehaviour
|
||||
{
|
||||
public float RotationSpeed;
|
||||
public float GridOffset;
|
||||
public float GridAttraction;
|
||||
public float Gravity;
|
||||
public float Drag = .1f;
|
||||
public float LaunchDrag;
|
||||
|
||||
private float2 _selfVelocity;
|
||||
private float _timeOffset;
|
||||
|
||||
public Zone Zone { get; set; }
|
||||
public Vector3 Velocity { get; set; }
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_timeOffset = Random.value * 100;
|
||||
}
|
||||
|
||||
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().ToCultMath()) + GridOffset;
|
||||
Velocity += Vector3.up * (sign(gridHeight - position.y) * GridAttraction * Time.deltaTime);
|
||||
Velocity *= max(0, 1 - LaunchDrag * Time.deltaTime);
|
||||
var normal = Zone.GetNormal(position.Flatland().ToCultMath());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 737f47c158214f0995ba2835f040ed96
|
||||
timeCreated: 1618441439
|
||||
|
|
@ -4,9 +4,15 @@ using CultMath;
|
|||
using CultMath.UnityBridge;
|
||||
using UnityEngine;
|
||||
using static CultMath.math;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
// How one floating item looks. Zone owns the item's every fact (existence, position, expiry, pickup); once bound,
|
||||
// this reads the latest sim body and draws, spun for show, and writes nothing back (rulings unity-presents-only-r3
|
||||
// and sim-speed-presentation). An unbound pickup (FieldTester's test scene) leaves its transform alone.
|
||||
public class ItemPickup : MonoBehaviour
|
||||
{
|
||||
public float RotationSpeed = 1f;
|
||||
public float GridOffset;
|
||||
public float LabelFadeDuration = .5f;
|
||||
public float LabelPersistDuration = 3;
|
||||
public float LabelDisplayAngle = 15;
|
||||
|
|
@ -15,14 +21,38 @@ public class ItemPickup : MonoBehaviour
|
|||
public TextMeshPro ScanLabel;
|
||||
|
||||
public ItemInstance Item { get; set; }
|
||||
public ZoneRenderer ZoneRenderer { get; set; }
|
||||
public float3 ViewOrigin { get; set; }
|
||||
public float3 ViewDirection { get; set; }
|
||||
|
||||
private float _displayTime;
|
||||
private float _timeOffset;
|
||||
private Zone _zone;
|
||||
private FloatingItem _floating;
|
||||
|
||||
public void Bind(Zone zone, FloatingItem floating)
|
||||
{
|
||||
_zone = zone;
|
||||
_floating = floating;
|
||||
Item = floating.Item;
|
||||
_timeOffset = Random.value * 100;
|
||||
Place();
|
||||
}
|
||||
|
||||
private void Place()
|
||||
{
|
||||
var p = _floating.Body.Position;
|
||||
transform.position = new Vector3(p.x, _zone.GetHeight(p) + GridOffset, p.y);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_floating != null)
|
||||
{
|
||||
Place();
|
||||
transform.localRotation = Quaternion.Euler(
|
||||
sin(Time.time - _timeOffset * RotationSpeed) * 90, 0, cos(Time.time - _timeOffset * RotationSpeed) * 90);
|
||||
}
|
||||
|
||||
var diff = transform.position.ToCultMath() - ViewOrigin;
|
||||
var toThis = normalize(diff);
|
||||
var viewAngle = acos(dot(toThis, ViewDirection)) * Mathf.Rad2Deg;
|
||||
|
|
@ -34,9 +64,4 @@ public class ItemPickup : MonoBehaviour
|
|||
ScanLabel.color = c;
|
||||
ScanLabelContainer.rotation = Quaternion.LookRotation((-toThis).ToUnity());
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ZoneRenderer?.DestroyLoot(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,40 +52,6 @@ public class ShieldManager : MonoBehaviour, IAbsorbPresenter
|
|||
Debug.LogWarning("ShieldManager has no ShieldEnvelope; falling back to its own transform for the hit direction.", this);
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision other)
|
||||
{
|
||||
var otherShield = other.collider.GetComponent<ShieldManager>();
|
||||
if (!otherShield)
|
||||
{
|
||||
var gridObject = other.collider.GetComponent<GridObject>();
|
||||
if (gridObject)
|
||||
{
|
||||
var itemPickup = other.collider.GetComponent<ItemPickup>();
|
||||
if (itemPickup)
|
||||
{
|
||||
if (Entity.CargoBays.Any(c => c.TryStore(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
|
||||
{
|
||||
Debug.Log("Shield collision occurred with grid object, but other collider isn't an item pickup!");
|
||||
}
|
||||
}
|
||||
else
|
||||
Debug.Log("Shield collision occurred, but other collider isn't a shield or a grid object!");
|
||||
}
|
||||
}
|
||||
|
||||
// A shot the shield took ripples it where the shot landed, drawn on the hull as it is drawn. The hull layer is
|
||||
// ImpactPresenter's.
|
||||
public void Absorb(AbsorbEvent e)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -14,20 +14,9 @@ public class TractorBeam : MonoBehaviour
|
|||
public void Update()
|
||||
{
|
||||
transform.rotation = Quaternion.LookRotation(Direction);
|
||||
// The beam draws nothing until it presents a sim grab (loot-3): no particles, and no pull; the simulation
|
||||
// decides every floating item's motion.
|
||||
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 = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -189,6 +189,7 @@ public interface IPowerConsumer
|
|||
Union(45, typeof(MunitionSeekerData)),
|
||||
Union(46, typeof(MunitionControlData)),
|
||||
Union(47, typeof(MunitionFuelData)),
|
||||
Union(48, typeof(PickupData)),
|
||||
Union(49, typeof(LaunchSuppressorData)),
|
||||
JsonObject(MemberSerialization.OptIn)]
|
||||
public abstract class BehaviorData
|
||||
|
|
|
|||
171
Assets/Scripts/ServerShared/Behaviors/Pickup.cs
Normal file
171
Assets/Scripts/ServerShared/Behaviors/Pickup.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using CultMath;
|
||||
using MessagePack;
|
||||
using Newtonsoft.Json;
|
||||
using static CultMath.math;
|
||||
using float2 = CultMath.float2;
|
||||
|
||||
// The tractor beam: the capability that lets a ship collect floating items. It spins its beam up in sim time toward
|
||||
// the pilot's command, and once the beam is up claims the free item nearest its axis and runs one timed grab
|
||||
// (extend, envelop, pull) that ends in Zone.TryPickUp, the one commit of a pickup. Its stats are flat
|
||||
// (Min == Max), as the other behaviours that carry their own timings.
|
||||
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
|
||||
public class PickupData : BehaviorData
|
||||
{
|
||||
// How far along the beam's axis an item can be claimed, and how far off the axis.
|
||||
[Inspectable, JsonProperty("range"), Key(1), RuntimeInspectable]
|
||||
public PerformanceStat Range = new PerformanceStat();
|
||||
|
||||
[Inspectable, JsonProperty("beamRadius"), Key(2), RuntimeInspectable]
|
||||
public PerformanceStat BeamRadius = new PerformanceStat();
|
||||
|
||||
// The spin-up (0..1) needed to claim an item, and to keep a grab going.
|
||||
[Inspectable, JsonProperty("grabPower"), Key(3), RuntimeInspectable]
|
||||
public PerformanceStat GrabPower = new PerformanceStat();
|
||||
|
||||
// Seconds of each phase of a grab.
|
||||
[Inspectable, JsonProperty("extendTime"), Key(4), RuntimeInspectable]
|
||||
public PerformanceStat ExtendTime = new PerformanceStat();
|
||||
|
||||
[Inspectable, JsonProperty("envelopTime"), Key(5), RuntimeInspectable]
|
||||
public PerformanceStat EnvelopTime = new PerformanceStat();
|
||||
|
||||
[Inspectable, JsonProperty("pullTime"), Key(6), RuntimeInspectable]
|
||||
public PerformanceStat PullTime = new PerformanceStat();
|
||||
|
||||
// Spin-up and spin-down, in power per sim second.
|
||||
[Inspectable, JsonProperty("spinRate"), Key(7), RuntimeInspectable]
|
||||
public PerformanceStat SpinRate = new PerformanceStat();
|
||||
|
||||
public override Behavior CreateInstance(EquippedItem item)
|
||||
{
|
||||
return new Pickup(this, item);
|
||||
}
|
||||
|
||||
// A beam is a piece of gear the pilot holds on; no consumable carries one.
|
||||
public override Behavior CreateInstance(ConsumableItemEffect item) =>
|
||||
throw new NotSupportedException("pickup: gear only");
|
||||
}
|
||||
|
||||
// Owns this ship's beam power and its one grab. Power is written only here; Zone owns the held item's pose.
|
||||
public class Pickup : Behavior, IAlwaysUpdatedBehavior
|
||||
{
|
||||
private readonly PickupData _data;
|
||||
// Power and the grab clock are kept in double so a fixed tick count lands on the same side of a boundary
|
||||
// whatever dt the zone steps by.
|
||||
private double _power;
|
||||
private bool _grabbing;
|
||||
private FloatingBodyId _target;
|
||||
private double _elapsed;
|
||||
// How many of Envelop and Pull have been published so far (0, 1 or 2).
|
||||
private int _published;
|
||||
|
||||
public Pickup(PickupData data, EquippedItem item) : base(data, item)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
// The beam's spun-up state in [0, 1]: what the pilot's command has driven it to.
|
||||
public float Power => (float) _power;
|
||||
|
||||
public void Update(float dt)
|
||||
{
|
||||
var active = Item.Active.Value;
|
||||
var command = active ? Math.Clamp(Entity.TractorCommand, 0f, 1f) : 0f;
|
||||
var step = (double) Evaluate(_data.SpinRate) * dt;
|
||||
_power = Math.Clamp(_power + Math.Clamp(command - _power, -step, step), 0.0, 1.0);
|
||||
|
||||
var zone = Entity.Zone;
|
||||
var grabPower = Evaluate(_data.GrabPower);
|
||||
var extend = (double) Evaluate(_data.ExtendTime);
|
||||
var envelop = (double) Evaluate(_data.EnvelopTime);
|
||||
var pull = (double) Evaluate(_data.PullTime);
|
||||
var range = Evaluate(_data.Range);
|
||||
var radius = Evaluate(_data.BeamRadius);
|
||||
var origin = Entity.Position.xz;
|
||||
|
||||
if (!_grabbing)
|
||||
{
|
||||
if (!active || _power < grabPower) return;
|
||||
var item = Select(zone, origin, range, radius);
|
||||
if (item == null || !zone.TryHold(Entity, item.Id)) return;
|
||||
_grabbing = true;
|
||||
_target = item.Id;
|
||||
_elapsed = 0;
|
||||
_published = 0;
|
||||
Publish(GrabPhase.Started, item, 0);
|
||||
Publish(GrabPhase.Extend, item, (float) extend);
|
||||
return;
|
||||
}
|
||||
|
||||
var held = zone.FloatingItems.FirstOrDefault(f => f.Id.Equals(_target));
|
||||
if (held == null || !ReferenceEquals(held.Holder, Entity))
|
||||
{
|
||||
_grabbing = false;
|
||||
Entity.Capabilities.PublishGrab(new GrabEvent(_target, GrabPhase.Cancelled, float3(0), 0));
|
||||
return;
|
||||
}
|
||||
|
||||
var pulling = _elapsed >= extend + envelop;
|
||||
if (!active || _power < grabPower || (!pulling && !InBeam(held.Body.Position, origin, range, radius, out _)))
|
||||
{
|
||||
_grabbing = false;
|
||||
zone.Free(_target);
|
||||
Publish(GrabPhase.Cancelled, held, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
_elapsed += dt;
|
||||
if (_published < 1 && _elapsed >= extend) { _published = 1; Publish(GrabPhase.Envelop, held, (float) envelop); }
|
||||
if (_published < 2 && _elapsed >= extend + envelop) { _published = 2; Publish(GrabPhase.Pull, held, (float) pull); }
|
||||
if (_published == 2)
|
||||
zone.SetHeldProgress(_target, pull > 0 ? (float) ((_elapsed - extend - envelop) / pull) : 1f);
|
||||
if (_elapsed < extend + envelop + pull) return;
|
||||
|
||||
_grabbing = false;
|
||||
if (zone.TryPickUp(Entity, _target)) Publish(GrabPhase.Completed, held, 0);
|
||||
else
|
||||
{
|
||||
zone.Free(_target);
|
||||
Publish(GrabPhase.Cancelled, held, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// The one beam region: within Range along the aim axis (never behind) and BeamRadius off it. Selection claims
|
||||
// from it and the grab holds on to its item only while the item stays in it. A zero aim has no region.
|
||||
private bool InBeam(float2 position, float2 origin, float range, float radius, out float off)
|
||||
{
|
||||
off = 0;
|
||||
var aim = Entity.Aim.xz;
|
||||
if (lengthsq(aim) <= 0) return false;
|
||||
var axis = normalize(aim);
|
||||
var to = position - origin;
|
||||
var along = dot(to, axis);
|
||||
off = length(to - axis * along);
|
||||
return along >= 0 && along <= range && off <= radius;
|
||||
}
|
||||
|
||||
// The free item nearest the beam axis within the beam region that some cargo bay can store whole (the bay's own
|
||||
// TryFindSpace, the test TryStore opens with); ties go to the lowest id. A zero aim selects nothing.
|
||||
private FloatingItem Select(Zone zone, float2 origin, float range, float radius)
|
||||
{
|
||||
FloatingItem best = null;
|
||||
var bestOff = float.MaxValue;
|
||||
foreach (var floating in zone.FloatingItems)
|
||||
{
|
||||
if (floating.Holder != null) continue;
|
||||
if (!InBeam(floating.Body.Position, origin, range, radius, out var off)) continue;
|
||||
if (!Entity.CargoBays.Any(bay => bay.TryFindSpace(floating.Item))) continue;
|
||||
if (off < bestOff || (off == bestOff && floating.Id.Value < best.Id.Value))
|
||||
{
|
||||
best = floating;
|
||||
bestOff = off;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private void Publish(GrabPhase phase, FloatingItem item, float duration) =>
|
||||
Entity.Capabilities.PublishGrab(new GrabEvent(item.Id, phase, float3(item.Body.Position.x, Entity.Position.y, item.Body.Position.y), duration));
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/Behaviors/Pickup.cs.meta
Normal file
11
Assets/Scripts/ServerShared/Behaviors/Pickup.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ac7678d240564331be7486588d28c5ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -52,19 +52,18 @@ public enum GrabPhase
|
|||
Cancelled
|
||||
}
|
||||
|
||||
// The target is identified by a plain handle, not a Unity type: the simulation will own the real
|
||||
// target identity (a loot drop or item instance id) once the pickup capability exists. Today's only
|
||||
// publisher (the FieldShieldTest scene) hands out its own handles; nothing here interprets them.
|
||||
// One phase of a grab. The target is the floating item the Pickup behaviour holds (the FieldShieldTest scene
|
||||
// publishes its own handles as ids); nothing here interprets it.
|
||||
public readonly struct GrabEvent
|
||||
{
|
||||
public readonly int TargetHandle;
|
||||
public readonly FloatingBodyId Target;
|
||||
public readonly GrabPhase Phase;
|
||||
public readonly float3 TargetPosition;
|
||||
public readonly float PhaseDuration;
|
||||
|
||||
public GrabEvent(int targetHandle, GrabPhase phase, float3 targetPosition, float phaseDuration)
|
||||
public GrabEvent(FloatingBodyId target, GrabPhase phase, float3 targetPosition, float phaseDuration)
|
||||
{
|
||||
TargetHandle = targetHandle;
|
||||
Target = target;
|
||||
Phase = phase;
|
||||
TargetPosition = targetPosition;
|
||||
PhaseDuration = phaseDuration;
|
||||
|
|
|
|||
|
|
@ -153,7 +153,11 @@ public abstract class Entity
|
|||
|
||||
public bool OverrideShutdown { get; set; }
|
||||
|
||||
public float TractorPower { get; set; }
|
||||
// The pilot's tractor command (0..1); the Pickup behaviour spins its beam toward it.
|
||||
public float TractorCommand;
|
||||
|
||||
// The beam's spun-up state: Pickup's, or 0 without one.
|
||||
public float TractorPower => GetBehavior<Pickup>()?.Power ?? 0f;
|
||||
|
||||
public bool Active
|
||||
{
|
||||
|
|
@ -1549,7 +1553,7 @@ public abstract class Entity
|
|||
{
|
||||
bay.DockedShip = ship;
|
||||
ship.SetParent(this);
|
||||
Zone.Entities.Remove(ship);
|
||||
Zone.Remove(ship);
|
||||
ship.Deactivate();
|
||||
ship.Docked.OnNext(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ public struct KinematicBody
|
|||
}
|
||||
|
||||
// An item adrift in a zone: a death drop or jettisoned cargo. Zone.Release is its only adder; Zone's floating-item
|
||||
// step (expiry) and TryPickUp are its only removers. Runtime state, never saved.
|
||||
// step (expiry) and TryPickUp are its only removers. Zone.TryHold and Free are the only writers of Holder, the
|
||||
// entity grabbing it with a tractor beam; a held item is placed by Zone, not drifted. Runtime state, never saved.
|
||||
public sealed class FloatingItem
|
||||
{
|
||||
public FloatingBodyId Id;
|
||||
|
|
@ -50,4 +51,9 @@ public sealed class FloatingItem
|
|||
public KinematicBody Body;
|
||||
public double ReleasedAt;
|
||||
public float Lifetime;
|
||||
// The entity holding it, or null when it floats free; HeldFrom is where it was claimed and HeldProgress how far
|
||||
// along its pull to the holder (0..1).
|
||||
public Entity Holder;
|
||||
public float2 HeldFrom;
|
||||
public float HeldProgress;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ public class LoadoutGenerator
|
|||
var entity = new Ship(ItemManager, null, hull, ItemManager.GameplaySettings.DefaultEntitySettings);
|
||||
entity.Faction = Faction;
|
||||
OutfitEntity(entity);
|
||||
EquipPickup(entity);
|
||||
StockConsumables(entity, ShipConsumableChance, ShipConsumableUnits);
|
||||
return EntitySerializer.Pack(entity);
|
||||
}
|
||||
|
|
@ -287,6 +288,24 @@ public class LoadoutGenerator
|
|||
FillInterior(entity);
|
||||
}
|
||||
|
||||
// Every ship carries a tractor beam: picking up needs a pickup behaviour (scenarios ruling, "everyone gets tractor
|
||||
// beams"). It goes in after the shared outfitting, which stations and turrets also run and which adds none, into
|
||||
// whatever interior the hull has left; a hull with no cell for one cannot be generated.
|
||||
private void EquipPickup(Entity entity)
|
||||
{
|
||||
var emptyShape = entity.UnoccupiedSpace;
|
||||
var (product, design) = RandomProduct<GearData>(2, item =>
|
||||
item.Behaviors.Any(b => b is PickupData) &&
|
||||
item.Shape.FitsWithin(emptyShape, out _, out _), required: true);
|
||||
if (design == null) throw new InvalidLoadoutException("No compatible tractor beam found for ship!");
|
||||
|
||||
design.Shape.FitsWithin(emptyShape, out var rotation, out var position);
|
||||
var beam = ItemManager.CreateInstance(product) as EquippableItem;
|
||||
beam.Rotation = rotation;
|
||||
if (!entity.TryEquip(beam, position))
|
||||
throw new InvalidLoadoutException("Failed to equip selected tractor beam!");
|
||||
}
|
||||
|
||||
private void EquipHardpoints(Entity entity)
|
||||
{
|
||||
var hullData = ItemManager.GetData(entity.Hull) as HullData;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,29 @@ using float2 = CultMath.float2;
|
|||
public class Zone
|
||||
{
|
||||
public Action<string> Log;
|
||||
public ReactiveCollection<Entity> Entities = new ReactiveCollection<Entity>();
|
||||
|
||||
// Membership, in admission order. Admit and Remove are the only writers; everyone else reads this view, which has
|
||||
// no mutating member, so the death watch below follows membership by construction.
|
||||
private readonly ReactiveCollection<Entity> _entities = new ReactiveCollection<Entity>();
|
||||
public MemberView Entities { get; }
|
||||
|
||||
public sealed class MemberView : IReadOnlyReactiveCollection<Entity>
|
||||
{
|
||||
private readonly ReactiveCollection<Entity> _members;
|
||||
internal MemberView(ReactiveCollection<Entity> members) => _members = members;
|
||||
public int Count => _members.Count;
|
||||
public Entity this[int index] => _members[index];
|
||||
public int IndexOf(Entity entity) => _members.IndexOf(entity);
|
||||
public bool Contains(Entity entity) => _members.Contains(entity);
|
||||
public IEnumerator<Entity> GetEnumerator() => _members.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
public UniRx.IObservable<CollectionAddEvent<Entity>> ObserveAdd() => _members.ObserveAdd();
|
||||
public UniRx.IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false) => _members.ObserveCountChanged(notifyCurrentCount);
|
||||
public UniRx.IObservable<CollectionMoveEvent<Entity>> ObserveMove() => _members.ObserveMove();
|
||||
public UniRx.IObservable<CollectionRemoveEvent<Entity>> ObserveRemove() => _members.ObserveRemove();
|
||||
public UniRx.IObservable<CollectionReplaceEvent<Entity>> ObserveReplace() => _members.ObserveReplace();
|
||||
public UniRx.IObservable<Unit> ObserveReset() => _members.ObserveReset();
|
||||
}
|
||||
|
||||
// Laid mines, in lay order. Zone owns their existence: Lay is the one adder, the mine step the one remover,
|
||||
// and every removal is a blast. Runtime state, never saved.
|
||||
|
|
@ -26,7 +48,8 @@ public class Zone
|
|||
public ReactiveCollection<VapourCloud> Clouds = new ReactiveCollection<VapourCloud>();
|
||||
|
||||
// Floating items (death drops, jettisoned cargo), in release order. Zone owns their existence: Release is the one
|
||||
// adder; StepFloatingItems (expiry) and TryPickUp are the only removers. Runtime state, never saved.
|
||||
// adder; StepFloatingItems (expiry) and TryPickUp are the only removers. TryHold, Free and Remove-of-the-holder
|
||||
// are the only writers of FloatingItem.Holder. Runtime state, never saved.
|
||||
public ReactiveCollection<FloatingItem> FloatingItems = new ReactiveCollection<FloatingItem>();
|
||||
public Dictionary<CultRecordKey, BodyData> Planets = new Dictionary<CultRecordKey, BodyData>();
|
||||
public Dictionary<CultRecordKey, Planet> PlanetInstances = new Dictionary<CultRecordKey, Planet>();
|
||||
|
|
@ -105,30 +128,13 @@ public class Zone
|
|||
// The death ordinal of the loot dice: the nth death in this zone rolls on SimulationDice.For(CombatSeed, LootStream, n).
|
||||
private uint _deaths;
|
||||
|
||||
// One death subscription per member entity, owned here: a membership record and its subscription are created
|
||||
// together, and only when the entity is not already a member. Leaving Entities (Remove) or resetting it (Clear)
|
||||
// disposes it; nothing else touches it.
|
||||
// One death subscription per member entity: Admit creates it together with the membership, Remove disposes it
|
||||
// together with the membership, and nothing else touches either.
|
||||
private readonly Dictionary<Entity, IDisposable> _deathWatches = new Dictionary<Entity, IDisposable>();
|
||||
|
||||
private void WatchDeath(Entity entity)
|
||||
{
|
||||
if (_deathWatches.ContainsKey(entity)) return;
|
||||
_deathWatches.Add(entity, entity.Death.Subscribe(_ => { DropLoot(entity); Entities.Remove(entity); entity.Deactivate(); }));
|
||||
}
|
||||
|
||||
private void UnwatchDeath(Entity entity)
|
||||
{
|
||||
if (_deathWatches.Remove(entity, out var watch)) watch.Dispose();
|
||||
}
|
||||
|
||||
private void UnwatchAll()
|
||||
{
|
||||
foreach (var watch in _deathWatches.Values) watch.Dispose();
|
||||
_deathWatches.Clear();
|
||||
}
|
||||
|
||||
public Zone(ItemManager itemManager, PlanetSettings settings, ZonePack pack, GalaxyZone galaxyZone, Galaxy galaxy)
|
||||
{
|
||||
Entities = new MemberView(_entities);
|
||||
_time = pack.Time;
|
||||
GalaxyZone = galaxyZone;
|
||||
Galaxy = galaxy;
|
||||
|
|
@ -144,11 +150,8 @@ public class Zone
|
|||
// ObserveAdd fires for all of them. Forbidden writer: no presentation may remove an entity from Zone.
|
||||
// The death watch lives exactly as long as the entity's membership in this zone: installed on admission,
|
||||
// disposed on removal (death, dock, jump), so an entity admitted again never holds a second one.
|
||||
Entities.ObserveAdd().Subscribe(add => WatchDeath(add.Value));
|
||||
Entities.ObserveRemove().Subscribe(remove => UnwatchDeath(remove.Value));
|
||||
Entities.ObserveReset().Subscribe(_ => UnwatchAll());
|
||||
// The one record of a removal for observers (ruling reveal-ends-on-perception): read by the next Sense pass.
|
||||
Entities.ObserveRemove().Subscribe(remove => _departures.Add(new Departure(remove.Value, remove.Value.Position.xz,
|
||||
_entities.ObserveRemove().Subscribe(remove => _departures.Add(new Departure(remove.Value, remove.Value.Position.xz,
|
||||
remove.Value.Visibility)));
|
||||
|
||||
Targets.Add(new EntityTargets());
|
||||
|
|
@ -205,17 +208,32 @@ public class Zone
|
|||
// TODO: Associate planets with stored entities for planetary colonies
|
||||
}
|
||||
|
||||
// The one admission into this zone (docs/scenarios-cut.md, 2.2): the only writer of Entities.Add and the only
|
||||
// The one admission into this zone (docs/scenarios-cut.md, 2.2): the only writer of membership and the only
|
||||
// creator of agents. Construction, new-run staging (RunStart), warp arrival, undock and the console's spawned
|
||||
// turret all join through here. A piloted entity gets the zone's Minion; an unpiloted one gets no agent at all.
|
||||
// Only a ship can be piloted. The Death subscription above covers every join, whichever caller admits it.
|
||||
// Only a ship can be piloted. The death watch is installed here, for every join, whichever caller admits it.
|
||||
// An entity that is already a member is refused: it would hold a second watch and a second agent.
|
||||
public void Admit(Entity entity, bool piloted)
|
||||
{
|
||||
Entities.Add(entity);
|
||||
if (_entities.Contains(entity)) throw new InvalidOperationException("entity: already a member of this zone");
|
||||
_entities.Add(entity);
|
||||
_deathWatches.Add(entity, entity.Death.Subscribe(_ => { DropLoot(entity); Remove(entity); entity.Deactivate(); }));
|
||||
entity.Activate();
|
||||
if (piloted) Agents.Add(CreateAgent((Ship) entity));
|
||||
}
|
||||
|
||||
// The one removal from this zone (death, dock, jump): membership and its death watch leave together. False, and
|
||||
// nothing changes, when the entity is not a member. The caller deactivates; Remove does not.
|
||||
public bool Remove(Entity entity)
|
||||
{
|
||||
if (!_entities.Remove(entity)) return false;
|
||||
_deathWatches.Remove(entity, out var watch);
|
||||
watch.Dispose();
|
||||
foreach (var floating in FloatingItems)
|
||||
if (ReferenceEquals(floating.Holder, entity)) Free(floating.Id);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Agent CreateAgent(Ship ship)
|
||||
{
|
||||
var agent = new Minion(ship);
|
||||
|
|
@ -231,7 +249,7 @@ public class Zone
|
|||
{
|
||||
Radius = Pack.Radius,
|
||||
Mass = Pack.Mass,
|
||||
Entities = Entities.Select(EntitySerializer.Pack).ToList(),
|
||||
Entities = _entities.Select(EntitySerializer.Pack).ToList(),
|
||||
Orbits = Orbits.Keys.Select(key => new CultRecordRef<OrbitData>(key)).ToList(),
|
||||
Planets = Planets.Keys.Select(key => new CultRecordRef<BodyData>(key)).ToList(),
|
||||
Time = _time,
|
||||
|
|
@ -315,14 +333,44 @@ public class Zone
|
|||
|
||||
// The one commit of a pickup: the first of the taker's cargo bays that accepts the item stores it and the item
|
||||
// stops floating. Otherwise nothing changes.
|
||||
// An item held by another entity is refused: one holder per item holds at the commit as well.
|
||||
public bool TryPickUp(Entity taker, FloatingBodyId id)
|
||||
{
|
||||
var floating = FloatingItems.FirstOrDefault(f => f.Id.Equals(id));
|
||||
if (floating == null || !taker.CargoBays.Any(bay => bay.TryStore(floating.Item))) return false;
|
||||
if (floating == null || (floating.Holder != null && !ReferenceEquals(floating.Holder, taker))) return false;
|
||||
if (!taker.CargoBays.Any(bay => bay.TryStore(floating.Item))) return false;
|
||||
FloatingItems.Remove(floating);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The tractor beam's claim on a floating item: false if it is absent or already held. The item stays where it is
|
||||
// until its pull begins; StepFloatingItems places it from then on.
|
||||
public bool TryHold(Entity holder, FloatingBodyId id)
|
||||
{
|
||||
var floating = FloatingItems.FirstOrDefault(f => f.Id.Equals(id));
|
||||
if (floating == null || floating.Holder != null) return false;
|
||||
floating.Holder = holder;
|
||||
floating.HeldFrom = floating.Body.Position;
|
||||
floating.HeldProgress = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// How far along its pull to the holder a held item is, 0..1.
|
||||
public void SetHeldProgress(FloatingBodyId id, float progress)
|
||||
{
|
||||
var floating = FloatingItems.FirstOrDefault(f => f.Id.Equals(id));
|
||||
if (floating?.Holder != null) floating.HeldProgress = saturate(progress);
|
||||
}
|
||||
|
||||
// Lets a held item go: it floats on from where it is with the Velocity and Drift it had.
|
||||
public void Free(FloatingBodyId id)
|
||||
{
|
||||
var floating = FloatingItems.FirstOrDefault(f => f.Id.Equals(id));
|
||||
if (floating == null) return;
|
||||
floating.Holder = null;
|
||||
floating.HeldProgress = 0;
|
||||
}
|
||||
|
||||
// Called from the death subscription, before the entity leaves the zone: one generator per death. Each equipped
|
||||
// item but the hull drops on a LootDropProbability draw; every cargo stack drops whole. Each drop leaves from the
|
||||
// dead entity's position at a random heading. Draw order: Equipment order (the probability draw, then the heading
|
||||
|
|
@ -344,12 +392,18 @@ public class Zone
|
|||
}
|
||||
|
||||
// After StepClouds and before FireControl.Step: each floating item drifts as a KinematicBody on the zone's force
|
||||
// and is removed once its lifetime has run out.
|
||||
// and is removed once its lifetime has run out. A held item is neither integrated nor expired: it is placed
|
||||
// between where it was claimed and its holder, now that the entities have moved.
|
||||
private void StepFloatingItems(float deltaTime)
|
||||
{
|
||||
var settings = _itemManager.GameplaySettings;
|
||||
foreach (var floating in FloatingItems.ToArray())
|
||||
{
|
||||
if (floating.Holder != null)
|
||||
{
|
||||
floating.Body.Position = lerp(floating.HeldFrom, floating.Holder.Position.xz, smoothstep(0f, 1f, floating.HeldProgress));
|
||||
continue;
|
||||
}
|
||||
floating.Body.Step(deltaTime, GetForce(floating.Body.Position), settings);
|
||||
if (floating.ReleasedAt + floating.Lifetime <= _time) FloatingItems.Remove(floating);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ public class FieldTester : MonoBehaviour
|
|||
Debug.LogError("FieldTester expects a CapabilityPresentationBinder on TestField's GameObject; no capability events will reach the field.");
|
||||
else
|
||||
binder.Bind(_capabilityEvents,
|
||||
grabTargetResolver: handle => handle >= 0 && handle < _pickups.Count ? _pickups[handle].transform : null,
|
||||
grabInitialVelocityResolver: handle => Vector3.forward *
|
||||
(PickupTravelDistance / lerp(PickupTravelTimeMin, PickupTravelTimeMax, _pickups[handle].lerp)));
|
||||
grabTargetResolver: id => (int) id.Value >= 0 && (int) id.Value < _pickups.Count ? _pickups[(int) id.Value].transform : null,
|
||||
grabInitialVelocityResolver: id => Vector3.forward *
|
||||
(PickupTravelDistance / lerp(PickupTravelTimeMin, PickupTravelTimeMax, _pickups[(int) id.Value].lerp)));
|
||||
|
||||
// Impact absorption (fork L): what used to be FieldDriver's own click handler is now this
|
||||
// scene's stand-in for whatever will eventually detect a hit and publish AbsorbEvent.
|
||||
|
|
@ -102,7 +102,7 @@ public class FieldTester : MonoBehaviour
|
|||
if (!TestField.CanGrab) return;
|
||||
click.Clear();
|
||||
var p = _pickups[i1];
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(i1, GrabPhase.Started, p.transform.position.ToCultMath(), 0));
|
||||
_capabilityEvents.PublishGrab(new GrabEvent(new FloatingBodyId((uint) i1), GrabPhase.Started, p.transform.position.ToCultMath(), 0));
|
||||
p.transform = null;
|
||||
_pickups[i1] = p;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ public class ZoneRenderer : MonoBehaviour
|
|||
public LODHandler LODHandler;
|
||||
public Slime SlimeRenderer;
|
||||
public Camera SlimeGravityCamera;
|
||||
public GridObject SimpleCommodityPickup;
|
||||
public GridObject CompoundCommodityPickup;
|
||||
public GridObject GearPickup;
|
||||
public GridObject WeaponPickup;
|
||||
public ItemPickup SimpleCommodityPickup;
|
||||
public ItemPickup CompoundCommodityPickup;
|
||||
public ItemPickup GearPickup;
|
||||
public ItemPickup WeaponPickup;
|
||||
public MineInstance MinePrefab;
|
||||
public VapourCloudInstance CloudPrefab;
|
||||
public Material[] MapGravityMaterials;
|
||||
|
|
@ -94,7 +94,7 @@ public class ZoneRenderer : MonoBehaviour
|
|||
private bool _showAsteroidUI;
|
||||
|
||||
public Dictionary<Wormhole, (GameObject gravity, CompassIcon icon)> WormholeInstances = new Dictionary<Wormhole, (GameObject, CompassIcon)>();
|
||||
private List<ItemPickup> _loot = new List<ItemPickup>();
|
||||
private Dictionary<FloatingItem, ItemPickup> _floatingItems = new Dictionary<FloatingItem, ItemPickup>();
|
||||
private Dictionary<Mine, MineInstance> _mines = new Dictionary<Mine, MineInstance>();
|
||||
private Dictionary<VapourCloud, VapourCloudInstance> _clouds = new Dictionary<VapourCloud, VapourCloudInstance>();
|
||||
|
||||
|
|
@ -236,6 +236,15 @@ public class ZoneRenderer : MonoBehaviour
|
|||
instance.Blast();
|
||||
}));
|
||||
|
||||
// Every floating item, whoever released it (a death drop, jettisoned cargo), is drawn through this one
|
||||
// subscription. An item leaves Zone.FloatingItems by expiry or by pickup, and either way its pickup is gone.
|
||||
foreach (var floating in zone.FloatingItems) LoadFloatingItem(zone, floating);
|
||||
_zoneSubscriptions.Add(zone.FloatingItems.ObserveAdd().Subscribe(e => LoadFloatingItem(zone, e.Value)));
|
||||
_zoneSubscriptions.Add(zone.FloatingItems.ObserveRemove().Subscribe(e =>
|
||||
{
|
||||
if (_floatingItems.Remove(e.Value, out var pickup) && pickup) Destroy(pickup.gameObject);
|
||||
}));
|
||||
|
||||
// Every vapour cloud, whoever vented it, is drawn through this one subscription. A cloud leaves Zone.Clouds
|
||||
// when its opacity has reached nothing, so a removal destroys its fog bank with nothing left to show.
|
||||
foreach (var cloud in zone.Clouds) LoadCloud(zone, cloud);
|
||||
|
|
@ -274,6 +283,29 @@ public class ZoneRenderer : MonoBehaviour
|
|||
_mines.Add(mine, instance);
|
||||
}
|
||||
|
||||
private void LoadFloatingItem(Zone zone, FloatingItem floating)
|
||||
{
|
||||
var prefab = floating.Item switch
|
||||
{
|
||||
SimpleCommodity _ => SimpleCommodityPickup,
|
||||
CompoundCommodity _ => CompoundCommodityPickup,
|
||||
EquippableItem equippableItem when ItemManager.GetData(equippableItem) is WeaponItemData => WeaponPickup,
|
||||
EquippableItem _ => GearPickup,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
var pickup = Instantiate(prefab, ZoneRoot);
|
||||
pickup.Bind(zone, floating);
|
||||
pickup.ScanLabel.text = ItemManager.Title(floating.Item);
|
||||
if (floating.Item is CraftedItemInstance craftedItemInstance)
|
||||
{
|
||||
var c = ItemManager.GetTier(craftedItemInstance).tier.Color.ToColor();
|
||||
c.a = 0;
|
||||
pickup.ScanLabel.color = c;
|
||||
}
|
||||
else pickup.ScanLabel.color = new Color(.75f, .75f, .75f, 0);
|
||||
_floatingItems.Add(floating, pickup);
|
||||
}
|
||||
|
||||
private void LoadCloud(Zone zone, VapourCloud cloud)
|
||||
{
|
||||
var instance = Instantiate(CloudPrefab);
|
||||
|
|
@ -312,9 +344,9 @@ public class ZoneRenderer : MonoBehaviour
|
|||
if (instance) Destroy(instance.gameObject);
|
||||
_clouds.Clear();
|
||||
|
||||
foreach(var gridObject in _loot)
|
||||
if(gridObject) Destroy(gridObject.gameObject);
|
||||
_loot.Clear();
|
||||
foreach (var pickup in _floatingItems.Values)
|
||||
if (pickup) Destroy(pickup.gameObject);
|
||||
_floatingItems.Clear();
|
||||
|
||||
foreach (var entity in Zone.Entities)
|
||||
{
|
||||
|
|
@ -487,7 +519,7 @@ public class ZoneRenderer : MonoBehaviour
|
|||
void Update()
|
||||
{
|
||||
var maxDepth = 0f;
|
||||
foreach (var loot in _loot)
|
||||
foreach (var loot in _floatingItems.Values)
|
||||
{
|
||||
loot.ViewOrigin = PerspectiveEntity.Position;
|
||||
loot.ViewDirection = PerspectiveEntity.Aim;
|
||||
|
|
@ -603,41 +635,6 @@ public class ZoneRenderer : MonoBehaviour
|
|||
// MinimapGravityQuad.transform.position = gravPos;
|
||||
// MinimapTintQuad.transform.position = gravPos - Vector3.up*10;
|
||||
}
|
||||
|
||||
public void DestroyLoot(ItemPickup loot)
|
||||
{
|
||||
_loot.Remove(loot);
|
||||
}
|
||||
|
||||
public void DropItem(Vector3 position, Vector3 velocity, ItemInstance item)
|
||||
{
|
||||
var gridObject = item switch
|
||||
{
|
||||
SimpleCommodity _ => Instantiate(SimpleCommodityPickup),
|
||||
CompoundCommodity _ => Instantiate(CompoundCommodityPickup),
|
||||
EquippableItem equippableItem when ItemManager.GetData(equippableItem) is WeaponItemData => Instantiate(WeaponPickup),
|
||||
EquippableItem _ => Instantiate(GearPickup),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
var t = gridObject.transform;
|
||||
t.parent = ZoneRoot;
|
||||
gridObject.Zone = Zone;
|
||||
t.position = position;
|
||||
gridObject.Velocity = velocity;
|
||||
var itemPickup = gridObject.gameObject.GetComponent<ItemPickup>();
|
||||
itemPickup.Item = item;
|
||||
itemPickup.ZoneRenderer = this;
|
||||
itemPickup.ScanLabel.text = ItemManager.Title(item);
|
||||
if (item is CraftedItemInstance craftedItemInstance)
|
||||
{
|
||||
var c = ItemManager.GetTier(craftedItemInstance).tier.Color.ToColor();
|
||||
c.a = 0;
|
||||
itemPickup.ScanLabel.color = c;
|
||||
}
|
||||
else itemPickup.ScanLabel.color = new Color(.75f, .75f, .75f, 0);
|
||||
gridObject.gameObject.AddComponent<TimedDestroy>().Duration = Settings.PickupLifetime;
|
||||
_loot.Add(itemPickup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
BIN
GameData/Aetheria.cc
(Stored with Git LFS)
BIN
GameData/Aetheria.cc
(Stored with Git LFS)
Binary file not shown.
|
|
@ -113,11 +113,10 @@ public sealed class BrownoutTests : IDisposable
|
|||
// not flight, and a lone off-centre thruster could not fire without turning.
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Consumer")), new int2(2, 1)));
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
// Ship.Turn stays 0, so Ship.Update commands no rotation: no incidental torque thrust from the
|
||||
// ship trying to turn. Aim steers nothing, so this one is only a plausible aim.
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
return ship;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public sealed class CapabilityEventsTests
|
|||
events.Thrust.Subscribe(thrust.Add);
|
||||
|
||||
var absorbEvent = new AbsorbEvent(float3(1, 2, 3), float3(0, 0, 1), 5f, DamageType.Corrosive, AbsorbLayer.Hull);
|
||||
var grabEvent = new GrabEvent(7, GrabPhase.Started, float3(1, 0, 0), 1.5f);
|
||||
var grabEvent = new GrabEvent(new FloatingBodyId(7), GrabPhase.Started, float3(1, 0, 0), 1.5f);
|
||||
var meleeEvent = new MeleeEvent(float3(0, 0, 1), 10f, 90f, 0.5f);
|
||||
var thrustEvent = new ThrustEvent(float2(1, 0), 0.5f, float2(0.2f, -0.2f));
|
||||
|
||||
|
|
|
|||
|
|
@ -96,9 +96,8 @@ public sealed class ConditionRatioTests : IDisposable
|
|||
// not flight, and a lone off-centre thruster could not fire without turning.
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Consumer")), new int2(2, 1)));
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Aim = float3(0, 0, 1); // Turn stays 0, as in BrownoutTests
|
||||
ship.Activate();
|
||||
consumer = ship.Equipment.Single(e => e.Data.Name == "Consumer");
|
||||
Assert.True(ship.GetBehavior<Thruster>().Torque == 0f, "the lone thruster turns nothing");
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ public sealed class ConsumableSupplyTests : IDisposable
|
|||
var stranger = seed.Upsert(new Faction { Name = "Stranger", ShortName = "STR" });
|
||||
maker = seed.Upsert(new Faction { Name = "Maker", ShortName = "MKR", Allegiance = { { ally, 1f } } });
|
||||
|
||||
var skiff = seed.Upsert(new HullData { Name = "Skiff", HullType = HullType.Ship, Shape = Solid(4, 3), Price = 100 });
|
||||
// Interior for the crate, the capacitor and the tractor beam every generated ship carries.
|
||||
var skiff = seed.Upsert(new HullData { Name = "Skiff", HullType = HullType.Ship, Shape = Solid(5, 3), Price = 100 });
|
||||
var platform = seed.Upsert(new HullData { Name = "Platform", HullType = HullType.Station, Shape = Solid(5, 5), Price = 100 });
|
||||
// A hold with no cell cannot take a consumable.
|
||||
var crate = seed.Upsert(new CargoBayData
|
||||
|
|
@ -93,6 +94,7 @@ public sealed class ConsumableSupplyTests : IDisposable
|
|||
});
|
||||
var dock = seed.Upsert(new DockingBayData { Name = "Dock", Shape = new Shape(), Price = 5, InteriorShape = holds == Holds.DockRefuses ? new Shape(1, 1) : Solid(1, 1) });
|
||||
var capacitor = seed.Upsert(new GearData { Name = "Cap", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new CapacitorData() } });
|
||||
var tractor = seed.Upsert(new GearData { Name = "Tractor", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new PickupData() } });
|
||||
var heater = seed.Upsert(new GearData
|
||||
{
|
||||
Name = "Heater", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1,
|
||||
|
|
@ -114,7 +116,7 @@ public sealed class ConsumableSupplyTests : IDisposable
|
|||
foreach (var (name, design) in new[]
|
||||
{
|
||||
("Skiff", skiff.Key), ("Platform", platform.Key), ("Crate", crate.Key), ("Dock", dock.Key),
|
||||
("Cap", capacitor.Key), ("Heater", heater.Key), ("Orphan", orphan.Key)
|
||||
("Cap", capacitor.Key), ("Tractor", tractor.Key), ("Heater", heater.Key), ("Orphan", orphan.Key)
|
||||
}.Concat(sold))
|
||||
Sell(name + " by Maker", design, maker);
|
||||
|
||||
|
|
@ -333,11 +335,13 @@ public sealed class ConsumableSupplyTests : IDisposable
|
|||
}
|
||||
|
||||
// What the plain catalog leaves in both generators' Random, measured on the tree before consumables existed
|
||||
// (origin/master d4a27def, the same fixture and seeds). The gated and plain catalogs agreeing is not enough:
|
||||
// a step that drew unconditionally would draw in both. This pins the value itself.
|
||||
// (origin/master d4a27def, the same fixture and seeds), plus the one product draw each of the three generated
|
||||
// ships makes for its tractor beam (loot-2): with that draw taken out the tree leaves the d4a27def values
|
||||
// 0.85624266, 0.037778974, 0.7931649, 0.3596493, 0.17511952, 0.6642336, 0.31897342, 0.5977364. The gated and
|
||||
// plain catalogs agreeing is not enough: a step that drew unconditionally would draw in both. This pins the value itself.
|
||||
var beforeConsumables = new[]
|
||||
{
|
||||
0.85624266f, 0.037778974f, 0.7931649f, 0.3596493f, 0.17511952f, 0.6642336f, 0.31897342f, 0.5977364f
|
||||
0.3596493f, 0.03616327f, 0.8790466f, 0.16682059f, 0.5977364f, 0.08633745f, 0.2869571f, 0.6890472f
|
||||
};
|
||||
var plainAfter = AfterGeneration(plain);
|
||||
Assert.Equal(beforeConsumables, plainAfter);
|
||||
|
|
|
|||
|
|
@ -83,8 +83,7 @@ public sealed class ConsumableTests : IDisposable
|
|||
var ship = new Ship(items, zone, Gear(cache.Get(hullRef)), new EntitySettings());
|
||||
Assert.True(ship.TryEquip(Gear(cache.Get(engineRef)), EngineCell));
|
||||
Assert.True(ship.TryEquip(Gear(cache.Get(holdRef)), HoldCell));
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
var data = cache.Get(consumableRef);
|
||||
return new Rig
|
||||
{
|
||||
|
|
|
|||
|
|
@ -230,8 +230,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
Assert.True(ship.TryFindSpace(item, out var at));
|
||||
Assert.True(ship.TryEquip(item, at));
|
||||
}
|
||||
_zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
_zone.Admit(ship, piloted: false);
|
||||
ship.Position = position;
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
_zone.Update(0f); // brings the gear online
|
||||
|
|
@ -603,8 +602,8 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
{
|
||||
switch (how)
|
||||
{
|
||||
case "remove": zone.Entities.Remove(e); break;
|
||||
case "warp": e.Deactivate(); zone.Entities.Remove(e); break;
|
||||
case "remove": zone.Remove(e); break;
|
||||
case "warp": e.Deactivate(); zone.Remove(e); break;
|
||||
case "death": e.Hull.Durability = 0f; e.HullDamage.OnNext(1f); break;
|
||||
default: throw new Exception("fixture: " + how);
|
||||
}
|
||||
|
|
@ -643,7 +642,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
Assert.True(e.Visibility * o.PassiveInfo(e.Position.xz, 1f) <= _items.GameplaySettings.TargetDetectionInfoThreshold, "fixture: its last light is below o's gate");
|
||||
Assert.Equal(2, times.Count);
|
||||
|
||||
_zone.Entities.Remove(e);
|
||||
_zone.Remove(e);
|
||||
Assert.Equal(2, o.RevealsOf(e).Count());
|
||||
|
||||
var lapsed = false;
|
||||
|
|
@ -729,7 +728,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
var gate = _items.GameplaySettings.TargetDetectionInfoThreshold / o.PassiveInfo(e.Position.xz, 1f);
|
||||
ShowOnly(e, gate * against, Guns(e)[0]);
|
||||
|
||||
_zone.Entities.Remove(e);
|
||||
_zone.Remove(e);
|
||||
Tick(.1f);
|
||||
|
||||
Assert.Equal(ended ? 0 : 1, o.RevealsOf(e).Count());
|
||||
|
|
@ -799,7 +798,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
ShowOnly(e, threshold * 8f, Guns(e)[0]);
|
||||
Assert.True(e.Visibility * o.PassiveInfo(e.Position.xz, 1f) == threshold, "fixture: the ship's light at removal is exactly the gate");
|
||||
|
||||
_zone.Entities.Remove(e);
|
||||
_zone.Remove(e);
|
||||
Tick(.1f);
|
||||
|
||||
Assert.Single(o.RevealsOf(e));
|
||||
|
|
@ -816,8 +815,9 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
Tick(1f); // the gun cools down
|
||||
ShowOnly(e, 1000f, gun); // bright enough for any gate
|
||||
|
||||
_zone.Entities.Remove(e);
|
||||
_zone.Entities.Add(e);
|
||||
_zone.Remove(e);
|
||||
e.Deactivate();
|
||||
_zone.Admit(e, piloted: false);
|
||||
Tick(.1f);
|
||||
|
||||
Assert.Equal(at, Assert.Single(o.RevealsOf(e)).Time);
|
||||
|
|
@ -932,7 +932,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
var first = Assert.Single(o.RevealsOf(locker)); // fixture: the lock paints every tick, and the first one is recorded
|
||||
Assert.True(locker.Visibility * o.PassiveInfo(locker.Position.xz, 1f) <= _items.GameplaySettings.TargetDetectionInfoThreshold, "fixture: its departure is below o's gate");
|
||||
|
||||
_zone.Entities.Remove(locker); // dies, warps or docks while its latest paint is still in o's inbox
|
||||
_zone.Remove(locker); // dies, warps or docks while its latest paint is still in o's inbox
|
||||
var inboxed = ((System.Collections.IList) typeof(Entity).GetField("_paintInbox", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(o)).Count;
|
||||
Assert.True(inboxed > 0 || o.IncomingPaints.Count > 0, "fixture: a paint is in flight");
|
||||
Tick(.1f);
|
||||
|
|
@ -984,7 +984,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
for (var tick = 0; tick < 100 && inbox.Count == 0; tick++) Tick(.1f);
|
||||
Assert.NotEmpty(inbox); // fixture: the ping crossed o and its paint waits to be published
|
||||
|
||||
_zone.Entities.Remove(p);
|
||||
_zone.Remove(p);
|
||||
Tick(.1f);
|
||||
|
||||
Assert.Single(o.IncomingPaints);
|
||||
|
|
@ -1158,7 +1158,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
ShowOnly(e, 1f, gun);
|
||||
var threshold = _items.GameplaySettings.TargetDetectionInfoThreshold;
|
||||
Assert.True(e.Visibility * o.PassiveInfo(e.Position.xz, 1f) <= threshold, "fixture: from where o is, the departure is below the gate");
|
||||
_zone.Entities.Remove(e);
|
||||
_zone.Remove(e);
|
||||
Tick(.1f);
|
||||
Assert.Single(o.RevealsOf(e));
|
||||
|
||||
|
|
@ -1188,8 +1188,8 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
var threshold = _items.GameplaySettings.TargetDetectionInfoThreshold;
|
||||
Assert.True(a.Visibility * o.PassiveInfo(a.Position.xz, 1f) > threshold && b.Visibility * o.PassiveInfo(b.Position.xz, 1f) > threshold, "fixture: both departures pass the gate");
|
||||
|
||||
_zone.Entities.Remove(firstThenSecond ? a : b);
|
||||
_zone.Entities.Remove(firstThenSecond ? b : a);
|
||||
_zone.Remove(firstThenSecond ? a : b);
|
||||
_zone.Remove(firstThenSecond ? b : a);
|
||||
Tick(.1f);
|
||||
|
||||
Assert.Empty(o.RevealsOf(a));
|
||||
|
|
@ -1215,7 +1215,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
Tick(.01f);
|
||||
Assert.Same(guns[1], Assert.Single(o.RevealsOf(e)).Item); // fixture: one gun ended as destroyed, one waiting
|
||||
e.Deactivate(); // the warp path: the ship's own subscriptions to the observer go with it
|
||||
_zone.Entities.Remove(e);
|
||||
_zone.Remove(e);
|
||||
for (var i = 0; i < 40; i++) Tick(.1f); // past BearingMemory (2 s)
|
||||
Assert.Equal(0, RevealBookSubjects(o)); // fixture: pruned
|
||||
|
||||
|
|
@ -1307,7 +1307,7 @@ public sealed partial class ContactRevealTests : IDisposable
|
|||
Assert.Contains(identified.VisibilitySources.Values, v => v.Item == gunItems[0] && v.Strength > 0f);
|
||||
var departed = _zone.Entities.Single(x => x.Equipment.Contains(gunItems[18]));
|
||||
foreach (var source in departed.VisibilitySources.Keys.ToArray()) departed.SetSignature(source, SignatureChannel.Reflection, 0f, gunItems[18]);
|
||||
_zone.Entities.Remove(departed);
|
||||
_zone.Remove(departed);
|
||||
Assert.Single((System.Collections.IList) typeof(Zone).GetProperty("Departures", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(_zone));
|
||||
sense();
|
||||
Assert.Single(o.RevealsOf(departed)); // fixture: awaiting its prune
|
||||
|
|
|
|||
|
|
@ -64,8 +64,7 @@ public sealed class ContactTests : IDisposable
|
|||
{
|
||||
var hullRef = Items.ItemData.RefOf<ItemData>(Items.ItemData.GetByName<HullData>(hull));
|
||||
var ship = new Ship(Items, Zone, new EquippableItem { Data = hullRef, Durability = 1000000, Lot = Lot++ }, new EntitySettings());
|
||||
Zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
Zone.Admit(ship, piloted: false);
|
||||
ship.Position = at;
|
||||
ship.Velocity = velocity;
|
||||
return ship;
|
||||
|
|
|
|||
|
|
@ -306,10 +306,11 @@ public sealed partial class RunStartTests
|
|||
else
|
||||
{
|
||||
// The envelope sums the live columns, the drives' yaw among them (turn-authority-full), so its reach sits above
|
||||
// the allocated one by the turn the drives add: 1.24 nose-on and 1.12 broadside at the last measurement. The
|
||||
// ceiling is that, from the columns the allocator solves with, not an open bound: an envelope inflated by a
|
||||
// factor (doubled: 2.5 and 2.2) breaks it.
|
||||
Assert.InRange(reach.noseOn, measured.noseOn * .9f, allocated.noseOn * 1.3f);
|
||||
// the allocated one by the turn the drives add: 1.37 nose-on and 1.17 broadside at the last measurement
|
||||
// (1.24 and 1.12 before every generated ship drew a tractor beam: the extra draw per ship fits the Longinus
|
||||
// with other rolls). The ceiling is that, from the columns
|
||||
// the allocator solves with, not an open bound: an envelope inflated by a factor (doubled: 2.7 and 2.3) breaks it.
|
||||
Assert.InRange(reach.noseOn, measured.noseOn * .9f, allocated.noseOn * 1.5f);
|
||||
Assert.InRange(reach.broadside, measured.broadside * .9f, allocated.broadside * 1.2f);
|
||||
Assert.InRange(measured.noseOn, allocated.noseOn * .9f, allocated.noseOn * 1.15f);
|
||||
Assert.InRange(measured.broadside, allocated.broadside * .85f, allocated.broadside * 1.15f);
|
||||
|
|
|
|||
|
|
@ -176,10 +176,8 @@ public sealed class FireAuthorityTests : IDisposable
|
|||
// extra gear a test needs (a second aimable item, a shield, a reactor) must go on before Activate().
|
||||
beforeActivate?.Invoke(items, shooter, target);
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, targetRange);
|
||||
|
|
@ -327,7 +325,7 @@ public sealed class FireAuthorityTests : IDisposable
|
|||
FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
var before = e.Target.Hull.Durability;
|
||||
|
||||
e.Zone.Entities.Remove(e.Target);
|
||||
e.Zone.Remove(e.Target);
|
||||
e.Zone.Update(10f); // well past arrival
|
||||
|
||||
Assert.Equal(before, e.Target.Hull.Durability);
|
||||
|
|
|
|||
|
|
@ -176,10 +176,8 @@ public sealed class FireControlCut11Tests : IDisposable
|
|||
Assert.True(target.TryEquip(shield, spos));
|
||||
}
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3(37, 0, -11);
|
||||
target.Position = float3(37, 0, 89); // 100 units straight ahead of the shooter
|
||||
|
|
@ -279,7 +277,7 @@ public sealed class FireControlCut11Tests : IDisposable
|
|||
// What the HUD's arrival countdown reads: time left in flight at commit, 5 s - 4.7 s.
|
||||
Assert.Equal(.3f, committed.ArrivalIn, 3);
|
||||
|
||||
e.Zone.Entities.Remove(e.Target); // the target is gone before the shot arrives
|
||||
e.Zone.Remove(e.Target); // the target is gone before the shot arrives
|
||||
e.Zone.Update(1f);
|
||||
|
||||
Assert.NotNull(resolved);
|
||||
|
|
@ -306,7 +304,7 @@ public sealed class FireControlCut11Tests : IDisposable
|
|||
e.Zone.Update(1f);
|
||||
SafeAssert.Empty(committed);
|
||||
|
||||
e.Zone.Entities.Remove(e.Target);
|
||||
e.Zone.Remove(e.Target);
|
||||
e.Zone.Update(1f);
|
||||
|
||||
foreach (var outcome in new[] { SafeAssert.Only(committed), SafeAssert.Only(resolved) })
|
||||
|
|
|
|||
|
|
@ -223,10 +223,8 @@ public sealed class FireControlCut123Tests : IDisposable
|
|||
cockpit = target.Equipment.Single(x => x.EquippableItem == ci);
|
||||
}
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
// Cut 11.3: the shooter sits off the origin.
|
||||
shooter.Position = float3(37, 0, -11);
|
||||
|
|
|
|||
|
|
@ -241,10 +241,8 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
Assert.True(target.TryEquip(shield, spos));
|
||||
}
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
// Off-origin (the fixture rule): the shooter sits away from the world origin, and the target sits
|
||||
// `targetRange` ahead of it along the shooter's own +z, so a stationary target's TravelDirection is
|
||||
|
|
@ -514,8 +512,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
Data = e.Items.ItemData.RefOf<ItemData>(e.Items.ItemData.GetByName<GearData>("Marker")), Durability = 1000000, Lot = lot + 1
|
||||
};
|
||||
Assert.True(ship.TryEquip(marker, new int2(1, 1)));
|
||||
e.Zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
e.Zone.Admit(ship, piloted: false);
|
||||
ship.Position = position;
|
||||
return ship;
|
||||
}
|
||||
|
|
@ -1464,7 +1461,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
Assert.True(SafeAssert.OnlyShot(e.Zone).Target == e.Target, "fixture: the pushed-out round keeps its target");
|
||||
e.Zone.Update(.01f);
|
||||
e.Zone.Entities.Remove(e.Target);
|
||||
e.Zone.Remove(e.Target);
|
||||
e.Zone.Update(3f);
|
||||
|
||||
SafeAssert.NoShots(e.Zone);
|
||||
|
|
@ -1509,7 +1506,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
var e = Build(TestSettings(), SolidShape(5, 4), velocity: 20f, fuse: WeaponFuse.Proximity, blastRadius: 4f, damage: 100f);
|
||||
FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
using var c = e.Zone.ShotCommitted.Subscribe(_ => throw new InvalidOperationException("observer"));
|
||||
e.Zone.Entities.Remove(e.Target);
|
||||
e.Zone.Remove(e.Target);
|
||||
|
||||
Assert.ThrowsAny<Exception>(() => e.Zone.Update(.01f));
|
||||
var second = Record.Exception(() => e.Zone.Update(.01f));
|
||||
|
|
@ -2003,7 +2000,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
|
||||
FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
for (var i = 0; i < updatesBeforeLeaving; i++) e.Zone.Update(.01f);
|
||||
e.Zone.Entities.Remove(e.Shooter);
|
||||
e.Zone.Remove(e.Shooter);
|
||||
e.Zone.Update(4f);
|
||||
|
||||
SafeAssert.NoShots(e.Zone);
|
||||
|
|
@ -2021,7 +2018,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
|
||||
FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
e.Zone.Update(.01f);
|
||||
e.Zone.Entities.Remove(e.Shooter);
|
||||
e.Zone.Remove(e.Shooter);
|
||||
e.Zone.Update(6f);
|
||||
|
||||
SafeAssert.NoShots(e.Zone);
|
||||
|
|
@ -2043,7 +2040,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
|
||||
for (var i = 0; i < 12; i++) FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
e.Zone.Update(.01f);
|
||||
e.Zone.Entities.Remove(e.Shooter);
|
||||
e.Zone.Remove(e.Shooter);
|
||||
e.Zone.Update(6f);
|
||||
|
||||
SafeAssert.NoShots(e.Zone);
|
||||
|
|
@ -2273,7 +2270,7 @@ public sealed partial class FireControlCut124Tests : IDisposable
|
|||
FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
e.Zone.Update(committedFirst ? 4.6f : .01f);
|
||||
Assert.Equal(committedFirst, e.Zone.PendingShots[0].Committed);
|
||||
e.Zone.Entities.Remove(e.Target);
|
||||
e.Zone.Remove(e.Target);
|
||||
e.Zone.Update(6f);
|
||||
|
||||
foreach (var s in subscriptions) s.Dispose();
|
||||
|
|
|
|||
|
|
@ -268,10 +268,8 @@ public sealed class FireControlCut12Tests : IDisposable
|
|||
Assert.True(target.TryEquip(shield, spos));
|
||||
}
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
// Cut 11.3: the shooter sits off the origin -- a shooter at float3.zero makes `target - source` and
|
||||
// `target + source` the same vector, hiding a whole class of sign mutants.
|
||||
|
|
|
|||
|
|
@ -154,10 +154,8 @@ public sealed class FireControlCut4Tests : IDisposable
|
|||
var targetGun = new EquippableItem { Data = targetGunRef, Durability = 1, Lot = 9 };
|
||||
Assert.True(target.TryEquip(targetGun, new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, targetRange);
|
||||
|
|
@ -233,8 +231,7 @@ public sealed class FireControlCut4Tests : IDisposable
|
|||
Assert.True(shooter.TryEquip(gun, new int2(0, 0)));
|
||||
var weapon = (InstantWeapon) shooter.Equipment.Single(e => e.EquippableItem == gun).Behaviors.Single(b => b is Weapon);
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
shooter.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Update(0f); // warm up resolved stats
|
||||
|
||||
// No target, no OnFire subscriber -- exactly a headless shooter with no target and no presentation.
|
||||
|
|
@ -276,7 +273,7 @@ public sealed class FireControlCut4Tests : IDisposable
|
|||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
ship.Position = position;
|
||||
ship.Direction = direction;
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
return ship;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,10 +183,8 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
|
||||
beforeActivate?.Invoke(items, shooter, target);
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, targetRange);
|
||||
|
|
@ -338,10 +336,8 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
var targetGun = new EquippableItem { Data = targetGunRef, Durability = 1, Lot = 5 };
|
||||
Assert.True(target.TryEquip(targetGun, new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = targetPosition;
|
||||
|
|
@ -426,10 +422,8 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
var targetGun = new EquippableItem { Data = gunRef, Durability = 1, Lot = 4 };
|
||||
Assert.True(target.TryEquip(targetGun, new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
return (items, shooter, target, weaponItem);
|
||||
}
|
||||
|
|
@ -575,8 +569,7 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
Assert.True(target.TryEquip(medium, mediumPos));
|
||||
var mediumItem = target.Equipment.Single(e => e.EquippableItem == medium);
|
||||
|
||||
zone.Entities.Add(target);
|
||||
target.Activate();
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
return (items, target, bigItem, mediumItem);
|
||||
}
|
||||
|
|
@ -591,8 +584,7 @@ public sealed class FireControlCut5Tests : IDisposable
|
|||
var (items, target, big, medium) = BuildRevealTarget(settings);
|
||||
var observerHullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Hull"));
|
||||
var observer = new Ship(items, target.Zone, new EquippableItem { Data = observerHullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
|
||||
target.Zone.Entities.Add(observer);
|
||||
observer.Activate();
|
||||
target.Zone.Admit(observer, piloted: false);
|
||||
observer.SetTarget(target);
|
||||
|
||||
observer.RecordReveal(target, big, SignatureChannel.LaunchFlash);
|
||||
|
|
|
|||
|
|
@ -155,10 +155,8 @@ public sealed class FireControlCut6Tests : IDisposable
|
|||
var targetGun = new EquippableItem { Data = targetGunRef, Durability = 1, Lot = 5 };
|
||||
Assert.True(target.TryEquip(targetGun, new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, targetRange);
|
||||
|
|
|
|||
|
|
@ -137,10 +137,8 @@ public sealed class FireControlCut6cTests : IDisposable
|
|||
var targetGun = new EquippableItem { Data = targetGunRef, Durability = 1, Lot = 9 };
|
||||
Assert.True(target.TryEquip(targetGun, new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, targetRange);
|
||||
|
|
@ -211,6 +209,7 @@ public sealed class FireControlCut6cTests : IDisposable
|
|||
var cargo = seedCache.Upsert(new CargoBayData { Name = "Crate", Shape = new Shape(), InteriorShape = new Shape(), Price = 5 });
|
||||
var capacitor = seedCache.Upsert(new GearData { Name = "Cap", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new CapacitorData() } });
|
||||
var targeting = seedCache.Upsert(new GearData { Name = "Targeting", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new TargetingSystemData() } });
|
||||
var tractor = seedCache.Upsert(new GearData { Name = "Tractor", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new PickupData() } });
|
||||
|
||||
// Everything but the targeting system is sold by Maker, who IS in the galaxy. Targeting is sold
|
||||
// only by Targeting Maker, who is NOT -- the single point of failure 6c.2 fixes.
|
||||
|
|
@ -218,6 +217,7 @@ public sealed class FireControlCut6cTests : IDisposable
|
|||
{
|
||||
("Armed by Maker", hull.Key, maker), ("Gun by Maker", gun.Key, maker),
|
||||
("Crate by Maker", cargo.Key, maker), ("Cap by Maker", capacitor.Key, maker),
|
||||
("Tractor by Maker", tractor.Key, maker),
|
||||
("Targeting by Targeting Maker", targeting.Key, targetingMaker)
|
||||
})
|
||||
seedCache.Upsert(new FactionProductData { Name = name, Design = new CultRecordRef<CraftedItemData>(design), Manufacturer = factionMaker });
|
||||
|
|
@ -266,8 +266,7 @@ public sealed class FireControlCut6cTests : IDisposable
|
|||
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
var entity = EntitySerializer.Unpack(items, zone, pack);
|
||||
zone.Entities.Add(entity);
|
||||
entity.Activate();
|
||||
zone.Admit(entity, piloted: false);
|
||||
zone.Update(0f);
|
||||
|
||||
Assert.Null(entity.GetBehavior<TargetingSystem>());
|
||||
|
|
|
|||
|
|
@ -155,10 +155,8 @@ public sealed class FireControlCut6dTests : IDisposable
|
|||
Assert.True(target.TryEquip(marker, markerCell.Value));
|
||||
}
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, targetRange);
|
||||
|
|
|
|||
|
|
@ -131,10 +131,8 @@ public sealed class FireControlCut7Tests : IDisposable
|
|||
|
||||
beforeActivate?.Invoke(items, shooter, target);
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, 100);
|
||||
|
|
|
|||
|
|
@ -137,10 +137,8 @@ public sealed class FireControlCut8Tests : IDisposable
|
|||
var targetGun = new EquippableItem { Data = targetGunRef, Durability = 1, Lot = 4 };
|
||||
Assert.True(target.TryEquip(targetGun, new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, 100);
|
||||
|
|
@ -216,10 +214,8 @@ public sealed class FireControlCut8Tests : IDisposable
|
|||
var bystanderGun = new EquippableItem { Data = bystanderGunRef, Durability = 1, Lot = 4 };
|
||||
Assert.True(bystander.TryEquip(bystanderGun, new int2(0, 0))); // _orderedEquipment must not be empty/null for Update to run
|
||||
|
||||
zone.Entities.Add(locker);
|
||||
zone.Entities.Add(bystander);
|
||||
locker.Activate();
|
||||
bystander.Activate();
|
||||
zone.Admit(locker, piloted: false);
|
||||
zone.Admit(bystander, piloted: false);
|
||||
|
||||
locker.Position = float3.zero;
|
||||
bystander.Position = float3(0, 0, 100);
|
||||
|
|
@ -297,41 +293,4 @@ public sealed class FireControlCut8Tests : IDisposable
|
|||
Assert.Empty(e.Target.EntityInfoGathered);
|
||||
Assert.True(e.Target.VisibleEntities.Count == 0, "the target's visible set must be empty");
|
||||
}
|
||||
|
||||
// ---- Ordering risk (8.1's own callout): EntityInstance's loot-drop subscription (EntityInstance.cs:292-325)
|
||||
// reads Entity.Equipment on Entity.HullDamage, independent of Entity.Death/Deactivate. EntityInstance
|
||||
// itself is a MonoBehaviour and cannot run headlessly (it lives in the Gameplay assembly, not
|
||||
// Aetheria.Shared), so the actual drop call (ZoneRenderer.DropItem) is an operator check, not something
|
||||
// this suite reaches. What this suite CAN confirm headlessly is the data precondition that call depends
|
||||
// on: Deactivate() must not clear Equipment, regardless of subscription order relative to Zone's own
|
||||
// Death-triggered removal. ----
|
||||
|
||||
// Mirrors EntityInstance's own subscription shape (HullDamage, re-checking Durability itself) established
|
||||
// AFTER the target has already joined the zone -- so Zone's Death-derived Deactivate (subscribed at join
|
||||
// time, per 8.1) fires first on the same HullDamage.OnNext call, and this handler observes Equipment
|
||||
// strictly after that. If Deactivate ever starts clearing Equipment, this goes red.
|
||||
[Fact]
|
||||
public void LootStillDropsOnDeath_EquipmentSurvivesDeactivate()
|
||||
{
|
||||
var e = Build(TestSettings(), damage: 10000, hullDurability: 5); // one hit is lethal
|
||||
|
||||
List<EquippedItem> equipmentAtDeath = null;
|
||||
e.Target.HullDamage.Subscribe(_ =>
|
||||
{
|
||||
if (e.Target.Hull.Durability < .01f) equipmentAtDeath = e.Target.Equipment.ToList();
|
||||
});
|
||||
|
||||
FireControl.Fire(e.Weapon, e.WeaponItem, e.Shooter);
|
||||
e.Zone.Update(.1f);
|
||||
|
||||
Assert.True(e.Target.Hull.Durability < .01f);
|
||||
Assert.False(e.Target.Active); // Zone's Death subscription (subscribed before this test's own) already
|
||||
// deactivated the target by the time this handler runs.
|
||||
Assert.NotNull(equipmentAtDeath);
|
||||
Assert.NotEmpty(equipmentAtDeath); // the target's own gun -- Deactivate must not have cleared Equipment
|
||||
|
||||
// Operator check, not reachable headlessly: confirm in play that ZoneRenderer.DropItem still fires for
|
||||
// each surviving equipment/cargo entry (EntityInstance.cs:292-325) after this cut, i.e. that loot
|
||||
// actually drops on a live kill, not only that the data behind it survived Deactivate().
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,10 +128,8 @@ public sealed class FireControlCut9Tests : IDisposable
|
|||
Assert.True(target.TryFindSpace(targetMarker, out var targetMarkerPos));
|
||||
Assert.True(target.TryEquip(targetMarker, targetMarkerPos));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, 100);
|
||||
|
|
@ -339,10 +337,8 @@ public sealed class FireControlCut9Tests : IDisposable
|
|||
var targetGun = new EquippableItem { Data = targetGunRef, Durability = 1, Lot = 5 };
|
||||
Assert.True(target.TryEquip(targetGun, new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = float3(0, 0, 100);
|
||||
|
|
@ -566,10 +562,8 @@ public sealed class FireControlCut9Tests : IDisposable
|
|||
var bystanderGun = new EquippableItem { Data = bystanderGunRef, Durability = 1, Lot = 4 };
|
||||
Assert.True(bystander.TryEquip(bystanderGun, new int2(0, 0))); // _orderedEquipment must not be empty/null for Update to run
|
||||
|
||||
zone.Entities.Add(locker);
|
||||
zone.Entities.Add(bystander);
|
||||
locker.Activate();
|
||||
bystander.Activate();
|
||||
zone.Admit(locker, piloted: false);
|
||||
zone.Admit(bystander, piloted: false);
|
||||
|
||||
locker.Position = float3.zero;
|
||||
bystander.Position = float3(0, 0, 100);
|
||||
|
|
@ -602,7 +596,7 @@ public sealed class FireControlCut9Tests : IDisposable
|
|||
// the target dies or leaves while the locker is inactive -- its own Zone.Entities.ObserveRemove
|
||||
// subscription (the only thing that would have nulled Target) was disposed by Deactivate above, so
|
||||
// nothing reacts to this removal yet.
|
||||
s.Zone.Entities.Remove(s.Bystander);
|
||||
s.Zone.Remove(s.Bystander);
|
||||
|
||||
s.Locker.Activate(); // undock
|
||||
Assert.True(s.Locker.Active);
|
||||
|
|
|
|||
|
|
@ -237,10 +237,8 @@ public sealed class FireControlPerWeaponTests : IDisposable
|
|||
var target = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000000, Lot = lot++ }, new EntitySettings());
|
||||
Assert.True(target.TryEquip(Make("Gun", lot++, 1), new int2(0, 0)));
|
||||
|
||||
zone.Entities.Add(shooter);
|
||||
zone.Entities.Add(target);
|
||||
shooter.Activate();
|
||||
target.Activate();
|
||||
zone.Admit(shooter, piloted: false);
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3(-53, 0, -101);
|
||||
target.Position = shooter.Position + float3(0, 0, targetRange);
|
||||
|
|
|
|||
|
|
@ -95,8 +95,7 @@ public sealed class FireControlTests : IDisposable
|
|||
weapons[i] = ship.Equipment.Single(e => e.EquippableItem == gun);
|
||||
}
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
|
||||
return (items, ship, weapons);
|
||||
}
|
||||
|
|
@ -195,8 +194,7 @@ public sealed class FireControlTests : IDisposable
|
|||
// gun so the target is a real, ordinarily-constructed ship rather than a workaround-shaped one.
|
||||
var targetGunRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<GearData>("Gun"));
|
||||
Assert.True(target.TryEquip(new EquippableItem { Data = targetGunRef, Durability = 1, Lot = 1 }, new int2(0, 0)));
|
||||
zone.Entities.Add(target);
|
||||
target.Activate();
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
shooter.Position = float3.zero;
|
||||
target.Position = targetPosition;
|
||||
|
|
|
|||
|
|
@ -239,9 +239,8 @@ public sealed class FloatingBodyTests : IDisposable
|
|||
Lot = items.Lots.Add(new Lot { Design = cache.RefOf<ItemData>(design), Origin = new Attributed(), Quality = .5f, Roles = new List<RoleFill>() })
|
||||
};
|
||||
var ship = new Ship(items, zone, hull, new EntitySettings());
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
ship.Position = float3(x, 0, 0);
|
||||
return ship;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,8 +82,7 @@ public sealed class IffAndCombatTests : IDisposable
|
|||
Assert.True(ship.TryEquip(gun, HardpointCell));
|
||||
}
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
return ship;
|
||||
}
|
||||
|
||||
|
|
@ -132,8 +131,9 @@ public sealed class IffAndCombatTests : IDisposable
|
|||
a.SetIff(b, true);
|
||||
Assert.True(a.EntityHostility[b]);
|
||||
|
||||
zone.Entities.Remove(b); // b leaves the zone: a's override on b must be cleared
|
||||
zone.Entities.Add(b); // b returns as a "new" contact from a's perspective
|
||||
zone.Remove(b); // b leaves the zone: a's override on b must be cleared
|
||||
b.Deactivate();
|
||||
zone.Admit(b, piloted: false); // b returns as a "new" contact from a's perspective
|
||||
|
||||
Assert.False(a.EntityHostility[b]); // recomputed fresh from the derived (non-hostile) rule
|
||||
Assert.False(a.IsHostileTo(b));
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ public sealed partial class FireControlCut124Tests
|
|||
{
|
||||
var hullRef = e.Items.ItemData.RefOf<ItemData>(e.Items.ItemData.GetByName<HullData>("Hull"));
|
||||
var ship = new Ship(e.Items, e.Zone, new EquippableItem { Data = hullRef, Durability = 100000, Lot = lot }, new EntitySettings());
|
||||
e.Zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
e.Zone.Admit(ship, piloted: false);
|
||||
ship.Position = position;
|
||||
return ship;
|
||||
}
|
||||
|
|
@ -136,7 +135,7 @@ public sealed partial class FireControlCut124Tests
|
|||
var gone = Build(TestSettings(), SolidShape(5, 4), velocity: 50f, damage: 100f);
|
||||
var goneAbsorbs = new[] { gone.Target, gone.Shooter }.SelectMany(x => Collect(x, subs)).ToList();
|
||||
FireControl.Fire(gone.Weapon, gone.WeaponItem, gone.Shooter);
|
||||
gone.Zone.Entities.Remove(gone.Target);
|
||||
gone.Zone.Remove(gone.Target);
|
||||
for (var i = 0; i < 40; i++) gone.Zone.Update(.1f);
|
||||
foreach (var s in subs) s.Dispose();
|
||||
|
||||
|
|
|
|||
|
|
@ -97,8 +97,7 @@ public sealed class InputCapacitorTests : IDisposable
|
|||
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, reactorData)));
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
|
||||
return (ship, items, ship.GetBehavior<Reactor>());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -501,7 +501,8 @@ public sealed class LoadoutTests : IDisposable
|
|||
}
|
||||
});
|
||||
var capacitor = cache.Upsert(new GearData { Name = "Cap", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new CapacitorData() } });
|
||||
foreach (var (name, design) in new[] { ("Twin by Maker", hull.Key), ("Cap by Maker", capacitor.Key) })
|
||||
var tractor = cache.Upsert(new GearData { Name = "Tractor", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new PickupData() } });
|
||||
foreach (var (name, design) in new[] { ("Twin by Maker", hull.Key), ("Cap by Maker", capacitor.Key), ("Tractor by Maker", tractor.Key) })
|
||||
cache.Upsert(new FactionProductData { Name = name, Design = new CultRecordRef<CraftedItemData>(design), Manufacturer = maker });
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
|
|
|
|||
|
|
@ -104,8 +104,7 @@ public sealed class LockWarningTests : IDisposable
|
|||
var gearRef = _items.ItemData.RefOf<ItemData>(_items.ItemData.GetByName<GearData>(launcher));
|
||||
Assert.True(ship.TryEquip(new EquippableItem { Data = gearRef, Durability = 1, Lot = _nextLot++ }, HardpointCell));
|
||||
}
|
||||
_zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
_zone.Admit(ship, piloted: false);
|
||||
ship.Position = position;
|
||||
_zone.Update(0f); // brings the gear online
|
||||
return ship;
|
||||
|
|
|
|||
|
|
@ -133,8 +133,7 @@ public sealed class LootTests : IDisposable
|
|||
Assert.True(ship.CargoBays.Single().TryStore(lab.Ore(7)));
|
||||
Assert.True(ship.CargoBays.Single().TryStore(lab.Make("Spare")));
|
||||
}
|
||||
lab.Zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
lab.Zone.Admit(ship, piloted: false);
|
||||
ship.Position = at;
|
||||
return ship;
|
||||
}
|
||||
|
|
@ -153,8 +152,7 @@ public sealed class LootTests : IDisposable
|
|||
Assert.True(station.TryEquip(spare, pos));
|
||||
Assert.True(station.CargoBays.Single().TryStore(lab.Ore(3)));
|
||||
}
|
||||
lab.Zone.Entities.Add(station);
|
||||
station.Activate();
|
||||
lab.Zone.Admit(station, piloted: false);
|
||||
station.Position = at;
|
||||
return station;
|
||||
}
|
||||
|
|
@ -393,10 +391,10 @@ public sealed class LootTests : IDisposable
|
|||
var lab = Build(probability: 1f);
|
||||
var ship = Spawn(lab, float3(3, 0, 4));
|
||||
// What Entity.TryDock then Entity.TryUndock do to the zone: Entities.Remove + Deactivate, then Zone.Admit.
|
||||
lab.Zone.Entities.Remove(ship);
|
||||
lab.Zone.Remove(ship);
|
||||
ship.Deactivate();
|
||||
lab.Zone.Admit(ship, piloted: false);
|
||||
lab.Zone.Entities.Remove(ship);
|
||||
lab.Zone.Remove(ship);
|
||||
lab.Zone.Admit(ship, piloted: false);
|
||||
Kill(ship);
|
||||
var dropped = lab.Zone.FloatingItems.Select(f => f.Item).ToList();
|
||||
|
|
@ -412,7 +410,7 @@ public sealed class LootTests : IDisposable
|
|||
var ship = Spawn(lab, float3(3, 0, 4));
|
||||
// What ActionGameManager does on a jump.
|
||||
ship.Deactivate();
|
||||
lab.Zone.Entities.Remove(ship);
|
||||
lab.Zone.Remove(ship);
|
||||
ship.Zone = other;
|
||||
other.Admit(ship, piloted: false);
|
||||
Kill(ship);
|
||||
|
|
@ -509,9 +507,10 @@ public sealed class LootTests : IDisposable
|
|||
Assert.True(lab.Zone.FloatingItems.Select(f => Math.Round(Math.Atan2(f.Body.Velocity.y, f.Body.Velocity.x), 3)).Distinct().Count() > 1);
|
||||
}
|
||||
|
||||
// A member added twice holds one death subscription, so it drops each item once, as a single death would.
|
||||
// Membership has one owner (aetheria-release zone-entities-encapsulation): a second admission of a member is
|
||||
// refused, so it never holds a second death watch and the zone never lists it twice.
|
||||
[Fact]
|
||||
public void AMemberAddedTwiceStillDropsEachItemOnce()
|
||||
public void AMemberAdmittedTwiceIsRefusedAndStillDropsEachItemOnce()
|
||||
{
|
||||
var once = Build("Once", probability: 1f);
|
||||
Kill(Spawn(once, float3(3, 0, 4)));
|
||||
|
|
@ -520,20 +519,45 @@ public sealed class LootTests : IDisposable
|
|||
|
||||
var lab = Build("Twice", probability: 1f);
|
||||
var ship = Spawn(lab, float3(3, 0, 4));
|
||||
lab.Zone.Entities.Add(ship);
|
||||
Assert.Throws<InvalidOperationException>(() => lab.Zone.Admit(ship, piloted: false));
|
||||
Assert.Equal(1, lab.Zone.Entities.Count(e => e == ship));
|
||||
Kill(ship);
|
||||
Assert.Equal(baseline, lab.Zone.FloatingItems.Count);
|
||||
}
|
||||
|
||||
// Clearing Entities removes every member, so a death afterwards drops nothing in this zone.
|
||||
// Remove is the one removal: a non-member is refused with nothing changed, and a member's removal takes its death
|
||||
// watch with it, so a death signal afterwards drops nothing and a re-admission watches exactly once.
|
||||
[Fact]
|
||||
public void AClearedZoneDropsNothingWhenAFormerMemberDies()
|
||||
public void RemovingANonMemberChangesNothingAndARemovedMembersWatchGoesWithIt()
|
||||
{
|
||||
var lab = Build("Cleared", probability: 1f);
|
||||
var lab = Build(probability: 1f);
|
||||
var ship = Spawn(lab, float3(3, 0, 4));
|
||||
lab.Zone.Entities.Clear();
|
||||
var stranger = Spawn(Build("Elsewhere", probability: 1f), float3(1, 0, 1));
|
||||
var members = lab.Zone.Entities.Count;
|
||||
Assert.False(lab.Zone.Remove(stranger));
|
||||
Assert.Equal(members, lab.Zone.Entities.Count);
|
||||
|
||||
Assert.True(lab.Zone.Remove(ship));
|
||||
Assert.False(lab.Zone.Remove(ship));
|
||||
Kill(ship);
|
||||
Assert.Empty(lab.Zone.FloatingItems);
|
||||
|
||||
ship.Deactivate();
|
||||
lab.Zone.Admit(ship, piloted: false);
|
||||
Kill(ship);
|
||||
var dropped = lab.Zone.FloatingItems.Select(f => f.Item).ToList();
|
||||
Assert.NotEmpty(dropped);
|
||||
Assert.Equal(dropped.Count, dropped.Distinct().Count());
|
||||
}
|
||||
|
||||
// The membership view has no mutating member: a caller can read it and subscribe to it, and nothing else.
|
||||
[Fact]
|
||||
public void TheMembershipViewOffersNoWayToMutateMembership()
|
||||
{
|
||||
var lab = Build();
|
||||
Spawn(lab, float3(3, 0, 4));
|
||||
Assert.IsNotAssignableFrom<System.Collections.Generic.ICollection<Entity>>(lab.Zone.Entities);
|
||||
Assert.IsNotAssignableFrom<System.Collections.IList>(lab.Zone.Entities);
|
||||
}
|
||||
|
||||
// The pickup is whole or nothing at the arithmetic boundary: two stacks of 90 hold 20 more in all and no free
|
||||
|
|
|
|||
|
|
@ -130,8 +130,7 @@ public sealed class MineTests : IDisposable
|
|||
var targeting = lab.Make("Targeting", 1);
|
||||
Assert.True(layer.TryFindSpace(targeting, out var tpos));
|
||||
Assert.True(layer.TryEquip(targeting, tpos));
|
||||
zone.Entities.Add(layer);
|
||||
layer.Activate();
|
||||
zone.Admit(layer, piloted: false);
|
||||
layer.Position = layerAt ?? float3(-53, 0, -101);
|
||||
lab.Layer = layer;
|
||||
zone.Update(1f);
|
||||
|
|
@ -152,8 +151,7 @@ public sealed class MineTests : IDisposable
|
|||
Assert.True(ship.TryFindSpace(gear, out var spos));
|
||||
Assert.True(ship.TryEquip(gear, spos));
|
||||
}
|
||||
lab.Zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
lab.Zone.Admit(ship, piloted: false);
|
||||
ship.Position = at;
|
||||
if (shield != null) ship.Shield.Item.Enabled.Value = true;
|
||||
return ship;
|
||||
|
|
|
|||
|
|
@ -220,10 +220,9 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
s.Cache.Upsert(gun);
|
||||
Assert.True(ship.TryEquip(Mint(s, gun)));
|
||||
}
|
||||
s.Zone.Entities.Add(ship);
|
||||
s.Zone.Admit(ship, piloted: false);
|
||||
Hold(ship, at);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
return ship;
|
||||
}
|
||||
|
||||
|
|
@ -865,10 +864,9 @@ public sealed partial class MiningCut3Tests : IDisposable
|
|||
var ship = new Ship(s.Items, s.Zone, Mint(s, s.Hull), new EntitySettings());
|
||||
Assert.True(ship.TryEquip(Mint(s, s.Eye)));
|
||||
foreach (var weapon in weapons) Assert.True(ship.TryEquip(Mint(s, weapon)));
|
||||
s.Zone.Entities.Add(ship);
|
||||
s.Zone.Admit(ship, piloted: false);
|
||||
Hold(ship, eyeAt);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
Tick((ship, eyeAt)); // weapons read their ranges when they run
|
||||
return ship;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,10 +53,9 @@ public sealed partial class MiningCut3Tests
|
|||
var ship = new Ship(s.Items, s.Zone, Mint(s, s.Hull), new EntitySettings());
|
||||
Assert.True(ship.TryEquip(Mint(s, eye)));
|
||||
Assert.True(ship.TryEquip(Mint(s, gun)));
|
||||
s.Zone.Entities.Add(ship);
|
||||
s.Zone.Admit(ship, piloted: false);
|
||||
Hold(ship, at);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
Tick((ship, at)); // equipment comes online and weapons read their ranges on the first tick
|
||||
return ship;
|
||||
}
|
||||
|
|
@ -176,7 +175,7 @@ public sealed partial class MiningCut3Tests
|
|||
Assert.True(expected.ToHashSet().SetEquals(offered), $"sensitivity {sensitivity}: {expected.Count} visible, {offered.Count} offered");
|
||||
visibleSeen += expected.Count;
|
||||
if (examined < inReach.Count) pruned++;
|
||||
s.Zone.Entities.Remove(observer);
|
||||
s.Zone.Remove(observer);
|
||||
}
|
||||
Assert.True(visibleSeen > 50 && pruned > 4, $"degenerate: {visibleSeen} visible, {pruned} queries pruned");
|
||||
}
|
||||
|
|
@ -421,7 +420,7 @@ public sealed partial class MiningCut3Tests
|
|||
var brightest = all.Max(c => s.Zone.ChunkVisibility(c));
|
||||
var unit = Observer(s, float2(5000, 0), 350f, 1f, Falloff());
|
||||
var sensitivity = 120f / (brightest * unit.DetectionReachPerVisibility());
|
||||
s.Zone.Entities.Remove(unit);
|
||||
s.Zone.Remove(unit);
|
||||
|
||||
var offered = new List<ChunkId>();
|
||||
long examined = 0, inReach = 0;
|
||||
|
|
@ -436,7 +435,7 @@ public sealed partial class MiningCut3Tests
|
|||
observer.VisibleChunksInReach(offered);
|
||||
examined += s.Zone.Targets.Examined - before;
|
||||
inReach += all.Count(c => length(At(s, c) - at) <= 350f);
|
||||
s.Zone.Entities.Remove(observer);
|
||||
s.Zone.Remove(observer);
|
||||
}
|
||||
Assert.True(examined < .265 * inReach, $"examined {examined} of {inReach} rocks in reach: the visibility bound has loosened");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public sealed partial class MiningCut3Tests
|
|||
{
|
||||
var observer = Observer(s, at, reach, sensitivity, Falloff());
|
||||
if (inReach.Count(observer.ChunkVisible) >= share * inReach.Count) return (s, observer);
|
||||
s.Zone.Entities.Remove(observer);
|
||||
s.Zone.Remove(observer);
|
||||
}
|
||||
throw new InvalidOperationException("no sensitivity sees the rocks");
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ public sealed partial class MiningCut3Tests
|
|||
private static Ship Sight(Scene s, Ship observer, float2 at, bool enemy = false)
|
||||
{
|
||||
var ship = new Ship(s.Items, s.Zone, Mint(s, s.Hull), new EntitySettings());
|
||||
s.Zone.Entities.Add(ship);
|
||||
s.Zone.Admit(ship, piloted: false);
|
||||
Hold(ship, at);
|
||||
observer.VisibleEntities.Add(ship);
|
||||
if (enemy) observer.VisibleEnemies.Add(ship);
|
||||
|
|
@ -265,7 +265,7 @@ public sealed partial class MiningCut3Tests
|
|||
observer.Aim = float3(cos(heading), 0, sin(heading));
|
||||
Check($"the reticle at {heading}", Cost(observer.TargetUnderReticle, TargetRef.None), reticle);
|
||||
}
|
||||
s.Zone.Entities.Remove(observer);
|
||||
s.Zone.Remove(observer);
|
||||
}
|
||||
}
|
||||
Assert.True(failures.Count == 0, string.Join("; ", failures));
|
||||
|
|
@ -336,10 +336,9 @@ public sealed partial class MiningCut3Tests
|
|||
var ship = new Ship(s.Items, s.Zone, Mint(s, s.Hull), new EntitySettings());
|
||||
Assert.True(ship.TryEquip(Mint(s, s.Eye)));
|
||||
Assert.True(ship.TryEquip(Mint(s, launcher)));
|
||||
s.Zone.Entities.Add(ship);
|
||||
s.Zone.Admit(ship, piloted: false);
|
||||
Hold(ship, at);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
Tick((ship, at));
|
||||
Assert.Contains(ship.Weapons, w => w.Range == 400f);
|
||||
Assert.True(AllChunks(s).Any(c => length(At(s, c) - at) <= 400f && ship.ChunkVisible(c)), "there are rocks within the launcher's range");
|
||||
|
|
@ -375,7 +374,7 @@ public sealed partial class MiningCut3Tests
|
|||
private static Ship Unseen(Scene s, float2 at)
|
||||
{
|
||||
var ship = new Ship(s.Items, s.Zone, Mint(s, s.Hull), new EntitySettings());
|
||||
s.Zone.Entities.Add(ship);
|
||||
s.Zone.Admit(ship, piloted: false);
|
||||
Hold(ship, at);
|
||||
return ship;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -266,8 +266,7 @@ public sealed class MunitionPartsTests : IDisposable
|
|||
Assert.True(ship.TryEquip(new EquippableItem { Data = cache.RefOf<ItemData>(gear), Durability = gear.Durability, Lot = 1 }, new int2(x, y)), name);
|
||||
}
|
||||
Assert.True(ship.TryEquip(new EquippableItem { Data = cache.RefOf<ItemData>(gt), Durability = gt.Durability, Lot = 2 }, hardpoint.Position));
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
// Settle at the item's optimal temperature, as ConditionRatioTests does, so heat and wear read their identity and only
|
||||
// the role's quality moves a stat.
|
||||
var item = ship.Equipment.Single(e => e.Data.Name == "GT 3K");
|
||||
|
|
|
|||
|
|
@ -124,8 +124,7 @@ public sealed class PaintModeTests : IDisposable
|
|||
var gearRef2 = _items.ItemData.RefOf<ItemData>(_items.ItemData.GetByName<GearData>(sensor2));
|
||||
Assert.True(ship.TryEquip(new EquippableItem { Data = gearRef2, Durability = 1, Lot = _nextLot++ }, new int2(1, 0)));
|
||||
}
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Position = position;
|
||||
ship.SetSignature(new object(), SignatureChannel.Reflection, 5f, null);
|
||||
zone.Update(0f); // brings the gear online
|
||||
|
|
@ -331,7 +330,7 @@ public sealed class PaintModeTests : IDisposable
|
|||
if (tick == 2 && illuminatorDies)
|
||||
{
|
||||
Assert.True(SensorOf(a).PingRadius < 100f, "fixture: A dies before its wavefront reaches T");
|
||||
_zone.Entities.Remove(a);
|
||||
_zone.Remove(a);
|
||||
}
|
||||
track.Add(Track(c, t));
|
||||
}
|
||||
|
|
@ -422,7 +421,7 @@ public sealed class PaintModeTests : IDisposable
|
|||
for (var tick = 1; tick <= 24; tick++)
|
||||
{
|
||||
Tick(.1f);
|
||||
if (tick == removePingerAfterTick) _zone.Entities.Remove(a);
|
||||
if (tick == removePingerAfterTick) _zone.Remove(a);
|
||||
Near(tick <= 3 ? 0f : .21213f, Track(c, t));
|
||||
Assert.Equal(tick == 4 ? 1 : 0, t.IncomingPaints.Count);
|
||||
}
|
||||
|
|
@ -683,7 +682,7 @@ public sealed class PaintModeTests : IDisposable
|
|||
SensorOf(a).Ping();
|
||||
for (var tick = 1; tick <= 3; tick++) Tick(.1f);
|
||||
Assert.True(SensorOf(a).PingRadius > 50f); // the front had passed 50 m by the move
|
||||
a.Deactivate(); _zone.Entities.Remove(a); a.Zone = z2; z2.Admit(a, false);
|
||||
a.Deactivate(); _zone.Remove(a); a.Zone = z2; z2.Admit(a, false);
|
||||
var paints = 0;
|
||||
for (var tick = 1; tick <= 24; tick++) { z2.Update(.1f); paints += near.IncomingPaints.Count + far.IncomingPaints.Count; }
|
||||
Assert.Equal(0, paints);
|
||||
|
|
@ -703,8 +702,8 @@ public sealed class PaintModeTests : IDisposable
|
|||
var paints = 0;
|
||||
for (var tick = 1; tick <= 30; tick++)
|
||||
{
|
||||
if (tick == 5) { _zone.Entities.Remove(t); t.Deactivate(); }
|
||||
if (tick == 8) { _zone.Entities.Add(t); t.Activate(); }
|
||||
if (tick == 5) { _zone.Remove(t); t.Deactivate(); }
|
||||
if (tick == 8) { _zone.Admit(t, piloted: false); }
|
||||
Tick(.1f);
|
||||
paints += t.IncomingPaints.Count;
|
||||
}
|
||||
|
|
|
|||
45
tests/Aetheria.Shared.Tests/PickupGenerationTests.cs
Normal file
45
tests/Aetheria.Shared.Tests/PickupGenerationTests.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* 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.Linq;
|
||||
using Xunit;
|
||||
|
||||
// Pickup is a capability (aetheria-release cut loot-2), so a ship that can pick things up carries the gear that grants
|
||||
// it. Generation is the one place a ship's gear is chosen, so this runs the real catalog through it.
|
||||
public sealed partial class RunStartTests
|
||||
{
|
||||
// The beams a pack carries fitted. A station may stock one in its hold to sell, which is the point of selling it.
|
||||
private int PickupGear(EntityPack pack) => pack.Equipment.Select(fitted => fitted.item)
|
||||
.Count(item => _items.GetData(item) is GearData gear && gear.Behaviors.Any(b => b is PickupData));
|
||||
|
||||
[Fact]
|
||||
public void EveryGeneratedShipCarriesATractorBeam()
|
||||
{
|
||||
// Every ship hull a product sells.
|
||||
var generator = PreludeGenerator();
|
||||
foreach (var hull in LoadoutGenerator.SoldHulls(_cache).Where(hull => hull.HullType == HullType.Ship))
|
||||
Assert.True(PickupGear(generator.GenerateShipLoadout(candidate => candidate == hull)) == 1, $"a {hull.Name} carries exactly one tractor beam");
|
||||
|
||||
// Ships generated for each faction of a main galaxy, and the starting ship the game stages.
|
||||
var main = Launch(new MainGalaxy(), Inputs(() => GalaxySeed));
|
||||
Assert.Empty(main.failures);
|
||||
var factions = main.galaxy.Factions.ToArray();
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
var faction = factions[i % factions.Length];
|
||||
var maker = new LoadoutGenerator(ref _items.Random, _items, main.galaxy, main.galaxy.Entrance, faction, .5f);
|
||||
Assert.True(PickupGear(maker.GenerateShipLoadout()) == 1, $"ship {i} of {faction.Name} carries exactly one tractor beam");
|
||||
}
|
||||
Assert.Equal(1, main.staged.Player.Equipment.Count(item => item.Behaviors.OfType<Pickup>().Any()));
|
||||
Assert.All(main.arena.Pack.Entities.Where(pack => pack is ShipPack), pack => Assert.Equal(1, PickupGear(pack)));
|
||||
|
||||
// Stations and turrets share the interior outfitting and get none.
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
Assert.Equal(0, PickupGear(generator.GenerateStationLoadout()));
|
||||
Assert.Equal(0, PickupGear(generator.GenerateTurretLoadout()));
|
||||
}
|
||||
Assert.All(main.arena.Pack.Entities.Where(pack => !(pack is ShipPack)), pack => Assert.Equal(0, PickupGear(pack)));
|
||||
}
|
||||
}
|
||||
706
tests/Aetheria.Shared.Tests/PickupTests.cs
Normal file
706
tests/Aetheria.Shared.Tests/PickupTests.cs
Normal file
|
|
@ -0,0 +1,706 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CultMath;
|
||||
using GameCult.Caching;
|
||||
using UniRx;
|
||||
using Xunit;
|
||||
using static CultMath.math;
|
||||
using float2 = CultMath.float2;
|
||||
using float3 = CultMath.float3;
|
||||
using int2 = CultMath.int2;
|
||||
|
||||
// The tractor beam in the sim (aetheria-release cut loot-2): a Pickup behaviour spins its beam up in sim time and runs
|
||||
// one timed grab of a floating item, ending in Zone.TryPickUp. Rulings: jettison-shared-floating-items,
|
||||
// unity-presents-only-r3, sim-speed-presentation; the question loot-grab-release-cancels, built hold-required. Every
|
||||
// grab here is run by Zone.Update ticking a ship's own gear: nothing calls the behaviour directly.
|
||||
public sealed class PickupTests : IDisposable
|
||||
{
|
||||
private readonly string _root = Path.Combine(Path.GetTempPath(), "aetheria-pickup-" + Guid.NewGuid().ToString("N"));
|
||||
private readonly List<CultCache> _caches = new List<CultCache>();
|
||||
|
||||
public PickupTests() => Directory.CreateDirectory(_root);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var c in _caches) c.Dispose();
|
||||
Directory.Delete(_root, true);
|
||||
}
|
||||
|
||||
private static PerformanceStat Constant(float v) => new PerformanceStat { Min = v, Max = v };
|
||||
|
||||
private static Shape Solid(int w, int h)
|
||||
{
|
||||
var shape = new Shape(w, h);
|
||||
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
|
||||
return shape;
|
||||
}
|
||||
|
||||
private static PickupData Beam(float extend = .3f, float envelop = .2f, float pull = .5f) => new PickupData
|
||||
{
|
||||
Range = Constant(75), BeamRadius = Constant(25), GrabPower = Constant(.5f), SpinRate = Constant(2),
|
||||
ExtendTime = Constant(extend), EnvelopTime = Constant(envelop), PullTime = Constant(pull)
|
||||
};
|
||||
|
||||
private sealed class Lab
|
||||
{
|
||||
public ItemManager Items;
|
||||
public Zone Zone;
|
||||
public int Lot = 1;
|
||||
|
||||
public EquippableItem Make(string name) => new EquippableItem
|
||||
{
|
||||
Data = Items.ItemData.RefOf<ItemData>(Items.ItemData.GetByName<EquippableItemData>(name)), Durability = 1, Lot = Lot++
|
||||
};
|
||||
|
||||
public SimpleCommodity Ore(int quantity) => new SimpleCommodity
|
||||
{
|
||||
Data = Items.ItemData.RefOf<ItemData>(Items.ItemData.GetByName<SimpleCommodityData>("Ore")), Quantity = quantity
|
||||
};
|
||||
}
|
||||
|
||||
private Lab Build(PickupData beam, float lifetime = 30f)
|
||||
{
|
||||
var settings = new GameplaySettings
|
||||
{
|
||||
DefaultEntitySettings = new EntitySettings(),
|
||||
Tiers = new[] { new RarityTier { Name = "Common", Quality = .5f, Rarity = 0, Color = new float3(1, 1, 1) } },
|
||||
QualityPriceModifier = new ExponentialLerp(),
|
||||
TargetDetectionInfoThreshold = .1f, TargetArmorInfoThreshold = .2f, TargetGearInfoThreshold = .8f,
|
||||
FiringArc = 170, CommitHorizon = .5f, SchematicCellSize = 1f,
|
||||
UnaidedAccuracy = .05f, UnaidedTracking = 10f, UnaidedPrecision = 2f, AgentMinHitProbability = 0f,
|
||||
LootDropProbability = 0, LootDropVelocity = 25, FloatingItemLifetime = lifetime
|
||||
};
|
||||
var cache = AetheriaStores.Open(Path.Combine(_root, Guid.NewGuid().ToString("N") + ".cc"), catalogWritable: true);
|
||||
_caches.Add(cache);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.Upsert(new HullData { Name = "Skiff", HullType = HullType.Ship, Shape = Solid(5, 5), Durability = 1000000, Mass = 1000 });
|
||||
cache.Upsert(new GearData
|
||||
{
|
||||
Name = "Tractor", Hardpoint = HardpointType.Tool, Shape = Solid(1, 1), Durability = 1,
|
||||
MinimumTemperature = -1000, MaximumTemperature = 1000, OptimalTemperature = 0, PlateauWidth = 2000,
|
||||
Behaviors = { beam }
|
||||
});
|
||||
cache.Upsert(new GearData
|
||||
{
|
||||
Name = "Spare", Hardpoint = HardpointType.Tool, Shape = Solid(1, 1), Durability = 1,
|
||||
MinimumTemperature = -1000, MaximumTemperature = 1000, OptimalTemperature = 0, PlateauWidth = 2000
|
||||
});
|
||||
cache.Upsert(new GearData
|
||||
{
|
||||
Name = "Block", Hardpoint = HardpointType.Tool, Shape = Solid(2, 2), Durability = 1,
|
||||
MinimumTemperature = -1000, MaximumTemperature = 1000, OptimalTemperature = 0, PlateauWidth = 2000
|
||||
});
|
||||
cache.Upsert(new CargoBayData { Name = "Crate", Shape = new Shape(), InteriorShape = Solid(4, 4), Durability = 4 });
|
||||
cache.Upsert(new SimpleCommodityData { Name = "Ore", Shape = Solid(1, 1), MaxStack = 100 });
|
||||
cache.FlushAsync().Wait();
|
||||
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 9000; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack { Radius = 0 }, new GalaxyZone { Name = "Pickup", Owner = null }, null);
|
||||
return new Lab { Items = items, Zone = zone };
|
||||
}
|
||||
|
||||
// A ship with a cargo bay, aimed along +x and (unless told otherwise) carrying the tractor beam.
|
||||
private static Ship Spawn(Lab lab, float3 at, bool beam = true)
|
||||
{
|
||||
var hull = lab.Items.ItemData.RefOf<ItemData>(lab.Items.ItemData.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(lab.Items, lab.Zone, new EquippableItem { Data = hull, Durability = 1000000, Lot = lab.Lot++ }, new EntitySettings());
|
||||
var crate = lab.Make("Crate");
|
||||
Assert.True(ship.TryFindSpace(crate, out var cratePos));
|
||||
Assert.True(ship.TryEquip(crate, cratePos));
|
||||
if (beam) Equip(ship, lab.Make("Tractor"));
|
||||
lab.Zone.Admit(ship, piloted: false);
|
||||
ship.Position = at;
|
||||
ship.Aim = float3(1, 0, 0);
|
||||
return ship;
|
||||
}
|
||||
|
||||
private static void Equip(Ship ship, EquippableItem gear)
|
||||
{
|
||||
Assert.True(ship.TryFindSpace(gear, out var pos));
|
||||
Assert.True(ship.TryEquip(gear, pos));
|
||||
}
|
||||
|
||||
private static FloatingItem Drift(Lab lab, float x, float y, float2 velocity = default, int ore = 5) =>
|
||||
lab.Zone.Release(lab.Ore(ore), float2(x, y), velocity);
|
||||
|
||||
private static List<GrabEvent> Watch(Entity ship, CompositeDisposable bag)
|
||||
{
|
||||
var events = new List<GrabEvent>();
|
||||
ship.Capabilities.Grab.Subscribe(events.Add).AddTo(bag);
|
||||
return events;
|
||||
}
|
||||
|
||||
private static void Tick(Lab lab, int ticks, float dt)
|
||||
{
|
||||
for (var i = 0; i < ticks; i++) lab.Zone.Update(dt);
|
||||
}
|
||||
|
||||
// Ticks until the condition holds, for at most twenty sim seconds; a grab that never comes fails here instead of hanging.
|
||||
private static void Until(Lab lab, float dt, Func<bool> done)
|
||||
{
|
||||
for (var tick = 0; tick < 1200 && !done(); tick++) lab.Zone.Update(dt);
|
||||
Assert.True(done(), "the expected moment never came");
|
||||
}
|
||||
|
||||
private static void Kill(Entity e) => e.HeatstrokeDeath.OnNext(Unit.Default);
|
||||
|
||||
[Fact]
|
||||
public void NoPickupBehaviourNoGrab()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
|
||||
// The same ship and item, with and without the gear: only the gear grabs.
|
||||
var bare = Build(Beam());
|
||||
var plain = Spawn(bare, float3(0, 0, 0), beam: false);
|
||||
var left = Drift(bare, 40, 0);
|
||||
var plainEvents = Watch(plain, bag);
|
||||
plain.TractorCommand = 1;
|
||||
Tick(bare, 240, 1 / 60f);
|
||||
Assert.True(left.Holder == null, "left is held");
|
||||
Assert.True(bare.Zone.FloatingItems.Contains(left), "left left the zone");
|
||||
Assert.Equal(0f, plain.TractorPower);
|
||||
Assert.Empty(plainEvents);
|
||||
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var item = Drift(lab, 40, 0);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Tick(lab, 60, 1 / 60f);
|
||||
Assert.True(ReferenceEquals(ship, item.Holder), "item is not held by ship");
|
||||
Assert.Equal(GrabPhase.Started, events.First().Phase);
|
||||
Assert.True(ship.TractorPower > 0f);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(60)]
|
||||
[InlineData(600)]
|
||||
public void TheBeamSpinsUpInSimTime(int ticksPerSecond)
|
||||
{
|
||||
var dt = 1f / ticksPerSecond;
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var item = Drift(lab, 40, 0);
|
||||
|
||||
// Power rises two per sim second and not faster, at any tick rate; the claim comes on the tick it passes GrabPower.
|
||||
ship.TractorCommand = 1;
|
||||
int claim = -1, full = -1;
|
||||
var before = 0f;
|
||||
for (var tick = 1; tick <= ticksPerSecond; tick++)
|
||||
{
|
||||
lab.Zone.Update(dt);
|
||||
if (claim < 0 && item.Holder != null)
|
||||
{
|
||||
claim = tick;
|
||||
Assert.True(ship.TractorPower >= .5f);
|
||||
Assert.True(before < .5f);
|
||||
}
|
||||
if (full < 0 && ship.TractorPower >= 1f) full = tick;
|
||||
before = ship.TractorPower;
|
||||
}
|
||||
Assert.Equal(ticksPerSecond / 4, claim);
|
||||
Assert.Equal(ticksPerSecond / 2, full);
|
||||
|
||||
// Command 0 spins it down at the same rate.
|
||||
ship.TractorCommand = 0;
|
||||
var empty = -1;
|
||||
for (var tick = 1; tick <= ticksPerSecond; tick++)
|
||||
{
|
||||
lab.Zone.Update(dt);
|
||||
if (empty < 0 && ship.TractorPower <= 0f) empty = tick;
|
||||
}
|
||||
Assert.Equal(ticksPerSecond / 2, empty);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(.3f, .2f, .5f, 60)]
|
||||
[InlineData(1f, 1f, 1f, 180)]
|
||||
public void AGrabTakesExactlyItsPhasesThenCommits(float extend, float envelop, float pull, int ticksAfterClaim)
|
||||
{
|
||||
foreach (var rate in new[] { 60, 600 })
|
||||
{
|
||||
var dt = 1f / rate;
|
||||
var lab = Build(Beam(extend, envelop, pull));
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var item = Drift(lab, 40, 0);
|
||||
ship.TractorCommand = 1;
|
||||
int claim = -1, commit = -1;
|
||||
for (var tick = 1; tick <= rate * 6 && commit < 0; tick++)
|
||||
{
|
||||
lab.Zone.Update(dt);
|
||||
if (claim < 0 && item.Holder != null) claim = tick;
|
||||
if (!lab.Zone.FloatingItems.Contains(item)) commit = tick;
|
||||
}
|
||||
Assert.True(claim > 0);
|
||||
Assert.Equal(ticksAfterClaim * rate / 60, commit - claim);
|
||||
Assert.Equal(5, ship.CargoBays.Single().Cargo.Keys.OfType<SimpleCommodity>().Single().Quantity);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GrabEventsNameTheFloatingItem()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var other = Drift(lab, 40, 20);
|
||||
var item = Drift(lab, 40, 0);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
for (var tick = 1; tick <= 150; tick++) lab.Zone.Update(1 / 60f);
|
||||
Assert.Equal(new[] { GrabPhase.Started, GrabPhase.Extend, GrabPhase.Envelop, GrabPhase.Pull, GrabPhase.Completed },
|
||||
events.Take(5).Select(e => e.Phase).ToArray());
|
||||
Assert.All(events.Take(5), e => Assert.Equal(item.Id, e.Target));
|
||||
Assert.NotEqual(other.Id, item.Id);
|
||||
Assert.Equal(.3f, events[1].PhaseDuration, 4);
|
||||
Assert.Equal(.2f, events[2].PhaseDuration, 4);
|
||||
Assert.Equal(.5f, events[3].PhaseDuration, 4);
|
||||
}
|
||||
|
||||
// Each phase is published on the first tick at or after its boundary (.3, .5 and 1 s after the claim), at any
|
||||
// tick rate or an uneven one. The clock is a float sum, so a boundary an exact number of ticks away can land one
|
||||
// tick late (Envelop and Pull do at 1/600): the rule is "not before, at most one tick after", not a tick count.
|
||||
[Theory]
|
||||
[InlineData(1, 60, 60, 60, 60)]
|
||||
[InlineData(1, 600, 600, 600, 600)]
|
||||
[InlineData(4, 60, 24, 90, 33)]
|
||||
public void EachPhaseFiresOnTheFirstTickAtOrAfterItsBoundary(int cycle, int a, int b, int c, int d)
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var rates = cycle == 1 ? new[] { a } : new[] { a, b, c, d };
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
Drift(lab, 40, 0);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
var since = -1.0;
|
||||
var times = new List<double>();
|
||||
var steps = new List<double>();
|
||||
for (var tick = 0; tick < rates.Length * 3000 && events.Count(e => e.Phase == GrabPhase.Completed) == 0; tick++)
|
||||
{
|
||||
var dt = 1f / rates[tick % rates.Length];
|
||||
lab.Zone.Update(dt);
|
||||
if (since >= 0) since += dt;
|
||||
else if (events.Count > 0) since = 0;
|
||||
while (times.Count < events.Count) { times.Add(since); steps.Add(dt); }
|
||||
}
|
||||
var phases = events.Select(e => e.Phase).ToList();
|
||||
Assert.Equal(GrabPhase.Completed, phases.Last());
|
||||
foreach (var (phase, boundary) in new[] { (GrabPhase.Envelop, .3), (GrabPhase.Pull, .5), (GrabPhase.Completed, 1.0) })
|
||||
{
|
||||
var i = phases.IndexOf(phase);
|
||||
Assert.True(i >= 0, "the phase never came");
|
||||
Assert.True(times[i] >= boundary - 1e-5, "the phase fired before its boundary");
|
||||
// Where no tick lands exactly on a boundary (the uneven cycle) the tick before must be strictly short of it;
|
||||
// on a regular grid the float sum may leave that tick a hair short, so it may be equal.
|
||||
var slack = cycle == 1 ? 1e-5 : -1e-5;
|
||||
Assert.True(times[i] - steps[i] < boundary + slack, "the phase fired after the first tick at or past its boundary");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReleasingTheBeamCancelsAndTheItemFloatsFree()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
const float dt = 1 / 60f;
|
||||
var lab = Build(Beam(), lifetime: 2f);
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var item = Drift(lab, 40, 0, float2(3, 1));
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
var tick = 0;
|
||||
while (!events.Any(e => e.Phase == GrabPhase.Pull) && tick++ < 600) lab.Zone.Update(dt);
|
||||
Assert.True(ReferenceEquals(ship, item.Holder), "item is not held by ship");
|
||||
var velocity = item.Body.Velocity;
|
||||
var drift = item.Body.Drift;
|
||||
|
||||
// The key comes up during the pull: Power falls below GrabPower and the grab is dropped.
|
||||
ship.TractorCommand = 0;
|
||||
while (!events.Any(e => e.Phase == GrabPhase.Cancelled) && tick++ < 600) lab.Zone.Update(dt);
|
||||
Assert.True(tick < 110);
|
||||
Assert.DoesNotContain(events, e => e.Phase == GrabPhase.Completed);
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
Assert.True(lab.Zone.FloatingItems.Contains(item), "item left the zone");
|
||||
// (It has taken the one step of its freeing tick, so it is the held velocity less a tick's drag.)
|
||||
Assert.Equal(velocity.x, item.Body.Velocity.x, velocity.x * .01f);
|
||||
Assert.Equal(velocity.y, item.Body.Velocity.y, velocity.y * .01f);
|
||||
Assert.True(velocity.x > 1f);
|
||||
|
||||
// It drifts on with that velocity and expires on the clock it was released on, not from the cancel.
|
||||
var at = item.Body.Position;
|
||||
lab.Zone.Update(dt);
|
||||
Assert.True(item.Body.Position.x > at.x);
|
||||
while (lab.Zone.FloatingItems.Contains(item) && tick++ < 600) lab.Zone.Update(dt);
|
||||
Assert.InRange(tick, 118, 122);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AHoldThatFillsMidGrabCancelsAtTheEnd()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var bay = ship.CargoBays.Single();
|
||||
var item = Drift(lab, 40, 0);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Until(lab, 1 / 60f, () => item.Holder != null);
|
||||
|
||||
// The hold fills while the grab runs: the item slides in and then floats free.
|
||||
for (var i = 0; i < 16; i++) Assert.True(bay.TryStore(lab.Make("Spare")));
|
||||
var tick = 0;
|
||||
while (!events.Any(e => e.Phase == GrabPhase.Cancelled) && tick++ < 300) lab.Zone.Update(1 / 60f);
|
||||
Assert.Equal(new[] { GrabPhase.Started, GrabPhase.Extend, GrabPhase.Envelop, GrabPhase.Pull, GrabPhase.Cancelled },
|
||||
events.Select(e => e.Phase).ToArray());
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
Assert.True(lab.Zone.FloatingItems.Contains(item), "item left the zone");
|
||||
Assert.Equal(16, bay.Cargo.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AFullHoldNeverClaimsWhatItCannotStore()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var bay = ship.CargoBays.Single();
|
||||
for (var i = 0; i < 16; i++) Assert.True(bay.TryStore(lab.Make("Spare")));
|
||||
var item = Drift(lab, 40, 0, float2(3, 1));
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Tick(lab, 300, 1 / 60f);
|
||||
Assert.Equal(1f, ship.TractorPower);
|
||||
Assert.Empty(events);
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
Assert.True(item.Body.Position.x > 40f, "item was pinned instead of drifting");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnItemTheHoldCannotStoreDoesNotBlockOneItCan()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var bay = ship.CargoBays.Single();
|
||||
for (var i = 0; i < 15; i++) Assert.True(bay.TryStore(lab.Make("Spare")));
|
||||
// One free cell: the 2x2 block is nearest the axis and cannot be stored; the ore beside it can.
|
||||
var block = lab.Zone.Release(lab.Make("Block"), float2(30, 0), default);
|
||||
var ore = Drift(lab, 50, 10);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Tick(lab, 150, 1 / 60f);
|
||||
Assert.Contains(events, e => e.Phase == GrabPhase.Completed);
|
||||
Assert.All(events, e => Assert.Equal(ore.Id, e.Target));
|
||||
Assert.False(lab.Zone.FloatingItems.Contains(ore), "ore is still in the zone");
|
||||
Assert.True(lab.Zone.FloatingItems.Contains(block), "block left the zone");
|
||||
Assert.True(block.Holder == null, "block is held");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnItemInTheBeamsFarCornerIsCollected()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
// 74 along and 20 off the axis: inside the beam, though 76.7 from the ship, past Range as a straight distance.
|
||||
var corner = Drift(lab, 74, 20);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Tick(lab, 150, 1 / 60f);
|
||||
Assert.DoesNotContain(events, e => e.Phase == GrabPhase.Cancelled);
|
||||
Assert.Contains(events, e => e.Phase == GrabPhase.Completed);
|
||||
Assert.False(lab.Zone.FloatingItems.Contains(corner), "corner is still in the zone");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnItemLeavingTheBeamBeforeThePullIsCancelled()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var item = Drift(lab, 40, 0);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Until(lab, 1 / 60f, () => item.Holder != null);
|
||||
|
||||
// The pilot swings the beam away while the grab extends: the item is off the axis and the grab is dropped.
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
Tick(lab, 3, 1 / 60f);
|
||||
Assert.Equal(GrabPhase.Cancelled, events.Last().Phase);
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OneHolderPerItem()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var a = Spawn(lab, float3(0, 0, 0));
|
||||
var b = Spawn(lab, float3(0, 0, 10));
|
||||
var eventsA = Watch(a, bag);
|
||||
var eventsB = Watch(b, bag);
|
||||
|
||||
// One item, two beams: exactly one claims it, the other never does, and the non-holder cannot take it.
|
||||
var only = Drift(lab, 40, 5);
|
||||
a.TractorCommand = 1;
|
||||
b.TractorCommand = 1;
|
||||
Tick(lab, 20, 1 / 60f);
|
||||
Assert.Equal(1, new[] { eventsA, eventsB }.Count(e => e.Any(x => x.Phase == GrabPhase.Started)));
|
||||
var holder = only.Holder;
|
||||
var loser = ReferenceEquals(holder, a) ? b : a;
|
||||
Assert.False(lab.Zone.TryPickUp(loser, only.Id));
|
||||
Assert.False(lab.Zone.TryHold(loser, only.Id));
|
||||
Assert.True(lab.Zone.FloatingItems.Contains(only), "only left the zone");
|
||||
Assert.Empty(loser.CargoBays.Single().Cargo);
|
||||
Tick(lab, 120, 1 / 60f);
|
||||
Assert.False(lab.Zone.FloatingItems.Contains(only), "only is still in the zone");
|
||||
Assert.Single(holder.CargoBays.Single().Cargo);
|
||||
Assert.Empty(loser.CargoBays.Single().Cargo);
|
||||
|
||||
// Two items, each nearest a different beam's axis: each ship holds its own.
|
||||
var first = Drift(lab, 60, 0);
|
||||
var second = Drift(lab, 60, 10);
|
||||
Tick(lab, 20, 1 / 60f);
|
||||
Assert.True(first.Holder != null, "first is free");
|
||||
Assert.True(second.Holder != null, "second is free");
|
||||
Assert.False(ReferenceEquals(first.Holder, second.Holder), "one holder for two items");
|
||||
|
||||
// Both ships' nearest item is the same one: the one that loses it takes the other.
|
||||
var shared = Build(Beam());
|
||||
var p = Spawn(shared, float3(0, 0, 0));
|
||||
var q = Spawn(shared, float3(0, 0, 10));
|
||||
var near = Drift(shared, 40, 5);
|
||||
var far = Drift(shared, 40, -14);
|
||||
p.TractorCommand = 1;
|
||||
q.TractorCommand = 1;
|
||||
Tick(shared, 20, 1 / 60f);
|
||||
Assert.True(near.Holder != null, "near is free");
|
||||
Assert.True(far.Holder != null, "far is free");
|
||||
Assert.False(ReferenceEquals(near.Holder, far.Holder), "one holder for two items");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AGrabIsDroppedWhenItsGearGoesInactiveOrTheItemFallsOutOfRangeBeforeThePull()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
const float dt = 1 / 60f;
|
||||
|
||||
// Gear switched off mid-grab: cancelled, the item free.
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var item = Drift(lab, 40, 0);
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Tick(lab, 30, dt);
|
||||
Assert.True(ReferenceEquals(ship, item.Holder), "item is not held by ship");
|
||||
ship.Equipment.Single(e => e.Behaviors.OfType<Pickup>().Any()).Enabled.Value = false;
|
||||
Tick(lab, 2, dt);
|
||||
Assert.Equal(GrabPhase.Cancelled, events.Last().Phase);
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
|
||||
// The ship leaves while the beam is still extending: cancelled. The same departure during the pull is not.
|
||||
foreach (var departAtPull in new[] { false, true })
|
||||
{
|
||||
var other = Build(Beam());
|
||||
var pilot = Spawn(other, float3(0, 0, 0));
|
||||
var target = Drift(other, 40, 0);
|
||||
var seen = Watch(pilot, bag);
|
||||
pilot.TractorCommand = 1;
|
||||
Until(other, dt, () => seen.Any(e => departAtPull ? e.Phase == GrabPhase.Pull : e.Phase == GrabPhase.Extend));
|
||||
pilot.Position = float3(-200, 0, 0);
|
||||
Tick(other, 120, dt);
|
||||
if (departAtPull)
|
||||
{
|
||||
Assert.Equal(GrabPhase.Completed, seen.First(e => e.Phase == GrabPhase.Completed || e.Phase == GrabPhase.Cancelled).Phase);
|
||||
Assert.False(other.Zone.FloatingItems.Contains(target), "target is still in the zone");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(GrabPhase.Cancelled, seen.First(e => e.Phase == GrabPhase.Completed || e.Phase == GrabPhase.Cancelled).Phase);
|
||||
Assert.True(target.Holder == null, "target is held");
|
||||
Assert.True(other.Zone.FloatingItems.Contains(target), "target left the zone");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectionIsTheItemNearestTheBeamAxis()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
|
||||
// The nearer to the axis wins, though the other is closer to the ship.
|
||||
var near = Drift(lab, 20, 12);
|
||||
var onAxis = Drift(lab, 60, 3);
|
||||
Tick(lab, 30, 1 / 60f);
|
||||
Assert.True(ReferenceEquals(ship, onAxis.Holder), "onAxis is not held by ship");
|
||||
Assert.True(near.Holder == null, "near is held");
|
||||
Assert.Equal(onAxis.Id, events.First().Target);
|
||||
|
||||
// Out of the beam, past its range or behind the ship, an item is never taken.
|
||||
foreach (var (x, y) in new[] { (30f, 30f), (80f, 0f), (-10f, 0f) })
|
||||
{
|
||||
var other = Build(Beam());
|
||||
var lone = Spawn(other, float3(0, 0, 0));
|
||||
var stray = Drift(other, x, y);
|
||||
var attempts = Watch(lone, bag);
|
||||
lone.TractorCommand = 1;
|
||||
Tick(other, 240, 1 / 60f);
|
||||
Assert.Empty(attempts);
|
||||
Assert.True(stray.Holder == null, "stray is held");
|
||||
Assert.True(other.Zone.FloatingItems.Contains(stray), "stray left the zone");
|
||||
}
|
||||
|
||||
// Equally far from the axis, the lower id.
|
||||
var tie = Build(Beam());
|
||||
var pilot = Spawn(tie, float3(0, 0, 0));
|
||||
var lower = Drift(tie, 40, 5);
|
||||
var higher = Drift(tie, 40, -5);
|
||||
pilot.TractorCommand = 1;
|
||||
Tick(tie, 30, 1 / 60f);
|
||||
Assert.True(ReferenceEquals(pilot, lower.Holder), "lower is not held by pilot");
|
||||
Assert.True(higher.Holder == null, "higher is held");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AHeldItemIsPlacedNotIntegratedAndDoesNotExpire()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
const float dt = 1 / 60f;
|
||||
var lab = Build(Beam(), lifetime: 1f);
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var item = Drift(lab, 40, 0, float2(3, 0));
|
||||
var events = Watch(ship, bag);
|
||||
ship.TractorCommand = 1;
|
||||
Until(lab, dt, () => item.Holder != null);
|
||||
var claimed = item.Body.Position;
|
||||
Assert.Equal(claimed, item.HeldFrom);
|
||||
|
||||
// Through Extend and Envelop the item stays where it was claimed, whatever velocity it had.
|
||||
for (var guard = 0; guard < 1200 && !events.Any(e => e.Phase == GrabPhase.Pull); guard++)
|
||||
{
|
||||
lab.Zone.Update(dt);
|
||||
if (events.Any(e => e.Phase == GrabPhase.Pull)) break;
|
||||
Assert.Equal(claimed, item.Body.Position);
|
||||
}
|
||||
Assert.Contains(events, e => e.Phase == GrabPhase.Pull);
|
||||
|
||||
// During the pull it moves toward the holder, and a held item outlives its lifetime.
|
||||
lab.Zone.Update(dt * 10);
|
||||
Assert.True(item.Body.Position.x < claimed.x);
|
||||
Assert.True(item.Body.Position.x > 0);
|
||||
var held = Build(Beam(), lifetime: 1f);
|
||||
var carrier = Spawn(held, float3(5, 0, 7));
|
||||
var stuck = Drift(held, 40, 0);
|
||||
Assert.True(held.Zone.TryHold(carrier, stuck.Id));
|
||||
Tick(held, 180, dt);
|
||||
Assert.True(held.Zone.FloatingItems.Contains(stuck), "stuck left the zone");
|
||||
Assert.Equal(float2(40, 0), stuck.Body.Position);
|
||||
|
||||
// The pose eases (smoothstep): a quarter of the progress is 15.625 per cent of the way, not 25.
|
||||
held.Zone.SetHeldProgress(stuck.Id, .25f);
|
||||
held.Zone.Update(dt);
|
||||
Assert.Equal(40f - 35f * .15625f, stuck.Body.Position.x, 3);
|
||||
Assert.Equal(7f * .15625f, stuck.Body.Position.y, 3);
|
||||
|
||||
// Placed, not drifted: the same progress held for two steps puts it at the same halfway point.
|
||||
held.Zone.SetHeldProgress(stuck.Id, .5f);
|
||||
held.Zone.Update(dt);
|
||||
held.Zone.Update(dt);
|
||||
Assert.Equal(22.5f, stuck.Body.Position.x, 3);
|
||||
Assert.Equal(3.5f, stuck.Body.Position.y, 3);
|
||||
|
||||
// At progress 1 it is at the holder; freed, an item past its lifetime goes on the next step.
|
||||
held.Zone.SetHeldProgress(stuck.Id, 1);
|
||||
held.Zone.Update(dt);
|
||||
Assert.Equal(5f, stuck.Body.Position.x, 3);
|
||||
Assert.Equal(7f, stuck.Body.Position.y, 3);
|
||||
held.Zone.Free(stuck.Id);
|
||||
held.Zone.Update(dt);
|
||||
Assert.False(held.Zone.FloatingItems.Contains(stuck), "stuck is still in the zone");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnInactiveBeamSpinsDownAndMustSpinUpAgain()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
const float dt = 1 / 60f;
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
var gear = ship.Equipment.Single(e => e.Behaviors.OfType<Pickup>().Any());
|
||||
ship.TractorCommand = 1;
|
||||
Tick(lab, 60, dt);
|
||||
Assert.Equal(1f, ship.TractorPower);
|
||||
|
||||
// Switched off with the key still down, the beam spins down at the spin rate...
|
||||
gear.Enabled.Value = false;
|
||||
Tick(lab, 15, dt);
|
||||
Assert.InRange(ship.TractorPower, .45f, .55f);
|
||||
Tick(lab, 30, dt);
|
||||
Assert.Equal(0f, ship.TractorPower);
|
||||
|
||||
// ...and switched back on it spins up from nothing: an item in the beam is not claimed at once.
|
||||
var item = Drift(lab, 40, 0);
|
||||
var events = Watch(ship, bag);
|
||||
gear.Enabled.Value = true;
|
||||
Tick(lab, 10, dt);
|
||||
Assert.Empty(events);
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
Tick(lab, 20, dt);
|
||||
Assert.True(ReferenceEquals(ship, item.Holder), "item is not held by ship");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemovingOneHolderFreesOnlyItsOwnItems()
|
||||
{
|
||||
var lab = Build(Beam());
|
||||
var a = Spawn(lab, float3(0, 0, 0));
|
||||
var b = Spawn(lab, float3(0, 0, 50));
|
||||
var mine = Drift(lab, 40, 0);
|
||||
var theirs = Drift(lab, 40, 50);
|
||||
a.TractorCommand = 1;
|
||||
b.TractorCommand = 1;
|
||||
Tick(lab, 30, 1 / 60f);
|
||||
Assert.True(ReferenceEquals(a, mine.Holder), "mine is not held by a");
|
||||
Assert.True(ReferenceEquals(b, theirs.Holder), "theirs is not held by b");
|
||||
|
||||
Kill(a);
|
||||
Assert.True(mine.Holder == null, "mine is held");
|
||||
Assert.True(ReferenceEquals(b, theirs.Holder), "theirs was freed with a");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheHoldersLeavingFreesItsItems()
|
||||
{
|
||||
using var bag = new CompositeDisposable();
|
||||
var lab = Build(Beam());
|
||||
var ship = Spawn(lab, float3(0, 0, 0));
|
||||
Assert.True(ship.CargoBays.Single().TryStore(lab.Ore(7)));
|
||||
var item = Drift(lab, 40, 0, ore: 3);
|
||||
ship.TractorCommand = 1;
|
||||
Tick(lab, 30, 1 / 60f);
|
||||
Assert.True(ReferenceEquals(ship, item.Holder), "item is not held by ship");
|
||||
var before = lab.Zone.FloatingItems.Count;
|
||||
|
||||
// Death goes through the zone's one removal: the held item is free, and the ship's own cargo drops as always.
|
||||
Kill(ship);
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
Assert.True(lab.Zone.FloatingItems.Contains(item), "item left the zone");
|
||||
Assert.Equal(before + 1, lab.Zone.FloatingItems.Count);
|
||||
Assert.Equal(7, ((SimpleCommodity) lab.Zone.FloatingItems.Last().Item).Quantity);
|
||||
Tick(lab, 60, 1 / 60f);
|
||||
Assert.True(lab.Zone.FloatingItems.Contains(item), "item left the zone");
|
||||
Assert.True(item.Holder == null, "item is held");
|
||||
}
|
||||
}
|
||||
|
|
@ -101,8 +101,7 @@ public sealed class PowerBusTests : IDisposable
|
|||
|
||||
if (reactorFirst) { EquipReactor(); EquipCapacitor(); } else { EquipCapacitor(); EquipReactor(); }
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
|
||||
var reactor = ship.GetBehavior<Reactor>();
|
||||
var capacitor = ship.GetBehavior<Capacitor>();
|
||||
|
|
|
|||
|
|
@ -103,8 +103,7 @@ public sealed class PowerCurveTests : IDisposable
|
|||
Assert.True(ship.TryEquip(Mint(cache, items, reactorData)));
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Engine"))));
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
return ship;
|
||||
}
|
||||
|
||||
|
|
@ -408,9 +407,8 @@ public sealed class PowerCurveTests : IDisposable
|
|||
var ship = new Ship(items, zone, hullItem, new EntitySettings());
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Drain")), HardpointCell));
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Booster")), SecondHardpointCell));
|
||||
zone.Entities.Add(ship);
|
||||
|
||||
var error = Assert.Throws<InvalidOperationException>(() => ship.Activate());
|
||||
var error = Assert.Throws<InvalidOperationException>(() => zone.Admit(ship, piloted: false));
|
||||
Assert.Contains("Drain", error.Message); // the item whose request would be corrupted
|
||||
Assert.Contains("Booster", error.Message); // the item whose modifier would corrupt it
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,7 @@ public sealed class PowerTierTests : IDisposable
|
|||
((ReactorData) reactorData.Behaviors[0]).Charge = Constant(reactorCharge);
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, reactorData)));
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
|
||||
var reactor = ship.GetBehavior<Reactor>();
|
||||
ship.Update(0f);
|
||||
|
|
|
|||
|
|
@ -314,9 +314,8 @@ public sealed class RestoredHullsTests
|
|||
Assert.True(ship.TryEquip(fillerItem, filled.Position));
|
||||
}
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Position = float3.zero;
|
||||
ship.Activate();
|
||||
zone.Update(0f); // warm-up: resolves equipment stats before the driven ticks read them
|
||||
return ship;
|
||||
}
|
||||
|
|
@ -618,9 +617,8 @@ public sealed class RestoredHullsTests
|
|||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Test", Owner = null }, null);
|
||||
var ship = (Ship) EntitySerializer.Unpack(items, zone, pack);
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Position = float3.zero;
|
||||
ship.Activate();
|
||||
zone.Update(0f);
|
||||
|
||||
ship.MovementDirection = float2(0, 1); // full forward
|
||||
|
|
|
|||
|
|
@ -80,6 +80,52 @@ public sealed class RunSaveTests : IDisposable
|
|||
Assert.Equal(8, RecordsIn(Run));
|
||||
}
|
||||
|
||||
// Continue finds the player's entity by its place in the saved zone: the index Capture writes must be the entity's
|
||||
// place in the live zone, and survive the round trip to the same entity's pack.
|
||||
[Fact]
|
||||
public void ThePlayersZoneIndexSurvivesSaveAndContinue()
|
||||
{
|
||||
using (var cache = AetheriaStores.Open(Catalog, catalogWritable: true))
|
||||
{
|
||||
var shape = new Shape(4, 4);
|
||||
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
|
||||
foreach (var name in new[] { "Skiff", "Barge" })
|
||||
cache.Upsert(new HullData { Name = name, HullType = HullType.Ship, Shape = shape, Durability = 100, Mass = 100 });
|
||||
cache.FlushAsync().Wait();
|
||||
}
|
||||
|
||||
using (var cache = Open())
|
||||
RunSave.Commit(cache, Game(cache), Zones(2, StageZoneContents(cache), 0), new ProvenanceLedger());
|
||||
|
||||
using (var cache = Open())
|
||||
{
|
||||
var galaxy = new Galaxy(cache, cache.GetGlobal<SavedGame>(), _ => { });
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 2; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, TestSettings(), _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), galaxy.Zones[0], galaxy);
|
||||
galaxy.Zones[0].Contents = zone;
|
||||
Ship Make(string hull, int lot) => new Ship(items, zone, new EquippableItem
|
||||
{
|
||||
Data = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>(hull)), Durability = 100, Lot = lot
|
||||
}, new EntitySettings());
|
||||
var other = Make("Skiff", 1);
|
||||
var player = Make("Barge", 2);
|
||||
zone.Admit(other, piloted: false);
|
||||
zone.Admit(player, piloted: false);
|
||||
var (game, zones) = RunSave.Capture(cache, galaxy, zone, player, new SavedActionBarBinding[0], 0);
|
||||
RunSave.Commit(cache, game, zones, items.Lots);
|
||||
}
|
||||
|
||||
using (var cache = Open())
|
||||
{
|
||||
var saved = cache.GetGlobal<SavedGame>();
|
||||
Assert.Equal(1, saved.CurrentZoneEntity);
|
||||
var packs = cache.Get(saved.Zones[saved.CurrentZone]).Contents.Entities;
|
||||
Assert.Equal("Barge", cache.Get(packs[saved.CurrentZoneEntity].Hull.Data).Name);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResumeThenSaveKeepsUnloadedZoneContents()
|
||||
{
|
||||
|
|
@ -307,7 +353,8 @@ public sealed class RunSaveTests : IDisposable
|
|||
var gear = cache.Upsert(new GearData { Name = "Scanner", Hardpoint = HardpointType.Sensors, Shape = new Shape(), Price = 5 });
|
||||
var cargo = cache.Upsert(new CargoBayData { Name = "Hold", Shape = new Shape(), InteriorShape = new Shape(), Price = 3 });
|
||||
var capacitor = cache.Upsert(new GearData { Name = "Cap", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new CapacitorData() } });
|
||||
foreach (var (name, design) in new[] { ("Runabout by Generator Maker", hull.Key), ("Scanner by Generator Maker", gear.Key), ("Hold by Generator Maker", cargo.Key), ("Cap by Generator Maker", capacitor.Key) })
|
||||
var tractor = cache.Upsert(new GearData { Name = "Tractor", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new PickupData() } });
|
||||
foreach (var (name, design) in new[] { ("Runabout by Generator Maker", hull.Key), ("Scanner by Generator Maker", gear.Key), ("Hold by Generator Maker", cargo.Key), ("Cap by Generator Maker", capacitor.Key), ("Tractor by Generator Maker", tractor.Key) })
|
||||
cache.Upsert(new FactionProductData { Name = name, Design = new CultRecordRef<CraftedItemData>(design), Manufacturer = maker });
|
||||
cache.FlushAsync().Wait();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,8 +95,7 @@ public sealed class ShieldReserveTests : IDisposable
|
|||
var shieldData = cache.GetByName<GearData>("Shield");
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, shieldData)));
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
|
||||
return (ship, ship.GetBehavior<Shield>());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,8 +158,7 @@ public sealed class SignatureTests : IDisposable
|
|||
Assert.True(ship.TryEquip(item, at));
|
||||
}
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
zone.Update(1f);
|
||||
return rig;
|
||||
|
|
|
|||
|
|
@ -189,8 +189,7 @@ public sealed partial class FireControlCut124Tests
|
|||
var e = Build(TestSettings(), SolidShape(5, 4), hardpointArc: SolutionArc, weaponRange: 200f);
|
||||
var hull = e.Items.ItemData.RefOf<ItemData>(e.HullData);
|
||||
var decoy = new Ship(e.Items, e.Zone, new EquippableItem { Data = hull, Durability = 1000000, Lot = 400 }, new EntitySettings());
|
||||
e.Zone.Entities.Add(decoy);
|
||||
decoy.Activate();
|
||||
e.Zone.Admit(decoy, piloted: false);
|
||||
var at = radians(40f);
|
||||
decoy.Position = e.Shooter.Position + float3(sin(at), 0, cos(at)) * 100f;
|
||||
e.Shooter.VisibleEntities.Add(decoy);
|
||||
|
|
|
|||
|
|
@ -71,8 +71,7 @@ public sealed class StatResolverTests : IDisposable
|
|||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Battery")), HardpointCell));
|
||||
if (withBooster)
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Booster")), SecondHardpointCell));
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
return ship;
|
||||
}
|
||||
|
||||
|
|
@ -287,9 +286,8 @@ public sealed class StatResolverTests : IDisposable
|
|||
var hullItem = Mint(cache, items, cache.GetByName<HullData>("Skiff"));
|
||||
var ship = new Ship(items, zone, hullItem, new EntitySettings());
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, cache.GetByName<GearData>("Loop")), HardpointCell));
|
||||
zone.Entities.Add(ship);
|
||||
|
||||
var error = Assert.Throws<InvalidOperationException>(() => ship.Activate());
|
||||
var error = Assert.Throws<InvalidOperationException>(() => zone.Admit(ship, piloted: false));
|
||||
Assert.Contains(ship.Name, error.Message); // the entity, by name ("Skiff", HullData.Name)
|
||||
Assert.Contains(nameof(CapacitorData), error.Message);
|
||||
Assert.Contains(nameof(CapacitorData.Capacity), error.Message);
|
||||
|
|
@ -513,8 +511,7 @@ public sealed class StatResolverTests : IDisposable
|
|||
Assert.True(ship.TryEquip(boost, cells[i + 1]));
|
||||
}
|
||||
ship.OverrideShutdown = true;
|
||||
zone.Entities.Add(ship);
|
||||
ship.Activate();
|
||||
zone.Admit(ship, piloted: false);
|
||||
|
||||
Func<ConsumableItem> mint = null;
|
||||
if (consumable != null)
|
||||
|
|
|
|||
|
|
@ -138,8 +138,7 @@ public sealed class TargetingSystemTests : IDisposable
|
|||
targetingItem = target.Equipment.Single(e => e.EquippableItem == targeting);
|
||||
}
|
||||
|
||||
zone.Entities.Add(target);
|
||||
target.Activate();
|
||||
zone.Admit(target, piloted: false);
|
||||
|
||||
return (items, target, weapon, wideItem, narrowItem, targetingItem);
|
||||
}
|
||||
|
|
@ -151,8 +150,7 @@ public sealed class TargetingSystemTests : IDisposable
|
|||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("TestHull"));
|
||||
var hull = new EquippableItem { Data = hullRef, Durability = 1, Lot = 1 };
|
||||
var observer = new Ship(items, zone, hull, new EntitySettings());
|
||||
zone.Entities.Add(observer);
|
||||
observer.Activate();
|
||||
zone.Admit(observer, piloted: false);
|
||||
return observer;
|
||||
}
|
||||
|
||||
|
|
@ -388,9 +386,8 @@ public sealed class TargetingSystemTests : IDisposable
|
|||
targeting.Lot = items.Lots.Add(new Lot { Design = cache.RefOf<ItemData>(targetingData), Origin = new Attributed(), Quality = .5f, Roles = new System.Collections.Generic.List<RoleFill>() });
|
||||
Assert.True(ship.TryEquip(targeting));
|
||||
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
return ship;
|
||||
}
|
||||
|
||||
|
|
@ -429,11 +426,12 @@ public sealed class TargetingSystemTests : IDisposable
|
|||
var cargo = cache.Upsert(new CargoBayData { Name = "Crate", Shape = new Shape(), InteriorShape = new Shape(), Price = 5 });
|
||||
var capacitor = cache.Upsert(new GearData { Name = "Cap", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new CapacitorData() } });
|
||||
var targeting = cache.Upsert(new GearData { Name = "Targeting", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new TargetingSystemData() } });
|
||||
var tractor = cache.Upsert(new GearData { Name = "Tractor", Hardpoint = HardpointType.Tool, Shape = new Shape(), Price = 1, Behaviors = { new PickupData() } });
|
||||
|
||||
foreach (var (name, design) in new[]
|
||||
{
|
||||
("Armed by Maker", hull.Key), ("Gun by Maker", gun.Key), ("Crate by Maker", cargo.Key),
|
||||
("Cap by Maker", capacitor.Key), ("Targeting by Maker", targeting.Key)
|
||||
("Cap by Maker", capacitor.Key), ("Targeting by Maker", targeting.Key), ("Tractor by Maker", tractor.Key)
|
||||
})
|
||||
cache.Upsert(new FactionProductData { Name = name, Design = new CultRecordRef<CraftedItemData>(design), Manufacturer = maker });
|
||||
cache.FlushAsync().Wait();
|
||||
|
|
|
|||
|
|
@ -105,9 +105,8 @@ public sealed class ThrottleLockTests : IDisposable
|
|||
}
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, gear, ItemRotation.CounterClockwise), new int2(1, 3)));
|
||||
Assert.True(ship.TryEquip(Mint(cache, items, gear, ItemRotation.Clockwise), new int2(3, 3)));
|
||||
zone.Entities.Add(ship);
|
||||
zone.Admit(ship, piloted: false);
|
||||
ship.Aim = float3(0, 0, 1);
|
||||
ship.Activate();
|
||||
// A thruster is live from its first performance update; the allocator reads that every tick.
|
||||
foreach (var item in ship.Equipment) item.UpdatePerformance();
|
||||
|
||||
|
|
|
|||
|
|
@ -130,10 +130,8 @@ public sealed class VapourCloudTests : IDisposable
|
|||
// Held off until a test pulls the trigger: the dump vents on its first run.
|
||||
lab.Dump.Enabled.Value = false;
|
||||
|
||||
zone.Entities.Add(lab.Observer);
|
||||
zone.Entities.Add(lab.Target);
|
||||
lab.Observer.Activate();
|
||||
lab.Target.Activate();
|
||||
zone.Admit(lab.Observer, piloted: false);
|
||||
zone.Admit(lab.Target, piloted: false);
|
||||
lab.Observer.Position = float3(0, 0, 0);
|
||||
lab.Observer.Aim = float3(0, 0, 1);
|
||||
lab.Target.Position = float3(0, 0, distance);
|
||||
|
|
|
|||
Loading…
Reference in a new issue