diff --git a/Assets/Materials/Build.meta b/Assets/Materials/Build.meta
new file mode 100644
index 00000000..d0de8cad
--- /dev/null
+++ b/Assets/Materials/Build.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b8bc301eacaa25d4a9bbdf35f989ad2e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Materials/Build/WandAreaBrush.mat b/Assets/Materials/Build/WandAreaBrush.mat
new file mode 100644
index 00000000..c7c6e3bb
--- /dev/null
+++ b/Assets/Materials/Build/WandAreaBrush.mat
@@ -0,0 +1,79 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+ serializedVersion: 6
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: WandAreaBrush
+ m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
+ m_ShaderKeywords: _ALPHAPREMULTIPLY_ON
+ m_LightmapFlags: 4
+ m_EnableInstancingVariants: 0
+ m_DoubleSidedGI: 0
+ m_CustomRenderQueue: 3000
+ stringTagMap:
+ RenderType: Transparent
+ disabledShaderPasses: []
+ m_SavedProperties:
+ serializedVersion: 3
+ m_TexEnvs:
+ - _BumpMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailAlbedoMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailMask:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailNormalMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _EmissionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MainTex:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MetallicGlossMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _OcclusionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _ParallaxMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ m_Floats:
+ - _BumpScale: 1
+ - _Cutoff: 0.5
+ - _DetailNormalMapScale: 1
+ - _DstBlend: 10
+ - _GlossMapScale: 1
+ - _Glossiness: 0.5
+ - _GlossyReflections: 1
+ - _Metallic: 0
+ - _Mode: 3
+ - _OcclusionStrength: 1
+ - _Parallax: 0.02
+ - _SmoothnessTextureChannel: 0
+ - _SpecularHighlights: 1
+ - _SrcBlend: 1
+ - _UVSec: 0
+ - _ZWrite: 0
+ m_Colors:
+ - _Color: {r: 0.031886935, g: 1, b: 0, a: 0.48235294}
+ - _EmissionColor: {r: 0.014443844, g: 0.014443844, b: 0.014443844, a: 1}
+ m_BuildTextureStacks: []
diff --git a/Assets/Materials/Build/WandAreaBrush.mat.meta b/Assets/Materials/Build/WandAreaBrush.mat.meta
new file mode 100644
index 00000000..fce4f1f5
--- /dev/null
+++ b/Assets/Materials/Build/WandAreaBrush.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9e00eb9bc0cd7be478e657c251ce0680
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 2100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Materials/Build/WandTop.mat b/Assets/Materials/Build/WandTop.mat
new file mode 100644
index 00000000..03962f30
--- /dev/null
+++ b/Assets/Materials/Build/WandTop.mat
@@ -0,0 +1,82 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+ serializedVersion: 6
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: WandTop
+ m_Shader: {fileID: 4800000, guid: 9027c75630615624b8d7e4b02852aa4e, type: 3}
+ m_ShaderKeywords:
+ m_LightmapFlags: 4
+ m_EnableInstancingVariants: 0
+ m_DoubleSidedGI: 0
+ m_CustomRenderQueue: -1
+ stringTagMap: {}
+ disabledShaderPasses: []
+ m_SavedProperties:
+ serializedVersion: 3
+ m_TexEnvs:
+ - _BumpMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailAlbedoMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailMask:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _DetailNormalMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _EmissionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MainTex:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _MetallicGlossMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _OcclusionMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _ParallaxMap:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ m_Floats:
+ - _BumpScale: 1
+ - _Cutoff: 0.5
+ - _DetailNormalMapScale: 1
+ - _Dst: 1
+ - _DstBlend: 0
+ - _GlossMapScale: 1
+ - _Glossiness: 0.5
+ - _GlossyReflections: 1
+ - _Metallic: 0
+ - _Mode: 0
+ - _OcclusionStrength: 1
+ - _Parallax: 0.02
+ - _ScaleX: 1
+ - _ScaleY: 1
+ - _SmoothnessTextureChannel: 0
+ - _SpecularHighlights: 1
+ - _Src: 1
+ - _SrcBlend: 1
+ - _UVSec: 0
+ - _ZWrite: 1
+ m_Colors:
+ - _Color: {r: 1, g: 1, b: 1, a: 1}
+ - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
+ m_BuildTextureStacks: []
diff --git a/Assets/Materials/Build/WandTop.mat.meta b/Assets/Materials/Build/WandTop.mat.meta
new file mode 100644
index 00000000..05bed763
--- /dev/null
+++ b/Assets/Materials/Build/WandTop.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5d46dd5277a083f49992dd646af64e34
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 2100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scenes/Tests/BuildModeTest.unity b/Assets/Scenes/Tests/BuildModeTest.unity
new file mode 100644
index 00000000..1af942d3
--- /dev/null
+++ b/Assets/Scenes/Tests/BuildModeTest.unity
@@ -0,0 +1,2026 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 9
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 0
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641275, b: 0.5748172, a: 1}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 12
+ m_GIWorkflowMode: 1
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 1
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 12
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_ExtractAmbientOcclusion: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_FinalGather: 0
+ m_FinalGatherFiltering: 1
+ m_FinalGatherRayCount: 256
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 1
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 512
+ m_PVRBounces: 2
+ m_PVREnvironmentSampleCount: 256
+ m_PVREnvironmentReferencePointCount: 2048
+ m_PVRFilteringMode: 1
+ m_PVRDenoiserTypeDirect: 1
+ m_PVRDenoiserTypeIndirect: 1
+ m_PVRDenoiserTypeAO: 1
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVREnvironmentMIS: 1
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 5
+ m_PVRFilteringGaussRadiusAO: 2
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ExportTrainingData: 0
+ m_TrainingDataDestination: TrainingData
+ m_LightProbeSampleCountMultiplier: 4
+ m_LightingDataAsset: {fileID: 0}
+ m_LightingSettings: {fileID: 0}
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ maxJobWorkers: 0
+ preserveTilesOutsideBounds: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &89992084
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 89992085}
+ - component: {fileID: 89992086}
+ - component: {fileID: 89992087}
+ m_Layer: 5
+ m_Name: Handle
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &89992085
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 89992084}
+ 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}
+ m_Children: []
+ m_Father: {fileID: 420274207}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 20, y: 20}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!222 &89992086
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 89992084}
+ m_CullTransparentMesh: 1
+--- !u!114 &89992087
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 89992084}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!1 &420274206
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 420274207}
+ m_Layer: 5
+ m_Name: Sliding Area
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &420274207
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 420274206}
+ 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}
+ m_Children:
+ - {fileID: 89992085}
+ m_Father: {fileID: 2062259699}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: -20, y: -20}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!1 &489915452
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 489915453}
+ - component: {fileID: 489915454}
+ m_Layer: 0
+ m_Name: BuildModeTestController
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &489915453
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 489915452}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 35.671333, y: 1.8509755, z: 31.046001}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 1538301813}
+ m_RootOrder: 2
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &489915454
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 489915452}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 94cb5833fa52cfc4392f5daf80bc17b3, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+--- !u!1 &548360276
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 548360277}
+ - component: {fileID: 548360279}
+ - component: {fileID: 548360278}
+ m_Layer: 0
+ m_Name: curs
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &548360277
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 548360276}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 1.51, z: 0}
+ m_LocalScale: {x: 0.15, y: 1.5, z: 0.15}
+ m_Children: []
+ m_Father: {fileID: 1458263242}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!23 &548360278
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 548360276}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RayTraceProcedural: 0
+ m_RenderingLayerMask: 1
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 0
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 1
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+ m_AdditionalVertexStreams: {fileID: 0}
+--- !u!33 &548360279
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 548360276}
+ m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!1 &648965860
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 648965865}
+ - component: {fileID: 648965864}
+ - component: {fileID: 648965863}
+ - component: {fileID: 648965862}
+ - component: {fileID: 648965861}
+ m_Layer: 5
+ m_Name: Cheat Console Canvas
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &648965861
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 648965860}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: c7d73af69c9e8614080ca76faff3ae13, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ ConsoleParent: {fileID: 1951021496}
+ CheatInputField: {fileID: 2114196948}
+ ConsoleOutput: {fileID: 1078632927}
+ ConsoleScroll: {fileID: 1085664058}
+--- !u!114 &648965862
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 648965860}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_IgnoreReversedGraphics: 1
+ m_BlockingObjects: 0
+ m_BlockingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+--- !u!114 &648965863
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 648965860}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_UiScaleMode: 0
+ m_ReferencePixelsPerUnit: 100
+ m_ScaleFactor: 1
+ m_ReferenceResolution: {x: 800, y: 600}
+ m_ScreenMatchMode: 0
+ m_MatchWidthOrHeight: 0
+ m_PhysicalUnit: 3
+ m_FallbackScreenDPI: 96
+ m_DefaultSpriteDPI: 96
+ m_DynamicPixelsPerUnit: 1
+ m_PresetInfoIsWorld: 0
+--- !u!223 &648965864
+Canvas:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 648965860}
+ m_Enabled: 1
+ serializedVersion: 3
+ m_RenderMode: 0
+ m_Camera: {fileID: 0}
+ m_PlaneDistance: 100
+ m_PixelPerfect: 0
+ m_ReceivesEvents: 1
+ m_OverrideSorting: 0
+ m_OverridePixelPerfect: 0
+ m_SortingBucketNormalizedSize: 0
+ m_AdditionalShaderChannelsFlag: 0
+ m_SortingLayerID: 0
+ m_SortingOrder: 100
+ m_TargetDisplay: 0
+--- !u!224 &648965865
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 648965860}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 0, y: 0, z: 0}
+ m_Children:
+ - {fileID: 1951021497}
+ m_Father: {fileID: 0}
+ m_RootOrder: 3
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0, y: 0}
+--- !u!1 &783440545
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 783440546}
+ - component: {fileID: 783440547}
+ m_Layer: 5
+ m_Name: Viewport
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &783440546
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 783440545}
+ 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}
+ m_Children:
+ - {fileID: 1078632924}
+ m_Father: {fileID: 1085664059}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: -17, y: 0}
+ m_Pivot: {x: 0, y: 1}
+--- !u!222 &783440547
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 783440545}
+ m_CullTransparentMesh: 1
+--- !u!1 &1078632923
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1078632924}
+ - component: {fileID: 1078632926}
+ - component: {fileID: 1078632927}
+ - component: {fileID: 1078632925}
+ m_Layer: 5
+ m_Name: Console Text
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1078632924
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1078632923}
+ 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}
+ m_Children: []
+ m_Father: {fileID: 783440546}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0, y: 1}
+--- !u!114 &1078632925
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1078632923}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_HorizontalFit: 0
+ m_VerticalFit: 2
+--- !u!222 &1078632926
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1078632923}
+ m_CullTransparentMesh: 1
+--- !u!114 &1078632927
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1078632923}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_FontData:
+ m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+ m_FontSize: 14
+ m_FontStyle: 0
+ m_BestFit: 0
+ m_MinSize: 10
+ m_MaxSize: 40
+ m_Alignment: 0
+ m_AlignByGeometry: 0
+ m_RichText: 0
+ m_HorizontalOverflow: 0
+ m_VerticalOverflow: 0
+ m_LineSpacing: 1
+ m_Text:
+--- !u!1 &1085664057
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1085664059}
+ - component: {fileID: 1085664060}
+ - component: {fileID: 1085664058}
+ m_Layer: 5
+ m_Name: Scroll View
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &1085664058
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1085664057}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Content: {fileID: 1078632924}
+ m_Horizontal: 0
+ m_Vertical: 1
+ m_MovementType: 2
+ m_Elasticity: 0.1
+ m_Inertia: 0
+ m_DecelerationRate: 0.135
+ m_ScrollSensitivity: 40
+ m_Viewport: {fileID: 783440546}
+ m_HorizontalScrollbar: {fileID: 0}
+ m_VerticalScrollbar: {fileID: 2062259698}
+ m_HorizontalScrollbarVisibility: 2
+ m_VerticalScrollbarVisibility: 1
+ m_HorizontalScrollbarSpacing: -3
+ m_VerticalScrollbarSpacing: -3
+ m_OnValueChanged:
+ m_PersistentCalls:
+ m_Calls: []
+--- !u!224 &1085664059
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1085664057}
+ 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}
+ m_Children:
+ - {fileID: 783440546}
+ m_Father: {fileID: 1326854590}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 1.6949463, y: -105.1}
+ m_SizeDelta: {x: -3.3900743, y: 200}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!222 &1085664060
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1085664057}
+ m_CullTransparentMesh: 1
+--- !u!1 &1134778609
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1134778610}
+ - component: {fileID: 1134778611}
+ m_Layer: 0
+ m_Name: LotViewUIController
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1134778610
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1134778609}
+ 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}
+ m_Children: []
+ m_Father: {fileID: 1538301813}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &1134778611
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1134778609}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 9727801f799a10a41875182ace337639, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+--- !u!1 &1139364661
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1139364662}
+ - component: {fileID: 1139364665}
+ - component: {fileID: 1139364664}
+ m_Layer: 0
+ m_Name: Plane
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1139364662
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1139364661}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 3.232, z: 0}
+ m_LocalScale: {x: 0.75, y: 0.75, z: 0}
+ m_Children: []
+ m_Father: {fileID: 1458263242}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!23 &1139364664
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1139364661}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RayTraceProcedural: 0
+ m_RenderingLayerMask: 1
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 2100000, guid: 5d46dd5277a083f49992dd646af64e34, type: 2}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 0
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 1
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+ m_AdditionalVertexStreams: {fileID: 0}
+--- !u!33 &1139364665
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1139364661}
+ m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!1 &1157865906
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1157865908}
+ - component: {fileID: 1157865907}
+ m_Layer: 0
+ m_Name: Directional Light
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!108 &1157865907
+Light:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1157865906}
+ m_Enabled: 1
+ serializedVersion: 10
+ m_Type: 1
+ m_Shape: 0
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
+ m_Intensity: 1
+ m_Range: 10
+ m_SpotAngle: 30
+ m_InnerSpotAngle: 21.80208
+ m_CookieSize: 10
+ m_Shadows:
+ m_Type: 2
+ m_Resolution: -1
+ m_CustomResolution: -1
+ m_Strength: 1
+ m_Bias: 0.05
+ m_NormalBias: 0.4
+ m_NearPlane: 0.2
+ m_CullingMatrixOverride:
+ e00: 1
+ e01: 0
+ e02: 0
+ e03: 0
+ e10: 0
+ e11: 1
+ e12: 0
+ e13: 0
+ e20: 0
+ e21: 0
+ e22: 1
+ e23: 0
+ e30: 0
+ e31: 0
+ e32: 0
+ e33: 1
+ m_UseCullingMatrixOverride: 0
+ m_Cookie: {fileID: 0}
+ m_DrawHalo: 0
+ m_Flare: {fileID: 0}
+ m_RenderMode: 0
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingLayerMask: 1
+ m_Lightmapping: 4
+ m_LightShadowCasterMode: 0
+ m_AreaSize: {x: 1, y: 1}
+ m_BounceIntensity: 1
+ m_ColorTemperature: 6570
+ m_UseColorTemperature: 0
+ m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_UseBoundingSphereOverride: 0
+ m_UseViewFrustumForShadowCasterCull: 1
+ m_ShadowRadius: 0
+ m_ShadowAngle: 0
+--- !u!4 &1157865908
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1157865906}
+ m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
+ m_LocalPosition: {x: 0, y: 3, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
+--- !u!1 &1196709330
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1196709331}
+ - component: {fileID: 1196709333}
+ - component: {fileID: 1196709332}
+ m_Layer: 5
+ m_Name: Placeholder
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1196709331
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1196709330}
+ 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}
+ m_Children: []
+ m_Father: {fileID: 2114196945}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: -0.5}
+ m_SizeDelta: {x: -20, y: -13}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1196709332
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1196709330}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_FontData:
+ m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+ m_FontSize: 14
+ m_FontStyle: 2
+ m_BestFit: 0
+ m_MinSize: 10
+ m_MaxSize: 40
+ m_Alignment: 0
+ m_AlignByGeometry: 0
+ m_RichText: 1
+ m_HorizontalOverflow: 0
+ m_VerticalOverflow: 0
+ m_LineSpacing: 1
+ m_Text:
+--- !u!222 &1196709333
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1196709330}
+ m_CullTransparentMesh: 1
+--- !u!1 &1267610420
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1267610421}
+ - component: {fileID: 1267610423}
+ - component: {fileID: 1267610422}
+ m_Layer: 5
+ m_Name: Text
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1267610421
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1267610420}
+ 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}
+ m_Children: []
+ m_Father: {fileID: 2114196945}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: -0.5}
+ m_SizeDelta: {x: -20, y: -13}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1267610422
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1267610420}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_FontData:
+ m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+ m_FontSize: 14
+ m_FontStyle: 0
+ m_BestFit: 0
+ m_MinSize: 10
+ m_MaxSize: 40
+ m_Alignment: 0
+ m_AlignByGeometry: 0
+ m_RichText: 0
+ m_HorizontalOverflow: 1
+ m_VerticalOverflow: 0
+ m_LineSpacing: 1
+ m_Text:
+--- !u!222 &1267610423
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1267610420}
+ m_CullTransparentMesh: 1
+--- !u!1 &1326854589
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1326854590}
+ - component: {fileID: 1326854593}
+ - component: {fileID: 1326854592}
+ - component: {fileID: 1326854591}
+ m_Layer: 5
+ m_Name: ConsolePanel
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1326854590
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1326854589}
+ 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}
+ m_Children:
+ - {fileID: 1085664059}
+ - {fileID: 2062259699}
+ m_Father: {fileID: 1951021497}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: -99.20001}
+ m_SizeDelta: {x: 0, y: 198.4}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1326854591
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1326854589}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_ShowMaskGraphic: 1
+--- !u!114 &1326854592
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1326854589}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &1326854593
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1326854589}
+ m_CullTransparentMesh: 1
+--- !u!1 &1428817267
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1428817268}
+ - component: {fileID: 1428817270}
+ - component: {fileID: 1428817269}
+ m_Layer: 0
+ m_Name: curs
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1428817268
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1428817267}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 1.5, z: 0}
+ m_LocalScale: {x: 0.1, y: 3, z: 0.1}
+ m_Children: []
+ m_Father: {fileID: 1637446162}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!23 &1428817269
+MeshRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1428817267}
+ m_Enabled: 1
+ m_CastShadows: 1
+ m_ReceiveShadows: 1
+ m_DynamicOccludee: 1
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 2
+ m_RayTraceProcedural: 0
+ m_RenderingLayerMask: 1
+ m_RendererPriority: 0
+ m_Materials:
+ - {fileID: 2100000, guid: 9e00eb9bc0cd7be478e657c251ce0680, type: 2}
+ m_StaticBatchInfo:
+ firstSubMesh: 0
+ subMeshCount: 0
+ m_StaticBatchRoot: {fileID: 0}
+ m_ProbeAnchor: {fileID: 0}
+ m_LightProbeVolumeOverride: {fileID: 0}
+ m_ScaleInLightmap: 1
+ m_ReceiveGI: 1
+ m_PreserveUVs: 0
+ m_IgnoreNormalsForChartDetection: 0
+ m_ImportantGI: 0
+ m_StitchLightmapSeams: 1
+ m_SelectedEditorRenderState: 3
+ m_MinimumChartSize: 4
+ m_AutoUVMaxDistance: 0.5
+ m_AutoUVMaxAngle: 89
+ m_LightmapParameters: {fileID: 0}
+ m_SortingLayerID: 0
+ m_SortingLayer: 0
+ m_SortingOrder: 0
+ m_AdditionalVertexStreams: {fileID: 0}
+--- !u!33 &1428817270
+MeshFilter:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1428817267}
+ m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
+--- !u!1 &1458263241
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1458263242}
+ m_Layer: 0
+ m_Name: Wand
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1458263242
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1458263241}
+ 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}
+ m_Children:
+ - {fileID: 548360277}
+ - {fileID: 1139364662}
+ m_Father: {fileID: 0}
+ m_RootOrder: 6
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1538301812
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1538301813}
+ m_Layer: 0
+ m_Name: Scene
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1538301813
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1538301812}
+ 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}
+ m_Children:
+ - {fileID: 2076958800}
+ - {fileID: 1134778610}
+ - {fileID: 489915453}
+ m_Father: {fileID: 0}
+ m_RootOrder: 5
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1592748653
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1592748656}
+ - component: {fileID: 1592748655}
+ - component: {fileID: 1592748654}
+ m_Layer: 0
+ m_Name: EventSystem
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &1592748654
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1592748653}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_HorizontalAxis: Horizontal
+ m_VerticalAxis: Vertical
+ m_SubmitButton: Submit
+ m_CancelButton: Cancel
+ m_InputActionsPerSecond: 10
+ m_RepeatDelay: 0.5
+ m_ForceModuleActive: 0
+--- !u!114 &1592748655
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1592748653}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_FirstSelected: {fileID: 0}
+ m_sendNavigationEvents: 1
+ m_DragThreshold: 10
+--- !u!4 &1592748656
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1592748653}
+ 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}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 4
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1637446161
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1637446162}
+ m_Layer: 0
+ m_Name: debugWandCompanion
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1637446162
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1637446161}
+ 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}
+ m_Children:
+ - {fileID: 1428817268}
+ m_Father: {fileID: 0}
+ m_RootOrder: 7
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1883555802
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1883555808}
+ - component: {fileID: 1883555804}
+ - component: {fileID: 1883555803}
+ - component: {fileID: 1883555806}
+ - component: {fileID: 1883555807}
+ - component: {fileID: 1883555805}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &1883555803
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1883555802}
+ m_Enabled: 1
+--- !u!20 &1883555804
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1883555802}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_GateFitMode: 2
+ m_FOVAxisMode: 0
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_FocalLength: 50
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 0
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!114 &1883555805
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1883555802}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_IgnoreReversedGraphics: 1
+ m_BlockingObjects: 0
+ m_BlockingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+--- !u!114 &1883555806
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1883555802}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 8021982501d05f645a42b19f4a41ab8a, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+--- !u!223 &1883555807
+Canvas:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1883555802}
+ m_Enabled: 1
+ serializedVersion: 3
+ m_RenderMode: 2
+ m_Camera: {fileID: 0}
+ m_PlaneDistance: 100
+ m_PixelPerfect: 0
+ m_ReceivesEvents: 1
+ m_OverrideSorting: 0
+ m_OverridePixelPerfect: 0
+ m_SortingBucketNormalizedSize: 0
+ m_AdditionalShaderChannelsFlag: 0
+ m_SortingLayerID: 0
+ m_SortingOrder: 0
+ m_TargetDisplay: 0
+--- !u!224 &1883555808
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1883555802}
+ m_LocalRotation: {x: 0.35355338, y: 0.35355338, z: -0.1464466, w: 0.8535535}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 45, y: 45, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: 0, y: 8}
+ m_SizeDelta: {x: 100, y: 100}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!1 &1951021496
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1951021497}
+ m_Layer: 5
+ m_Name: Cheat Console
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1951021497
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1951021496}
+ 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}
+ m_Children:
+ - {fileID: 2114196945}
+ - {fileID: 1326854590}
+ m_Father: {fileID: 648965865}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!1 &2000707108
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2000707112}
+ - component: {fileID: 2000707111}
+ - component: {fileID: 2000707110}
+ - component: {fileID: 2000707109}
+ - component: {fileID: 2000707113}
+ m_Layer: 5
+ m_Name: Canvas
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &2000707109
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2000707108}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_IgnoreReversedGraphics: 1
+ m_BlockingObjects: 0
+ m_BlockingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+--- !u!114 &2000707110
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2000707108}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_UiScaleMode: 0
+ m_ReferencePixelsPerUnit: 100
+ m_ScaleFactor: 1
+ m_ReferenceResolution: {x: 800, y: 600}
+ m_ScreenMatchMode: 0
+ m_MatchWidthOrHeight: 0
+ m_PhysicalUnit: 3
+ m_FallbackScreenDPI: 96
+ m_DefaultSpriteDPI: 96
+ m_DynamicPixelsPerUnit: 1
+ m_PresetInfoIsWorld: 0
+--- !u!223 &2000707111
+Canvas:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2000707108}
+ m_Enabled: 1
+ serializedVersion: 3
+ m_RenderMode: 0
+ m_Camera: {fileID: 0}
+ m_PlaneDistance: 100
+ m_PixelPerfect: 1
+ m_ReceivesEvents: 1
+ m_OverrideSorting: 0
+ m_OverridePixelPerfect: 0
+ m_SortingBucketNormalizedSize: 0
+ m_AdditionalShaderChannelsFlag: 0
+ m_SortingLayerID: 0
+ m_SortingOrder: 0
+ m_TargetDisplay: 0
+--- !u!224 &2000707112
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2000707108}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 0, y: 0, z: 0}
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_RootOrder: 2
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 0, y: 0}
+ m_Pivot: {x: 0, y: 0}
+--- !u!114 &2000707113
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2000707108}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: db3b6e6445c9df84bb2e95b9365382be, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+--- !u!1 &2062259697
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2062259699}
+ - component: {fileID: 2062259701}
+ - component: {fileID: 2062259700}
+ - component: {fileID: 2062259698}
+ m_Layer: 5
+ m_Name: Scrollbar Vertical
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!114 &2062259698
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2062259697}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Navigation:
+ m_Mode: 3
+ m_WrapAround: 0
+ m_SelectOnUp: {fileID: 0}
+ m_SelectOnDown: {fileID: 0}
+ m_SelectOnLeft: {fileID: 0}
+ m_SelectOnRight: {fileID: 0}
+ m_Transition: 1
+ m_Colors:
+ m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+ m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+ m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+ m_ColorMultiplier: 1
+ m_FadeDuration: 0.1
+ m_SpriteState:
+ m_HighlightedSprite: {fileID: 0}
+ m_PressedSprite: {fileID: 0}
+ m_SelectedSprite: {fileID: 0}
+ m_DisabledSprite: {fileID: 0}
+ m_AnimationTriggers:
+ m_NormalTrigger: Normal
+ m_HighlightedTrigger: Highlighted
+ m_PressedTrigger: Pressed
+ m_SelectedTrigger: Selected
+ m_DisabledTrigger: Disabled
+ m_Interactable: 1
+ m_TargetGraphic: {fileID: 89992087}
+ m_HandleRect: {fileID: 89992085}
+ m_Direction: 2
+ m_Value: 0
+ m_Size: 1
+ m_NumberOfSteps: 0
+ m_OnValueChanged:
+ m_PersistentCalls:
+ m_Calls: []
+--- !u!224 &2062259699
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2062259697}
+ 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}
+ m_Children:
+ - {fileID: 420274207}
+ m_Father: {fileID: 1326854590}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 1, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: 0.000015258789}
+ m_SizeDelta: {x: 20, y: 0}
+ m_Pivot: {x: 1, y: 1}
+--- !u!114 &2062259700
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2062259697}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &2062259701
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2062259697}
+ m_CullTransparentMesh: 1
+--- !u!1001 &2076958799
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ m_TransformParent: {fileID: 1538301813}
+ m_Modifications:
+ - target: {fileID: 2467515431058217605, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_Name
+ value: Scene Startup
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_RootOrder
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: -0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: -0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: -0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 7238001765745231388, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ propertyPath: TargetScene
+ value:
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+--- !u!4 &2076958800 stripped
+Transform:
+ m_CorrespondingSourceObject: {fileID: 2467515431058217606, guid: 188047b711783b74aa73c17ff9d66147, type: 3}
+ m_PrefabInstance: {fileID: 2076958799}
+ m_PrefabAsset: {fileID: 0}
+--- !u!1 &2114196944
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2114196945}
+ - component: {fileID: 2114196947}
+ - component: {fileID: 2114196946}
+ - component: {fileID: 2114196948}
+ m_Layer: 5
+ m_Name: InputField
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &2114196945
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2114196944}
+ 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}
+ m_Children:
+ - {fileID: 1196709331}
+ - {fileID: 1267610421}
+ m_Father: {fileID: 1951021497}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 0, y: -213.4}
+ m_SizeDelta: {x: 0, y: 30}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &2114196946
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2114196944}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10911, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &2114196947
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2114196944}
+ m_CullTransparentMesh: 1
+--- !u!114 &2114196948
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 2114196944}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d199490a83bb2b844b9695cbf13b01ef, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Navigation:
+ m_Mode: 3
+ m_WrapAround: 0
+ m_SelectOnUp: {fileID: 0}
+ m_SelectOnDown: {fileID: 0}
+ m_SelectOnLeft: {fileID: 0}
+ m_SelectOnRight: {fileID: 0}
+ m_Transition: 1
+ m_Colors:
+ m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+ m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+ m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+ m_ColorMultiplier: 1
+ m_FadeDuration: 0.1
+ m_SpriteState:
+ m_HighlightedSprite: {fileID: 0}
+ m_PressedSprite: {fileID: 0}
+ m_SelectedSprite: {fileID: 0}
+ m_DisabledSprite: {fileID: 0}
+ m_AnimationTriggers:
+ m_NormalTrigger: Normal
+ m_HighlightedTrigger: Highlighted
+ m_PressedTrigger: Pressed
+ m_SelectedTrigger: Selected
+ m_DisabledTrigger: Disabled
+ m_Interactable: 1
+ m_TargetGraphic: {fileID: 2114196946}
+ m_TextComponent: {fileID: 1267610422}
+ m_Placeholder: {fileID: 1196709332}
+ m_ContentType: 0
+ m_InputType: 0
+ m_AsteriskChar: 42
+ m_KeyboardType: 0
+ m_LineType: 0
+ m_HideMobileInput: 0
+ m_CharacterValidation: 0
+ m_CharacterLimit: 0
+ m_OnEndEdit:
+ m_PersistentCalls:
+ m_Calls: []
+ m_OnValueChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_CustomCaretColor: 0
+ m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412}
+ m_Text:
+ m_CaretBlinkRate: 0.85
+ m_CaretWidth: 1
+ m_ReadOnly: 0
+ m_ShouldActivateOnSelect: 1
diff --git a/Assets/Scenes/Tests/BuildModeTest.unity.meta b/Assets/Scenes/Tests/BuildModeTest.unity.meta
new file mode 100644
index 00000000..83aed113
--- /dev/null
+++ b/Assets/Scenes/Tests/BuildModeTest.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 74352a833a6dc254ab203706be9743b9
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Cameras.meta b/Assets/Scripts/OpenTS2/Cameras.meta
new file mode 100644
index 00000000..c8ef2231
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Cameras.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 47b4f5547f913ba4882d83a6ffa154e4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Cameras/TestLotViewCamera.cs b/Assets/Scripts/OpenTS2/Cameras/TestLotViewCamera.cs
new file mode 100644
index 00000000..78f04bc5
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Cameras/TestLotViewCamera.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+public class TestLotViewCamera : MonoBehaviour
+{
+ private const int HitscanLength = 2000;
+
+ private Camera _camera;
+ private Transform _camTransform;
+ private Vector3 _mouseHoldPosition;
+ private float _initialXCamRot;
+
+ //level detection system
+ ///
+ /// A set of mesh colliders that the camera will shoot rays at to detect where the mouse cursor is in 3D world space
+ /// and also where the floor is to adjust its Y position to see the floor appropriately
+ /// See:
+ ///
+ Dictionary _floorElevationMeshes;
+
+ // Start is called before the first frame update
+ private void Start()
+ {
+ _floorElevationMeshes = new Dictionary();
+ _camTransform = GetComponentInParent();
+ _camera = GetComponentInParent();
+ }
+
+ // Update is called once per frame
+ private void Update()
+ {
+ if (Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2)) // right or middle click
+ {
+ _mouseHoldPosition = Input.mousePosition;
+ if (Input.GetMouseButtonDown(2))
+ _initialXCamRot = _camTransform.rotation.eulerAngles.x;
+ }
+
+ var mouseChange = _mouseHoldPosition - Input.mousePosition;
+
+ if (Input.GetMouseButton(1)) // Right Click camera translation
+ {
+ var change = mouseChange / 10;
+ _camTransform.TransformDirection(change);
+ change *= Time.deltaTime;
+
+ _camTransform.position =
+ new Vector3(_camTransform.position.x - change.x,
+ _camTransform.position.y,
+ _camTransform.position.z - change.y);
+ }
+ else if (Input.GetMouseButton(2)) // Middle Mouse Click Camera pitch
+ {
+ float pitchChange = (mouseChange / 1).y;
+ var transformedXRot = Math.Max(0, Math.Min(90, _initialXCamRot - pitchChange));
+ var currentAngle = _camTransform.rotation.eulerAngles;
+ currentAngle = new Vector3(transformedXRot, currentAngle.y, currentAngle.z);
+ _camTransform.rotation = Quaternion.Euler(currentAngle);
+ }
+ }
+
+ ///
+ /// Sets the mesh colliders used to detect the height of the floor the camera is looking at.
+ ///
+ ///
+ ///
+ public void SetCameraDetectionMeshes(int floor, IEnumerable colliders)
+ {
+ _floorElevationMeshes.Remove(floor);
+ _floorElevationMeshes.Add(floor, colliders.Where(x => x != default).ToArray());
+ }
+
+ public static Ray GetMouseCursor3DRay(Camera selectedCamera)
+ {
+ //transform scr pos to world
+ Vector3 mousePosition = Input.mousePosition;
+ mousePosition.z = 20;
+ mousePosition = Camera.main.ScreenToWorldPoint(mousePosition);
+
+ //get directional vector
+ var direction = (mousePosition - selectedCamera.transform.position); direction.Normalize();
+
+ //build a ray from camera to terrain
+ return new Ray(selectedCamera.transform.position, direction);
+ }
+
+ ///
+ /// Potentially expensive!! use with caution
+ ///
+ ///
+ ///
+ ///
+ public bool TranslateScreen2WorldPos(int maxFloor, out Vector3 worldPosition, out int floor)
+ {
+ worldPosition = new Vector3();
+ floor = -1;
+
+ if (!_floorElevationMeshes.Any())
+ {
+ Debug.LogWarning("No terrain meshes added to the lot camera so hit detection is disabled!");
+ return false;
+ }
+
+ Ray camRay = GetMouseCursor3DRay(_camera);
+
+ //Check each floor by casting the above ray at each collider.
+ //TODO: (NEEDS REFACTOR TO USE ELEVATION IN LOT DATA)
+ //also just in general this iteration is really not great should only be used on frames where theres actual movement
+ RaycastHit hitInfo = default;
+ bool hitComplete = false;
+
+ //go top down searching for which floor we're looking at
+ for(int candidateFloor = maxFloor; candidateFloor >= _floorElevationMeshes.Keys.Min(); candidateFloor--)
+ {
+ if (!_floorElevationMeshes.ContainsKey(candidateFloor)) continue;
+ foreach(var collider in _floorElevationMeshes[candidateFloor])
+ {
+ if (collider is MeshCollider mCollider)
+ {
+ var meshRef = collider.gameObject.GetComponent().sharedMesh;
+ mCollider.sharedMesh = meshRef;
+ }
+ if (collider.Raycast(camRay, out hitInfo, HitscanLength))
+ {
+ hitComplete = true;
+ floor = candidateFloor;
+ break;
+ }
+ }
+ if (hitComplete) break;
+ }
+ if (!hitComplete) return false;
+
+ worldPosition = hitInfo.point;
+ return true;
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Cameras/TestLotViewCamera.cs.meta b/Assets/Scripts/OpenTS2/Cameras/TestLotViewCamera.cs.meta
new file mode 100644
index 00000000..2d9470ac
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Cameras/TestLotViewCamera.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8021982501d05f645a42b19f4a41ab8a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Components/AssetReferenceComponent.cs b/Assets/Scripts/OpenTS2/Components/AssetReferenceComponent.cs
index 2427d0eb..acdfca05 100644
--- a/Assets/Scripts/OpenTS2/Components/AssetReferenceComponent.cs
+++ b/Assets/Scripts/OpenTS2/Components/AssetReferenceComponent.cs
@@ -16,5 +16,10 @@ public void AddReference(params AbstractAsset[] assets)
{
References.AddRange(assets);
}
+
+ ///
+ /// Indicates that this component should re-evaluate itself for changes made at runtime
+ ///
+ public virtual void Invalidate() { }
}
}
diff --git a/Assets/Scripts/OpenTS2/Content/DBPF/WallGraphAsset.cs b/Assets/Scripts/OpenTS2/Content/DBPF/WallGraphAsset.cs
index f3720d79..7372e10b 100644
--- a/Assets/Scripts/OpenTS2/Content/DBPF/WallGraphAsset.cs
+++ b/Assets/Scripts/OpenTS2/Content/DBPF/WallGraphAsset.cs
@@ -1,4 +1,7 @@
+using System;
using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
namespace OpenTS2.Content.DBPF
{
@@ -31,11 +34,12 @@ public class WallGraphAsset : AbstractAsset
{
public int Width { get; }
public int Height { get; }
- public int Floors { get; }
+ public int Floors { get; private set; }
public int BaseFloor { get; }
public Dictionary Positions { get; }
public int[] Rooms { get; }
- public WallGraphLineEntry[] Lines { get; }
+ public WallGraphLineEntry[] Lines { get => _lines; set => _lines = value; }
+ private WallGraphLineEntry[] _lines;
public WallGraphAsset(int width, int height, int floors, int baseFloor, WallGraphPositionEntry[] pos, int[] rooms, WallGraphLineEntry[] lines)
{
@@ -59,6 +63,129 @@ private Dictionary ToPositionDictionary(WallGraphPo
return result;
}
- }
-}
\ No newline at end of file
+ bool GetJuncIdByPosition(Vector2 position, int floor, out int juncId, bool createIfNull = true)
+ {
+ bool existed = false;
+ var junctions = Positions.Where(x =>
+ x.Value.XPos == position.x && x.Value.YPos == position.y && x.Value.Level == floor);
+ juncId = -1;
+ if (junctions.Any())
+ {
+ juncId = junctions.First().Key; // found a matching position
+ existed = true;
+ }
+ else if (createIfNull)
+ {
+ // make a new position
+ if (Positions.Any())
+ juncId = Positions.Select(x => x.Key).Max() + 1;
+ else juncId = 0;
+ Positions.Add(juncId, new WallGraphPositionEntry()
+ {
+ Id = juncId,
+ Level = floor,
+ XPos = position.x,
+ YPos = position.y,
+ });
+ }
+
+ return existed;
+ }
+
+ public bool PushWall(Vector2 pos1, Vector2 pos2, int floor, out int layerID)
+ {
+ layerID = -1;
+
+ bool fromExistedAlready = GetJuncIdByPosition(pos1, floor, out int fromId);
+ if (fromId == -1) // unable to create !!
+ return false;
+ bool toExistedAlready = GetJuncIdByPosition(pos2, floor, out int toId);
+ if (toId == -1) // unable to create !!
+ return false;
+
+ if (fromExistedAlready && toExistedAlready)
+ { // wall may already exist
+ bool exists = _lines.Any(x => x.ToId == toId && x.FromId == fromId); // check if this wall segment exists
+ if (exists)
+ return false; // this wall segment already exists.
+ exists = _lines.Any(x => x.ToId == fromId && x.FromId == toId);
+ if (exists)
+ return false; // this wall segment already exists.
+ }
+
+ int layerId = 99;
+ if(_lines.Any()) layerId = _lines.Select(x => x.LayerId).Max() + 1;
+
+ Array.Resize(ref _lines, Lines.Length + 1);
+ WallGraphLineEntry line = new WallGraphLineEntry()
+ {
+ FromId = fromId,
+ LayerId = layerId,
+ ToId = toId,
+ };
+ _lines[_lines.Length - 1] = line;
+ layerID = layerId;
+ if (Floors <= floor)
+ Floors++;
+ return true;
+ }
+
+ private void _delWall(int index)
+ {
+ int i = index;
+ //found the wall
+ if (i != _lines.Length - 1) // last wall
+ _lines[i] = _lines[_lines.Length - 1]; // move last line to this spot
+ Array.Resize(ref _lines, _lines.Length - 1); // shorten array by one
+ }
+
+ internal bool RemoveWall(Vector2 from, Vector2 to, int floor, out int layerID)
+ { // should be refactored to batch delete a set of segments instead of one at a time due to iterations being potentially large
+ layerID = -1;
+
+ GetJuncIdByPosition(from, floor, out int fromId, false);
+ if (fromId == -1) // wall index doesn't exist?
+ return false;
+ GetJuncIdByPosition(to, floor, out int toId, false);
+ if (toId == -1) // wall index doesn't exist?
+ return false;
+
+ for (int i = _lines.Length - 1; i >= 0; i--) // reverse order -- since players may more often delete walls they just made
+ {
+ var line = _lines[i];
+
+ if (toId == fromId) throw new Exception("From and To IDs are the same!");
+ if (line.ToId != toId && line.ToId != fromId) continue;
+ if (line.FromId != toId && line.FromId != fromId) continue;
+
+ _delWall(i);
+ layerID = line.LayerId;
+ return true;
+ }
+ return false; // not found
+ }
+ ///
+ /// Deletes all the wall lines from the selected floor by their LayerID.
+ ///
+ ///
+ ///
+ internal int RemoveWalls(params int[] wallLayerIDs)
+ {
+ int index = -1;
+ int found = 0;
+ var deletionIndices = new HashSet();
+ foreach(var line in _lines)
+ {
+ index++;
+ if (!wallLayerIDs.Contains(line.LayerId)) continue;
+ found++;
+ deletionIndices.Add(index);
+ }
+ int deleted = 0;
+ foreach (int i in deletionIndices)
+ _delWall(i - deleted++);
+ return deleted;
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes.meta b/Assets/Scripts/OpenTS2/Engine/Modes.meta
new file mode 100644
index 00000000..819c8022
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1df3d8a68fd8f874eb366660526b7c53
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build.meta
new file mode 100644
index 00000000..378aafd1
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 28f9123fa5bd6d04ea188898ff9c5c80
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildModeServer.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildModeServer.cs
new file mode 100644
index 00000000..d5c582ad
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildModeServer.cs
@@ -0,0 +1,665 @@
+using OpenTS2.Content.DBPF;
+using OpenTS2.Engine.Modes.Build.Tools;
+using OpenTS2.Scenes.Lot;
+using UnityEngine;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using OpenTS2.Scenes.Lot.Extensions;
+using OpenTS2.Scenes.Lot.State;
+using log4net.Core;
+using UnityEngine.UIElements;
+using OpenTS2.Files.Formats.DBPF;
+using OpenTS2.Diagnostic;
+
+namespace OpenTS2.Engine.Modes.Build
+{
+
+ ///
+ /// Controls the flow of changes to the lot architecture and serves the functionality for Build Mode
+ ///
+ internal class BuildModeServer : MonoBehaviour
+ {
+ ///
+ /// Modes that walls can be created
+ ///
+ public enum WallCreationModes
+ {
+ ///
+ /// A single normal wall
+ ///
+ Single,
+ ///
+ /// A square area of walls
+ ///
+ Room,
+ ///
+ /// A diagonally oriented square area of walls
+ ///
+ DiagnonalRoom
+ }
+ ///
+ /// Modes that describe the modification being made to the terrain
+ ///
+ public enum TerrainModificationModes
+ {
+ ///
+ /// No brush selected
+ ///
+ None,
+ ///
+ /// Raise the terrain brush
+ ///
+ Raise,
+ ///
+ /// Lower the terrain brush
+ ///
+ Lower,
+ ///
+ /// Smooth an area of terrain brush
+ ///
+ Smooth,
+ ///
+ /// Pond brush
+ ///
+ Water,
+ ///
+ /// Terrain paints brush
+ ///
+ Paint
+ }
+
+ //STATIC
+ private static BuildModeServer Current { get; set; }
+ public static BuildModeServer Get() => Current;
+
+ private static Dictionary _tools;
+ private ushort dryWallDesignPattern;
+ ///
+ /// The floor currently selected using the UI
+ /// This also is the highest level shown in the lot geometry
+ ///
+ public int CurrentFloor => loadedLot.Floor;
+
+ //PRIVATE
+ private readonly StringBuilder lotHistory = new StringBuilder();
+
+ private LotLoad loadedLot;
+ private LotArchitecture architecture => loadedLot.Architecture;
+ [GameProperty("constrainfloorelevation", userProp:true)]
+ private static bool ConstrainedFloorElevation = false;
+
+ //PUBLIC
+ ///
+ /// The current
+ /// See:
+ ///
+ public WorldState WorldState
+ {
+ get => loadedLot.WorldState; set => loadedLot.WorldState = value;
+ }
+ ///
+ /// After making changes to the , use this to apply the changes
+ ///
+ public void InvalidateLotState() => loadedLot.InvalidateState();
+ ///
+ /// The tool that the user is currently using (or the one the user more recently selected)
+ ///
+ public BuildTools SelectedTool { get; private set;} = BuildTools.None;
+ ///
+ /// The handler for the tool the user is currently using
+ ///
+ public AbstractBuildTool CurrentTool { get; private set; } = null;
+ public int BaseFloor => loadedLot.BaseFloor;
+ public int MaxFloor => loadedLot.MaxFloor;
+
+ private void LogHistory(string Message, string Caption = default, string Sender = default)
+ {
+ if (string.IsNullOrWhiteSpace(Sender)) Sender = typeof(BuildModeServer).Name;
+ var msgTxt = $"OpenTS2::{Sender} -> {(!string.IsNullOrWhiteSpace(Caption) ? $"({Caption})" : "")} {Message}";
+ lotHistory.AppendLine(msgTxt);
+ Debug.Log(msgTxt);
+ }
+
+ public void Initialize(LotLoad LoadedLot)
+ {
+ Current = this;
+
+ loadedLot = LoadedLot;
+
+ SelectedTool = BuildTools.None;
+ CurrentTool = null;
+
+ MakeTools();
+ Init();
+ }
+
+ void MakeTools()
+ {
+ _tools = new Dictionary
+ { // funfact: these are added in the order they were implemented .. incase you were wondering
+ { BuildTools.Wall, new WallTool(this) },
+ { BuildTools.Hand, new HandTool(loadedLot, architecture) },
+ { BuildTools.TerrainBrush, new TerrainBrushTool(this) },
+ { BuildTools.Floor, new FloorTool(this) },
+ { BuildTools.Foundation, new FoundationTool(this) }
+ };
+ }
+
+ void Init()
+ {
+ //set drywall design pattern (used so frequently we can save a little time frontloading it)
+ architecture.EnsurePatternReferenced("blank", LotArchitecture.ArchitectureGameObjectTypes.wall, out dryWallDesignPattern, out _);
+ }
+ ///
+ /// Changes the currently used tool by the User.
+ /// Passing will drop the current tool, if held.
+ ///
+ ///
+ public void ChangeTool(BuildTools Tool)
+ {
+ if (CurrentTool != null)
+ { // A tool is currently being used.
+ if (Tool == SelectedTool)
+ return; // the newly selected tool is the same as the one currently held, exit.
+ CurrentTool.OnToolCancel(Tool != BuildTools.None ? "The user selected a different tool." : "The current tool has been dropped.");
+ CurrentTool.SetActive(false);
+ }
+ switch(Tool)
+ {
+ default:
+ case BuildTools.None:
+ CurrentTool = null; break;
+ case BuildTools.Foundation:
+ case BuildTools.Hand:
+ case BuildTools.Wall:
+ case BuildTools.TerrainBrush:
+ case BuildTools.Floor:
+ CurrentTool = _tools[Tool];
+ CurrentTool.SetActive(true);
+ LogHistory($"Selected tool: {Tool} (trans from: {SelectedTool})", "Change Tool");
+ break;
+ }
+ SelectedTool = Tool;
+ }
+
+ ///
+ /// Lists all the points for wall segments in the area of walls provided
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static List<(Vector2Int A, Vector2Int B)> GetWallPoints(Vector2Int From, Vector2Int To, WallCreationModes CreationOption = WallCreationModes.Single)
+ {
+ List<(Vector2Int A, Vector2Int B)> walls = new List<(Vector2Int, Vector2Int)>();
+ switch (CreationOption)
+ {
+ case WallCreationModes.Single:
+ walls.Add((From, To));
+ break;
+ case WallCreationModes.Room:
+ walls.Add((From, new Vector2Int(To.x, From.y)));
+ walls.Add((new Vector2Int(To.x, From.y), To));
+ walls.Add((To, new Vector2Int(From.x, To.y)));
+ walls.Add((new Vector2Int(From.x, To.y), From));
+ break;
+ case WallCreationModes.DiagnonalRoom: break;
+ }
+ return walls;
+ }
+
+ public float PollElevation(Vector2Int Position, int Floor) => architecture.PollElevation(Position, Floor);
+
+ ///
+ /// Creates walls with the blank wallpaper set with optional creation options like Square area of walls or Single
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int[]? CreateWalls(Vector2Int From, Vector2Int To, int Floor,
+ WallType Type = WallType.Normal,
+ WallCreationModes CreationOption = WallCreationModes.Single,
+ string FrontWallpaperPattern = "blank", string BackWallpaperPattern = "blank")
+ {
+ if (From == To) return null;
+ if (CreationOption != WallCreationModes.Single && !RegionValid(From, To)) return null;
+ string argsStr = $"From: {From} To: {To} Floor: {Floor} Type: {Type} Mode: {CreationOption}" +
+ $"Front: {FrontWallpaperPattern} Back: {BackWallpaperPattern}";
+
+ //OrderPoints(ref From, ref To);
+ var walls = GetWallPoints(From, To, CreationOption);
+
+ //WALLPAPERS
+ ushort front = dryWallDesignPattern;
+ ushort back = front;
+ if (FrontWallpaperPattern != "blank")
+ architecture.EnsurePatternReferenced(FrontWallpaperPattern, LotArchitecture.ArchitectureGameObjectTypes.wall, out front, out _);
+ if (BackWallpaperPattern != "blank")
+ architecture.EnsurePatternReferenced(BackWallpaperPattern, LotArchitecture.ArchitectureGameObjectTypes.wall, out back, out _);
+
+ int[] createdWallIDs = null;
+ foreach (var wall in walls)
+ createdWallIDs = architecture.CreateWall(wall.A, wall.B, Floor, Type, front, back);
+
+ if (createdWallIDs != null) // walls were placed here
+ {
+ loadedLot.InvalidateWalls();
+ LogHistory($"Created wall(s). {argsStr}", "Create Walls");
+ return createdWallIDs;
+ }
+ else LogHistory($"Could not create wall(s). {argsStr}", "Create Walls");
+ return null;
+ }
+ ///
+ /// Causes the room detection system to re-evaluate the lot.
+ ///
+ public void SignalRoomsInvalidate() => loadedLot.EvaluateRoomMap();
+ ///
+ /// Clears out all walls in the specified region with the given wall creation options
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void DeleteWalls(Vector2Int From, Vector2Int To, int Floor, WallCreationModes CreationOption = WallCreationModes.Single)
+ {
+ var walls = GetWallPoints(From, To, CreationOption);
+ string argsStr = $"From: {From} To: {To} Floor: {Floor} Mode: {CreationOption}";
+
+ bool? returnValue = true;
+ foreach (var wall in walls)
+ returnValue = architecture.DeleteWall(wall.A, wall.B, Floor);
+
+ if (returnValue.HasValue) // walls were placed here
+ {
+ loadedLot.InvalidateWalls();
+ LogHistory($"Deleted wall(s). {argsStr}", "Delete Walls");
+ }
+ else LogHistory($"Could not delete wall(s). {argsStr}", "Delete Walls");
+ }
+ public void DeleteAllWalls(params int[] WallLayerIDs)
+ {
+ if (WallLayerIDs.Length == 0) return;
+ architecture.DeleteAllWalls(WallLayerIDs);
+ loadedLot.InvalidateWalls();
+ }
+
+ ///
+ /// Puts the two points in ascending order
+ ///
+ void OrderPoints(ref Vector2Int A, ref Vector2Int B)
+ {
+ var a = new Vector2Int(Math.Min(A.x, B.x), Math.Min(A.y, B.y));
+ var b = new Vector2Int(Math.Max(A.x, B.x), Math.Max(A.y, B.y));
+ A = a;
+ B = b;
+ }
+
+ ///
+ /// Checks if the area between two points is nonzero
+ ///
+ ///
+ ///
+ ///
+ bool RegionValid(Vector2Int From, Vector2Int To) => Math.Abs(From.x - To.x) * Math.Abs(From.y - To.y) != 0;
+
+ ///
+ /// Checks to see if there is a floor at the given location.
+ /// If is provided as , it will check floors on ALL levels.
+ ///
+ ///
+ ///
+ ///
+ public bool IsFloorAt(Vector2Int Position, int? Level = default)
+ {
+ bool inquire(int level, int index)
+ {
+ var value = architecture.FloorPatterns.Data[level][index];
+ return value.w != 0 || value.x != 0 || value.y != 0 || value.z != 0;
+ }
+
+ int level = 0;
+ bool allLevels = Level == default;
+ if (!allLevels)
+ level = -architecture.BaseFloor + Level.Value;
+
+ var mPos = Position;
+
+ bool inquiryList(int level, int index)
+ {
+ if (inquire(level, index)) return true;
+ if (index == 0) return false;
+ if (inquire(level, index - 1)) return true;
+ index = ((mPos.x - 1) * architecture.FloorPatterns.Height) + mPos.y;
+ if (inquire(level, index)) return true;
+ return false;
+ }
+
+ int index = (mPos.x * architecture.FloorPatterns.Height) + mPos.y;
+
+ if (!allLevels)
+ return inquiryList(level, index);
+
+ for (level = architecture.BaseFloor; level < architecture.FloorPatterns.Data.Length; level++)
+ if (inquiryList(level, index)) return true;
+ return false;
+ }
+
+ ///
+ /// Ensures the provided has layers up to the provided floor level
+ ///
+ /// Function used to fill the array's new level(s) if applicable.
+ /// arg1 is the index in the array being filled
+ /// arg2 is the value at arg1 on the floor beneath this one. If this is the lowest floor,
+ /// it will reference itself.
+ void Ensure3DViewDepth2Floor(_3DArrayView ArrayView, int Floor, Func FillFunc) where T : unmanaged
+ {
+ //ensure a layer for elevation exists
+ if (ArrayView.Depth <= Floor)
+ {
+ int fromFloor = ArrayView.Depth;
+ int toFloor = Floor;
+ //make new level(s)
+ ArrayView.Resize(Floor + 1);
+ for(int f = fromFloor; f <= toFloor; f++)
+ { // iterate over new level(s)
+ int underFloor = f - 1;
+ if (underFloor < BaseFloor) underFloor = BaseFloor;
+ for (int i = 0; i < ArrayView.Width * ArrayView.Height; i++)
+ {
+ T underValue = ArrayView.Data[underFloor][i];
+ ArrayView.Data[f][i] = FillFunc(i, underValue); // invoke fillfunc to fill the array
+ }
+ }
+ ArrayView.Commit();
+ }
+ }
+ ///
+ /// Ensures the provided has layers up to the provided floor level
+ ///
+ /// Value used to fill the newly created array levels
+ void Ensure3DViewDepth2Floor(_3DArrayView ArrayView, int Floor, T DefaultFillVal) where T : unmanaged
+ {
+ //ensure a layer for elevation exists
+ if (ArrayView.Depth <= Floor)
+ {
+ int fromFloor = ArrayView.Depth;
+ int toFloor = Floor;
+ //make new level(s)
+ ArrayView.Resize(Floor + 1);
+ for (int f = fromFloor; f <= toFloor; f++)
+ { // iterate over new level(s)
+ for (int i = 0; i < ArrayView.Width * ArrayView.Height; i++)
+ ArrayView.Data[f][i] = DefaultFillVal; // invoke fillfunc to fill the array
+ }
+ ArrayView.Commit();
+ }
+ }
+
+ public void DeleteFloors(Vector2Int From, Vector2Int To, int Level = 0) =>
+ CreateFloors(From, To, 0, Level, false);
+ ///
+ /// Creates an area of flooring with the specified .
+ /// This function handles adding the pattern reference to the
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CreateFloors(Vector2Int From, Vector2Int To, string PatternName, int Level = 0, bool ModifyTerrain = true)
+ {
+ if (!architecture.EnsurePatternReferenced(PatternName, LotArchitecture.ArchitectureGameObjectTypes.floor, out ushort patternID, out _))
+ {
+ LogHistory($"Could not create flooring due to the pattern: {PatternName} not existing or isn't valid.");
+ return;
+ }
+ CreateFloors(From, To, patternID, Level, ModifyTerrain);
+ }
+ public void CreateFloors(Vector2Int From, Vector2Int To, ushort PatternID, int Level = 0, bool ModifyTerrain = true) =>
+ CreateFloors(From, To, new Files.Formats.DBPF.Vector4(PatternID, PatternID, PatternID, PatternID), Level, ModifyTerrain);
+ public void CreateFloors(Vector2Int From, Vector2Int To,
+ Vector4 QuarterTilePatternIDs, int Level = 0, bool ModifyTerrain = true)
+ {
+ if (!RegionValid(From, To)) return; // check area nonzero
+
+ var from = From; var to = To; // order the points in order of closest to origin of the lot first
+ OrderPoints(ref from, ref to);
+
+ if (ModifyTerrain) // flatten the area of terrain if applicable
+ {
+ Vector2Int pollPosition = From; // NE corner of the origin tile always
+ if(From.y > To.y) // selection is towards NORTH
+ pollPosition.y -= 1;
+ else pollPosition.y += 1;
+ if (From.x > To.x) // selection is towards WEST
+ ;
+ else pollPosition.x += 1;
+
+ float elevation = architecture.PollElevation(pollPosition, Level);
+ LevelRegion(from, to, elevation, Level); // level terrain under floors
+ }
+ Level = -architecture.BaseFloor + Level;
+ _3DArrayView> floorPatterns = architecture.FloorPatterns;
+
+ //ensure a layer for elevation exists
+ Ensure3DViewDepth2Floor(floorPatterns, Level, new Vector4()); // fill with empty
+
+ ref Vector4[] baseFloorData = ref floorPatterns.Data[Level];
+
+ int changedFloors = 0;
+ for (int tx = from.x; tx < to.x; tx++)
+ {
+ for (int ty = from.y; ty < to.y; ty++)
+ {
+ var mPos = new Vector2Int(tx, ty);
+ int index = (mPos.x * floorPatterns.Height) + mPos.y;
+
+ if (index > baseFloorData.Length - 1)
+ index = baseFloorData.Length - 1;
+ if (index < 0)
+ index = 0;
+
+ baseFloorData[index] = QuarterTilePatternIDs;
+ changedFloors++;
+ }
+ }
+
+ if (changedFloors <= 0) return;
+
+ LogHistory($"Modified flooring. From: {From} To: {To} PatIDs: {QuarterTilePatternIDs}" +
+ $" Floor: {Level} TerrainMod?: {ModifyTerrain}", "Modify Flooring");
+ loadedLot.InvalidateFloors(); // invalidating floors also may incur a terrain re-evaluation if necessary
+ }
+
+ ///
+ /// Levels a region of terrain/elevation map.
+ ///
+ /// The point to start at
+ /// The point to go to.
+ /// What elevation value to level to.
+ /// Which level of the Elevation Map to perform the modification.
+ /// If true, will ensure each level beneath does not exceed this elevation.
+ /// Only greater elevations will be modified, lower ones will be ignored.
+ /// In the event the terrain on a lower floor exceeds the elevation here on this floor, by how much to
+ /// decrease to yield the elevation of the terrain on the lower floor?
+ public void LevelRegion(Vector2Int From, Vector2Int To, float Elevation = 0, int Floor = 0, bool LevelFloorsBeneathMe = true, float LevelBeneathMeDelta = -0f)
+ {
+ if (!RegionValid(From, To)) return;
+ Floor = -architecture.BaseFloor + Floor;
+
+ var from = From; var to = To;
+ OrderPoints(ref from, ref to);
+
+ //check the cheat system for constrainfloorelevation cheat
+ bool cfeProp = ConstrainedFloorElevation;
+
+ _3DArrayView arr = architecture.Elevation;
+
+ //ensure a layer for elevation exists
+ //fill function will set the entire level to appropriate wall height.
+ Ensure3DViewDepth2Floor(arr, Floor, (int i, float beneathElevation) => beneathElevation + LotWallComponent.WallHeight);
+
+ ref float[] dataSet = ref arr.Data[Floor];
+ bool levelModify = false;
+ for (int tx = from.x; tx <= to.x; tx++)
+ {
+ for(int ty = from.y; ty <= to.y; ty++)
+ {
+ var mPos = new Vector2Int(tx, ty);
+ int index = (mPos.x * arr.Height) + mPos.y;
+
+ if (index > dataSet.Length - 1)
+ index = dataSet.Length - 1;
+ if (index < 0)
+ index = 0;
+
+ if (cfeProp && IsFloorAt(mPos))
+ continue;
+
+ dataSet[index] = Elevation;
+ levelModify = true;
+
+ //level beneath me
+ if (LevelFloorsBeneathMe)
+ {
+ try
+ {
+ for (int elevationLevel = Floor; elevationLevel > 0; elevationLevel--)
+ {
+ ref float[] meValDataSet = ref arr.Data[elevationLevel - 1];
+ meValDataSet[index] = Math.Min(Elevation + LevelBeneathMeDelta, meValDataSet[index]);
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.LogError($"LevelRegion() :: LevelBeneathMe error: {e.Message}");
+ }
+ }
+ }
+ }
+
+ if (!levelModify) return;
+
+ LogHistory($"Leveled terrain. From: {From} To: {To} Elevation: {Elevation}" +
+ $" Floor: {Floor}", "Level Terrain");
+ //all changes complete, invalidate mesh
+ loadedLot.InvalidateFloors();
+ }
+ ///
+ /// Sets the elevation at the position in lot grid coordinates at the Floor provided by adding it to the elevation of the lower level.
+ ///
+ ///
+ ///
+ ///
+ public void SetElevationRelative(float RelativeElevation, int Floor, params Vector2Int[] Positions)
+ {
+ Floor = -architecture.BaseFloor + Floor;
+
+ _3DArrayView arr = architecture.Elevation;
+ //ensure a layer for elevation exists
+ //fill function will set the entire level to appropriate wall height.
+ Ensure3DViewDepth2Floor(arr, Floor, (int i, float beneathElevation) => beneathElevation + RelativeElevation);
+
+ ref float[] dataSet = ref arr.Data[Floor];
+ foreach (var p in Positions)
+ {
+ var mPos = p;
+ int index = (mPos.x * arr.Height) + mPos.y;
+
+ float lowerElevation = dataSet[index];
+ if (Floor > 0)
+ lowerElevation = arr.Data[Floor - 1][index];
+
+ dataSet[index] = lowerElevation + RelativeElevation;
+ }
+ }
+
+ ///
+ /// Submits a terrain modification with the supplied parameters
+ ///
+ /// The center point of the terrain modification, in lot grid coordinates
+ /// The radius of the terrain brush being used, in grid cells
+ /// By how much should the terrain be altered in this call
+ /// Which modification is requested
+ ///
+ public bool ModifyTerrain(Vector2Int Position, int Radius, float Strength, TerrainModificationModes Mode, int Floor = 0)
+ {
+ if (Mode == TerrainModificationModes.None) return false;
+ Floor = -architecture.BaseFloor + Floor;
+ ref float[] dataSet = ref architecture.Elevation.Data[Floor];
+
+ var mPos = Position;
+ int index = (mPos.x * architecture.Elevation.Height) + mPos.y;
+
+ //The Sims 2 won't allow any terrain modifications if the cursor is directly over unexposed terrain
+ if (ConstrainedFloorElevation && IsFloorAt(mPos, Floor))
+ return false;
+
+ bool levelModify = false;
+
+ for (int dx = -Radius; dx <= Radius; dx++)
+ {
+ for (int dy = -Radius; dy <= Radius; dy++)
+ {
+ //get position to edit
+ mPos = Position + new Vector2Int(dx, dy);
+ index = (mPos.x * architecture.Elevation.Height) + mPos.y;
+
+ //measure length from center to this point
+ int length = (int)Math.Round(Vector2.Distance(mPos, Position),0);
+ if (length > Radius) continue;
+
+ float _stren = Strength;
+
+ //CFE cheat
+ if (ConstrainedFloorElevation && IsFloorAt(mPos))
+ continue;
+
+ //terrain modification
+ switch (Mode)
+ {
+ default: return false; // ??
+ case TerrainModificationModes.Water:
+ case TerrainModificationModes.Lower:
+ _stren *= -1;
+ goto case TerrainModificationModes.Raise;
+ case TerrainModificationModes.Raise:
+ dataSet[index] += _stren;
+ levelModify = true;
+ break;
+ }
+ }
+ }
+
+ if (!levelModify) return false;
+
+ LogHistory($"{Mode}ed terrain. Center: {Position} Radius: {Radius} Strength: {Strength} Level: {Floor}", "Level Terrain");
+ //all changes complete, invalidate mesh
+ loadedLot.InvalidateFloors();
+ return true;
+ }
+
+ ///
+ /// Resets the lot to having nothing on it and flattened terrain
+ /// Note: It is highly recommended to alert the user before performing an action like this.
+ ///
+ ///
+ public void FlattenLot(float Elevation = 0)
+ {
+ int Floor = 0;
+ Floor = -architecture.BaseFloor + Floor;
+ ref float[] dataSet = ref architecture.Elevation.Data[-architecture.BaseFloor];
+ for(int i = 0; i < dataSet.Length; i++)
+ dataSet[i] = Elevation;
+ //all changes complete, invalidate mesh
+ loadedLot.InvalidateFloors();
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildModeServer.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildModeServer.cs.meta
new file mode 100644
index 00000000..e776a78d
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildModeServer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1e91cfd046356bd4496e1fe219b072b9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildTools.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildTools.cs
new file mode 100644
index 00000000..626c10ae
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildTools.cs
@@ -0,0 +1,12 @@
+namespace OpenTS2.Engine.Modes.Build
+{
+ internal enum BuildTools
+ {
+ None,
+ Hand,
+ Wall,
+ TerrainBrush,
+ Foundation,
+ Floor,
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildTools.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildTools.cs.meta
new file mode 100644
index 00000000..979e247e
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/BuildTools.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: be91059a50da924489cf226bdd5d6d45
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools.meta
new file mode 100644
index 00000000..502e3bdb
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4b7ab705d82e6f8419b9d1db9e8ff271
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/AbstractBuildTool.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/AbstractBuildTool.cs
new file mode 100644
index 00000000..878da93a
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/AbstractBuildTool.cs
@@ -0,0 +1,296 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace OpenTS2.Engine.Modes.Build.Tools
+{
+ ///
+ /// Context passed a parameter for functions on a instance
+ ///
+ internal class BuildToolContext
+ {
+ ///
+ /// The position of the 3D mouse cursor in world space
+ ///
+ public Vector3 Cursor3DWorldPosition { get; set; }
+ ///
+ /// The position of the wand snapped to grid coordinates
+ ///
+ public Vector3 WandPosition { get; set; }
+ ///
+ /// The position of the wand in the 2D lot grid
+ ///
+ public Vector2Int GridPosition { get; set; }
+ ///
+ /// The quadrant the 3D mouse cursor is in
+ /// Chart Quadrants: 1 = top left, 2 = top right, 3 = bottom right, 4 = bottom left
+ ///
+ public int GridTileCursorQuadrant { get; set; }
+ public int CursorFloor { get; internal set; }
+ }
+
+ ///
+ /// A build tool with basic Pick-Up and Put-Down functionality
+ ///
+ internal abstract class AbstractBuildTool
+ {
+ public delegate void OnFinalizeToolHandler(AbstractBuildTool sender);
+ ///
+ /// Called when the user releases the tool and the changes will be submitted.
+ ///
+ public event OnFinalizeToolHandler OnFinalizeTool;
+
+ ///
+ /// The name of this tool
+ ///
+ public abstract string ToolName { get; }
+ ///
+ /// The current type of tool this instance is acting as
+ ///
+ public abstract BuildTools ToolType { get; }
+ ///
+ /// True when the tool is actively being used by the user. (A wall is being created, for instance)
+ ///
+ public bool IsHolding { get; protected set; }
+ ///
+ /// True when the tool is not being actively used but is ready for user input
+ ///
+ public bool IsActive { get; private set; } = false;
+ ///
+ /// The position within the lot boundaries of the tool at the current time
+ ///
+ public Vector3 ToolLotPosition { get; protected set; }
+ ///
+ /// The position on the grid that the tool is currently nearest to
+ ///
+ public Vector2 ToolGridPosition { get; protected set; }
+ ///
+ /// Sets the property
+ ///
+ ///
+ public void SetActive(bool Activated)
+ {
+ IsActive = Activated;
+ OnActiveChanged(Activated);
+ }
+ protected abstract void OnActiveChanged(bool NewValue);
+ ///
+ /// Called once per frame for the tool to process changes between frames
+ ///
+ ///
+ public virtual void OnToolUpdate(BuildToolContext Context)
+ {
+ ToolLotPosition = Context.WandPosition;
+ ToolGridPosition = Context.GridPosition;
+ }
+ ///
+ /// Called when a tool begins to be used by the user
+ ///
+ ///
+ public abstract void OnToolStart(BuildToolContext Context);
+ ///
+ /// Called when a tool is cancelled.
+ /// Note: Calling this at any time will immediately cancel the current tool action, you can give a reason to add clarity
+ /// to why this was called.
+ ///
+ ///
+ public abstract void OnToolCancel(string Reason = null);
+ ///
+ /// Called when the user commits to an action. (Left click)
+ ///
+ ///
+ public virtual void OnToolFinalize(BuildToolContext Context) => OnFinalizeTool?.Invoke(this);
+ }
+
+ ///
+ /// Represents shared behavior for tools in Build Mode where the user can click and drag an area to manipulate the lot
+ ///
+ internal abstract class AbstractRegionSelectBuildTool : AbstractBuildTool
+ {
+ //protected fields
+ ///
+ /// Defines behaviors for how the cursor should react to moving between levels
+ ///
+ protected enum MultilevelBehavior
+ {
+ ///
+ /// If the cursor leaves the level it started dragging, cancel the action immediately.
+ ///
+ CancelAction,
+ ///
+ /// If the cursor is not at the same level as it started when the action is finished, cancel the action.
+ /// Unlike , the action won't be cancelled immediately.
+ ///
+ Deny,
+ ///
+ /// Allows the cursor to freely move between levels and will not intervene.
+ ///
+ Allow,
+ ///
+ /// Keeps the level the same as when the tool started dragging so the level is retained even when the cursor moves between levels
+ ///
+ Constrain,
+ }
+
+ protected Vector2Int toolDragStart;
+ protected Vector2Int toolDragEnd;
+ protected Vector2Int toolLastActionDragEnd;
+ protected Vector3 toolDragStartWorldPos;
+ protected int toolDragFloor;
+
+ //public properties
+ ///
+ /// Where the user drag gesture initiated, in lot grid coordinates
+ ///
+ public Vector2Int ToolDragOrigin => toolDragStart;
+ ///
+ /// Where the user drag gesture ended, in lot grid coordinates
+ ///
+ public Vector2Int ToolDragDestination => toolDragEnd;
+ ///
+ /// Where the user drag gesture started in terms of which floor
+ ///
+ public int ToolDragFloor => toolDragFloor;
+
+ //Protected
+ protected bool DeleteMode { get; set; }
+ protected static GameObject ToolCursorObject { get; set; }
+ protected static Transform ToolCursorObjectTransform => ToolCursorObject.transform;
+ protected static GameObject SelectionStartToolCursor { get; set; }
+ ///
+ /// See:
+ ///
+ protected virtual MultilevelBehavior MultiLevelBehavior { get; set; } = MultilevelBehavior.Deny;
+
+ protected BuildModeServer BuildModeServer { get; }
+
+ protected AbstractRegionSelectBuildTool(BuildModeServer Server) : base()
+ {
+ BuildModeServer = Server;
+ Init();
+ }
+
+ protected virtual void Init()
+ {
+ //get the default Wand
+ if (ToolCursorObject == null)
+ ToolCursorObject = GameObject.Find("Wand");
+ if (SelectionStartToolCursor == null)
+ {
+ SelectionStartToolCursor = GameObject.Find("debugWandCompanion");
+ SelectionStartToolCursor.SetActive(false);
+ }
+ }
+
+ protected override void OnActiveChanged(bool NewValue)
+ {
+ ToolCursorObject.SetActive(NewValue);
+ if (!NewValue) SelectionStartToolCursor.SetActive(false);
+ }
+
+ public override void OnToolStart(BuildToolContext Context)
+ {
+ if (IsHolding) return; // huh? weird edge case here
+ IsHolding = true;
+ toolDragStart = toolDragEnd = toolLastActionDragEnd = Context.GridPosition;
+ toolDragFloor = Context.CursorFloor;
+
+ //set the selection origin cursor
+ toolDragStartWorldPos = Context.WandPosition;
+ SelectionStartToolCursor.SetActive(true);
+ SelectionStartToolCursor.transform.position = Context.WandPosition;
+ return;
+ }
+
+ public override void OnToolUpdate(BuildToolContext Context)
+ {
+ base.OnToolUpdate(Context);
+
+ var dirtyToolDragEnd = toolDragEnd;
+
+ if (!IsActive) return;
+ ToolCursorObjectTransform.position = Context.WandPosition;
+ if (!IsHolding) return;
+ toolDragEnd = Context.GridPosition;
+
+ if (MultiLevelBehavior == MultilevelBehavior.CancelAction && toolDragFloor != Context.CursorFloor)
+ {// drag between floors!
+ OnToolCancel("Start and end positions are not at the same level.");
+ return;
+ }
+
+ //check if player change wall creation type
+ CheckDeleteMode();
+
+ if(dirtyToolDragEnd != toolDragEnd)
+ //clear any created wall facades
+ DoHoverAction(true);
+ toolLastActionDragEnd = toolDragEnd;
+
+ //User let go of the Wand, signal finalize event
+ if (!Input.GetMouseButton(0))
+ { // finalize
+ OnToolFinalize(Context);
+ return;
+ }
+
+ if (dirtyToolDragEnd != toolDragEnd)
+ //Update facade
+ DoHoverAction();
+ }
+
+ public override void OnToolFinalize(BuildToolContext Context)
+ {
+ IsHolding = false; // finish drag gesture
+ if (toolDragStart == toolDragEnd) return; // misclick
+ if ((MultiLevelBehavior == MultilevelBehavior.CancelAction || MultiLevelBehavior == MultilevelBehavior.Deny)
+ && toolDragFloor != Context.CursorFloor)
+ {// drag between floors!
+ OnToolCancel("Start and end positions are not at the same level.");
+ return;
+ }
+
+ //check if player change creation type
+ CheckDeleteMode();
+ //Create / Delete the area
+ DoAction();
+
+ SelectionStartToolCursor.SetActive(false); // hide cursor
+ IsHolding = false; // drop tool
+
+ base.OnToolFinalize(Context);
+ }
+
+ public override void OnToolCancel(string Reason)
+ {
+ DoHoverAction(true);
+
+ SelectionStartToolCursor.SetActive(false); // hide cursor
+ IsHolding = false; // drop tool
+
+ Debug.Log($"{ToolName} cancelled. Reason: {Reason ?? "none given"}");
+ }
+
+ protected virtual void CheckDeleteMode()
+ {
+ //check if deleting objects
+ DeleteMode = false;
+ if (Input.GetKey(KeyCode.LeftControl)) DeleteMode = true;
+ }
+ ///
+ /// Performs the action that this tool advertises.
+ /// Note: This is not the same thing as as that is distinctly
+ /// when the user finishes the action. can be called many times depending on the implementation.
+ ///
+ ///
+ protected abstract void DoAction(bool Undo = false);
+ ///
+ /// Same as except with the intention of previewing the action before committing it
+ ///
+ ///
+ protected virtual void DoHoverAction(bool Undo = false) => DoAction(Undo);
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/AbstractBuildTool.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/AbstractBuildTool.cs.meta
new file mode 100644
index 00000000..6d4038e1
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/AbstractBuildTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: be2df28f574842c48b230b4c3cf403dc
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FloorTool.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FloorTool.cs
new file mode 100644
index 00000000..6a8a745a
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FloorTool.cs
@@ -0,0 +1,83 @@
+namespace OpenTS2.Engine.Modes.Build.Tools
+{
+ ///
+ /// Build tools that allow the user to make selections using the catalog in Build Mode
+ ///
+ internal interface ICatalogSelectableBuildTool
+ {
+
+ }
+ ///
+ /// Build tool that takes patterns from Build Mode (walls and floors)
+ ///
+ internal interface IPatternSelectableBuildTool
+ {
+ ///
+ /// Dictates whether the tool should be using the or property
+ ///
+ bool IDPainting { get; }
+
+ ///
+ /// The name of the pattern's material
+ ///
+ string PatternName { get; }
+ ///
+ /// The ID of the pattern in the target component map.
+ /// Walls and Floors (for example) both have individual WallMaps and FloorMaps
+ /// independent of one another and this mode should generally not be used in favor of .
+ ///
+ ushort PatternID { get; }
+
+ ///
+ /// Sets the pattern the floor tool is currently painting
+ ///
+ void SetPaintPattern(string PatternName);
+ ///
+ /// Sets the pattern the floor tool is currently painting
+ ///
+ void SetPaintPatternID(ushort PatternID);
+ }
+
+ ///
+ /// This Build Tool allows the User to drag out areas of flooring.
+ /// It invokes the for all lot modifications
+ ///
+ internal class FloorTool : AbstractRegionSelectBuildTool, IPatternSelectableBuildTool
+ {
+ public override string ToolName => "Floor Tool";
+ public override BuildTools ToolType => BuildTools.Floor;
+ public bool IDPainting { get; private set; }
+ public string PatternName { get; private set; }
+ public ushort PatternID { get; private set; }
+
+ public FloorTool(BuildModeServer Server) : base(Server) { }
+
+ public void SetPaintPattern(string PatternName)
+ {
+ IDPainting = false;
+ this.PatternName = PatternName;
+ }
+ public void SetPaintPatternID(ushort PatternID)
+ {
+ IDPainting = true;
+ this.PatternID = PatternID;
+ }
+
+ protected override void DoAction(bool Undo = false)
+ {
+ if (DeleteMode)
+ BuildModeServer.DeleteFloors(ToolDragOrigin, ToolDragDestination, ToolDragFloor);
+ else
+ {
+ if (IDPainting)
+ BuildModeServer.CreateFloors(ToolDragOrigin, ToolDragDestination, PatternID, ToolDragFloor);
+ else BuildModeServer.CreateFloors(ToolDragOrigin, ToolDragDestination, PatternName, ToolDragFloor);
+ }
+ }
+
+ protected override void DoHoverAction(bool Undo = false)
+ {
+ ;
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FloorTool.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FloorTool.cs.meta
new file mode 100644
index 00000000..50429916
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FloorTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: af9a2f90db10abc4390cbda8afdecf9b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FoundationTool.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FoundationTool.cs
new file mode 100644
index 00000000..6f714be4
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FoundationTool.cs
@@ -0,0 +1,92 @@
+using OpenTS2.Content.DBPF;
+using OpenTS2.Engine.Modes.Build.Tools;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace OpenTS2.Engine.Modes.Build.Tools
+{
+ ///
+ /// A build tool for creating foundations on the lot
+ /// This tool can be boiled down to the and put together
+ ///
+ internal class FoundationTool : AbstractRegionSelectBuildTool
+ {
+ const float FoundationHeight = 1;
+ const string FoundationWallpaper = "foundationbrick";
+ private const string FoundationFlooring = "wood_wide_planks";
+
+ ///
+ /// Sets up a new Foundation Tool with the specified walls and floors
+ /// By default this is The Sims 2 default values Brick walls and Wood floors
+ ///
+ ///
+ ///
+ ///
+ public FoundationTool(BuildModeServer Server,
+ string Wallpaper = FoundationWallpaper,
+ string Flooring = FoundationFlooring) : base(Server)
+ {
+ WallpaperPattern = Wallpaper;
+ FlooringPattern = Flooring;
+ }
+
+ public override string ToolName => "Foundation Tool";
+ public override BuildTools ToolType => BuildTools.Foundation;
+
+ //**PATTERN
+ ///
+ /// The pattern to paint the walls when creating a Foundation
+ ///
+ public string WallpaperPattern { get; set; }
+ ///
+ /// The pattern to paint the floors when creating a Foundation
+ ///
+ public string FlooringPattern { get; set; }
+ //***
+
+ protected override MultilevelBehavior MultiLevelBehavior { get; set; } = MultilevelBehavior.Constrain;
+
+
+ protected override void DoAction(bool Undo = false)
+ {
+ if (DeleteMode) Undo = !Undo;
+ if (ToolDragFloor > 1)
+ {
+ OnToolCancel("Foundation can only be placed on terrain.");
+ return;
+ }
+
+ int currFloor = 0;
+ int nextFloor = currFloor + 1;
+
+ float elevation = BuildModeServer.PollElevation(ToolDragOrigin, ToolDragFloor);
+ if (ToolDragFloor == 0)
+ elevation += FoundationHeight;
+ //This levels the Elevation map where the floor is on this foundation.
+ //This makes the walls stubby and the floor appear seamless. This also uses the LevelBeneathMe feature of the function.
+ //It will ensure all terrain beneath the foundation is less than the elevation -.1f.
+ BuildModeServer.LevelRegion(ToolDragOrigin, ToolDragDestination, elevation, nextFloor, true, -.1f);
+ if (!Undo)
+ {
+ BuildModeServer.CreateWalls(ToolDragOrigin, ToolDragDestination, currFloor,
+ Content.DBPF.WallType.Foundation, BuildModeServer.WallCreationModes.Room,
+ WallpaperPattern, WallpaperPattern);
+ BuildModeServer.CreateFloors(ToolDragOrigin, ToolDragDestination, FlooringPattern, nextFloor, false);
+ }
+ else
+ {
+ BuildModeServer.DeleteWalls(ToolDragOrigin, ToolDragDestination, currFloor, BuildModeServer.WallCreationModes.Room);
+ BuildModeServer.DeleteFloors(ToolDragOrigin, ToolDragDestination, nextFloor);
+ }
+ }
+
+ protected override void DoHoverAction(bool Undo = false)
+ {
+ ;
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FoundationTool.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FoundationTool.cs.meta
new file mode 100644
index 00000000..b73ebff2
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/FoundationTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 62528670419620547b98daca6a7118d5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/HandTool.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/HandTool.cs
new file mode 100644
index 00000000..f2123bd2
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/HandTool.cs
@@ -0,0 +1,102 @@
+using OpenTS2.Components;
+using OpenTS2.Scenes.Lot;
+using System.Linq;
+using UnityEngine;
+
+namespace OpenTS2.Engine.Modes.Build.Tools
+{
+ //*** NEEDS A REWORK ONCE SCENEGRAPH RENDERING IS REWORKED BECAUSE THIS IS NOT A GOOD SOLUTION
+ internal class HandTool : AbstractBuildTool
+ {
+ private LotLoad loadedLot;
+ private LotArchitecture architecture;
+
+ private Transform holdingObjectRoot;
+ private GameObject holdingObjectReference;
+ private Vector3 originalPosition;
+ bool IsHoldingObject => holdingObjectReference != null;
+
+ public override string ToolName => "Hand Tool";
+ public override BuildTools ToolType => BuildTools.Hand;
+
+ ///
+ /// Creates a new on the specified lot
+ ///
+ ///
+ ///
+ public HandTool(LotLoad loadedLot, LotArchitecture architecture)
+ {
+ this.loadedLot = loadedLot;
+ this.architecture = architecture;
+
+ Init();
+ }
+
+ private void Init()
+ {
+
+ }
+
+ public override void OnToolCancel(string Reason)
+ {
+ if (!IsHoldingObject) return;
+
+ holdingObjectRoot.position = originalPosition;
+
+ holdingObjectReference = null;
+ holdingObjectRoot = null;
+
+ Debug.Log($"{ToolName} cancelled. Reason: {Reason ?? "null"}");
+ }
+
+ public override void OnToolUpdate(BuildToolContext Context)
+ {
+ base.OnToolUpdate(Context);
+
+ if (!IsHoldingObject) return;
+ holdingObjectRoot.position = Context.WandPosition;
+
+ if (Input.GetMouseButtonDown(0))
+ OnToolFinalize(Context);
+ }
+
+ public override void OnToolFinalize(BuildToolContext Context)
+ {
+ if (!IsHoldingObject) return;
+
+ holdingObjectRoot.position = Context.WandPosition;
+
+ holdingObjectReference = null;
+ holdingObjectRoot = null;
+
+ base.OnToolFinalize(Context);
+ }
+
+ public override void OnToolStart(BuildToolContext Context)
+ {
+ // PERFORM OBJECT HITTEST (The colliders are added in ScenegraphComponent)
+ Ray camRay = TestLotViewCamera.GetMouseCursor3DRay(Camera.main);
+ var hits = Physics.RaycastAll(camRay);
+
+ if (hits.Any())
+ { // find any hits
+ foreach (RaycastHit hit in hits.OrderBy(x => x.distance))
+ {
+ var parent = hit.collider.gameObject.transform.parent.gameObject;
+ if (parent.GetComponent() != null)
+ {
+ holdingObjectReference = parent;
+ holdingObjectRoot = holdingObjectReference.transform;
+ originalPosition = holdingObjectRoot.position;
+ break;
+ }
+ }
+ }
+ }
+
+ protected override void OnActiveChanged(bool NewValue)
+ {
+
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/HandTool.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/HandTool.cs.meta
new file mode 100644
index 00000000..07a9c5d6
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/HandTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4bac8d546ec25bb4e94cf635b9d3d75f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/TerrainBrushTool.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/TerrainBrushTool.cs
new file mode 100644
index 00000000..78882de4
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/TerrainBrushTool.cs
@@ -0,0 +1,109 @@
+using UnityEngine;
+using static OpenTS2.Engine.Modes.Build.BuildModeServer;
+
+namespace OpenTS2.Engine.Modes.Build.Tools
+{
+ ///
+ /// Handles the functionality for the Wall Tool interacting with the lot
+ ///
+ internal class TerrainBrushTool : AbstractBuildTool
+ {
+ ///
+ /// Radii of terrain brush sizes
+ ///
+ public enum TerrainBrushSizes
+ {
+ Small = 0, // since this tool is only 1 point, it's not a tile wide
+ Medium = 2,
+ Large = 4
+ }
+
+ //private
+ BuildModeServer buildModeServer;
+ ushort dryWallDesignPattern;
+ private GameObject rootWallCursor;
+ private Transform _wallCursorTransform;
+
+ ///
+ /// The current brush mode the tool is using
+ ///
+ public BuildModeServer.TerrainModificationModes CurrentBrushMode { get; set; }
+ ///
+ /// The current stroke thickness of the brush
+ ///
+ public int BrushSize { get; set; } = (int)TerrainBrushSizes.Large;
+ public float BrushStrength { get; set; } = 1f;
+
+ ///
+ /// Creates a new on the specified lot
+ ///
+ ///
+ ///
+ public TerrainBrushTool(BuildModeServer Server)
+ {
+ buildModeServer = Server;
+ Init();
+ }
+
+ void Init()
+ {
+ //get the default Wand
+ rootWallCursor = GameObject.Find("Wand");
+ _wallCursorTransform = rootWallCursor.transform;
+ }
+
+ public override string ToolName => "Terrain Tool";
+ public override BuildTools ToolType => BuildTools.TerrainBrush;
+
+ protected override void OnActiveChanged(bool NewValue)
+ {
+ rootWallCursor.SetActive(NewValue);
+ CurrentBrushMode = TerrainModificationModes.Raise;
+ }
+
+ public override void OnToolCancel(string Reason)
+ {
+ IsHolding = false; // drop tool
+
+ Debug.Log($"{ToolName} cancelled. Reason: {Reason ?? "null"}");
+ }
+
+ public override void OnToolFinalize(BuildToolContext Context)
+ {
+ IsHolding = false; // finish brush stroke
+
+ base.OnToolFinalize(Context);
+ }
+
+ public override void OnToolStart(BuildToolContext Context)
+ {
+ if (IsHolding) return; // huh? weird edge case here
+ IsHolding = true; // brush stroke start
+
+ CommitToolStroke(Context.GridPosition); // makes single clicks (only lasting a frame) possible
+ }
+
+ public override void OnToolUpdate(BuildToolContext Context)
+ {
+ base.OnToolUpdate(Context);
+
+ if (!IsActive) return;
+ _wallCursorTransform.position = Context.WandPosition;
+ if (!IsHolding) return;
+
+ //User let go of the Wand, signal finalize event
+ if (!Input.GetMouseButton(0))
+ { // finalize
+ OnToolFinalize(Context);
+ return;
+ }
+ //user is still painting
+ CommitToolStroke(Context.GridPosition);
+ }
+
+ void CommitToolStroke(Vector2Int position)
+ {
+ buildModeServer.ModifyTerrain(position, BrushSize, BrushStrength * Time.deltaTime, CurrentBrushMode);
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/TerrainBrushTool.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/TerrainBrushTool.cs.meta
new file mode 100644
index 00000000..de132f71
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/TerrainBrushTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 322a39bbf1f7a8446b4b6194c8ec1abf
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/WallTool.cs b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/WallTool.cs
new file mode 100644
index 00000000..b2721ce3
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/WallTool.cs
@@ -0,0 +1,124 @@
+using OpenTS2.Content.DBPF;
+using OpenTS2.Engine.Modes.Build.Tools;
+using OpenTS2.Scenes.Lot;
+using OpenTS2.Scenes.Lot.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+using static OpenTS2.Engine.Modes.Build.BuildModeServer;
+
+namespace OpenTS2.Engine.Modes.Build.Tools
+{
+ ///
+ /// Handles the functionality for the Wall Tool interacting with the lot
+ ///
+ internal class WallTool : AbstractRegionSelectBuildTool
+ {
+ // Wall cursor tool
+ WallCreationModes wallCreateMode = WallCreationModes.Single;
+ bool deletingWalls => DeleteMode;
+
+ //private
+ ///
+ /// This is used when dragging an area of walls.
+ /// The walls created as a facade have their IDs stored here.
+ /// When the action is cancelled or the facade changes, these IDs
+ /// will be used to delete the unneeded walls.
+ ///
+ HashSet facadeWallIDs = new HashSet();
+ BuildModeServer buildModeServer => BuildModeServer;
+ bool Hovering = false;
+
+ ///
+ /// Creates a new on the specified lot
+ ///
+ ///
+ ///
+ public WallTool(BuildModeServer Server) : base(Server) { }
+
+ public override string ToolName => "Wall Tool";
+ public override BuildTools ToolType => BuildTools.Wall;
+
+ ///
+ /// Updates depending on the player's current input
+ ///
+ protected override void CheckDeleteMode()
+ {
+ //creation mode
+ WallCreationModes mode = WallCreationModes.Single;
+ if (Input.GetKey(KeyCode.LeftShift)) mode = WallCreationModes.Room;
+ wallCreateMode = mode;
+
+ //check delete mode
+ base.CheckDeleteMode();
+ }
+
+ ///
+ /// Creates/Deletes the walls in the selected space
+ ///
+ protected override void DoAction(bool Undoing = false)
+ {
+ bool actionSelected = !deletingWalls;
+ Vector2Int destination = toolDragEnd;
+ if (deletingWalls) destination = toolLastActionDragEnd;
+ if (Undoing)
+ {
+ if(destination != toolLastActionDragEnd)
+ destination = toolLastActionDragEnd;
+ else destination = toolDragEnd;
+ actionSelected = !actionSelected;
+ }
+ if (toolDragStart - destination == new Vector2Int(0, 0))
+ {
+ if (Hovering && facadeWallIDs.Count > 0)
+ {
+ buildModeServer.DeleteAllWalls(facadeWallIDs.ToArray());
+ facadeWallIDs.Clear();
+ }
+ return; // zero area
+ }
+ if (actionSelected)
+ {
+ float startElevation = buildModeServer.PollElevation(toolDragStart, toolDragFloor);
+ //level upper elevation to ensure the wall is the correct size
+ foreach (var segment in GetWallPoints(toolDragStart, destination, wallCreateMode))
+ {
+ //level terrain beneath the wall
+ //buildModeServer.SetElevationRelative(startElevation, toolDragFloor, segment.A, segment.B);
+ //level above the wall to ensure the height is accurate
+ buildModeServer.SetElevationRelative(LotWallComponent.WallHeight, toolDragFloor + 1, segment.A, segment.B);
+ }
+ var createdWallIDs = buildModeServer.CreateWalls(toolDragStart, destination, toolDragFloor, WallType.Normal, wallCreateMode);
+ if (Hovering)
+ {
+ if (createdWallIDs != null)
+ Array.ForEach(createdWallIDs, (int item) => facadeWallIDs.Add(item));
+ }
+ else if (facadeWallIDs.Any())
+ {
+ facadeWallIDs.Clear();
+ buildModeServer.SignalRoomsInvalidate();
+ }
+ }
+ else
+ {
+ if (Hovering && facadeWallIDs.Count > 0)
+ {
+ buildModeServer.DeleteAllWalls(facadeWallIDs.ToArray());
+ facadeWallIDs.Clear();
+ }
+ buildModeServer.DeleteWalls(toolDragStart, destination, ToolDragFloor, wallCreateMode);
+ }
+ }
+
+ protected override void DoHoverAction(bool Undo = false)
+ {
+ Hovering = true;
+ DoAction(Undo);
+ Hovering = false;
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/WallTool.cs.meta b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/WallTool.cs.meta
new file mode 100644
index 00000000..e49f6257
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Modes/Build/Tools/WallTool.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7ed8cbcbc0bb3d24f864d9ff7650d4e6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Engine/Tests/BuildModeTest.cs b/Assets/Scripts/OpenTS2/Engine/Tests/BuildModeTest.cs
new file mode 100644
index 00000000..35dbe571
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Tests/BuildModeTest.cs
@@ -0,0 +1,143 @@
+// JDrocks450 11-22-2023 on GitHub
+
+using OpenTS2.Common;
+using OpenTS2.Content;
+using OpenTS2.Content.DBPF.Scenegraph;
+using OpenTS2.Content.DBPF;
+using OpenTS2.Files.Formats.DBPF;
+using OpenTS2.Files;
+using OpenTS2.Scenes.Lot.State;
+using OpenTS2.Scenes.Lot;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using UnityEngine;
+using static OpenTS2.Content.DBPF.LotObjectAsset;
+using OpenTS2.Scenes.Lot.Extensions;
+using OpenTS2.Engine.Modes.Build;
+using OpenTS2.UI.Layouts;
+using System.Linq;
+using OpenTS2.Engine;
+using OpenTS2.UI.Layouts.Lot;
+using UnityEditor.SearchService;
+using OpenTS2.Engine.Modes.Build.Tools;
+
+///
+/// Serves the behavior for the Build Mode Test scene
+///
+public class BuildModeTest : MonoBehaviour
+{
+ // Init
+ LotLoad _loadedLotProvider;
+ LotArchitecture _architectrue => _loadedLotProvider.Architecture;
+ TestLotViewCamera viewCamera;
+ BuildModeServer buildServer;
+ LotViewHUDController hudController;
+
+ void ContentStartup(bool LoadGameContent = true)
+ {
+ EPManager.Instance.InstalledProducts = 0x3EFFF;
+ ContentLoading.LoadContentStartup();
+
+ GameGlobals.allowCustomContent = false;
+
+ if (!LoadGameContent) return;
+
+ //load the game content
+ ContentLoading.LoadGameContentSync();
+ Core.OnFinishedLoading.Invoke();
+ CatalogManager.Instance.Initialize();
+ }
+
+ void Awake()
+ {
+ //Load content
+ ContentStartup(true);
+ }
+
+ void Start()
+ {
+ //load the default lot for now
+ _loadedLotProvider = gameObject.AddComponent();
+ _loadedLotProvider.LoadLot("N001", 80);
+
+ //create the build mode server
+ buildServer = gameObject.AddComponent();
+ buildServer.Initialize(_loadedLotProvider);
+ buildServer.ChangeTool(BuildTools.None);
+
+ //set the camera transform
+ viewCamera = GameObject.Find("Main Camera").GetComponent();
+
+ //connect to the HUD controller
+ hudController = GameObject.Find("Scene/LotViewUIController").GetComponent();
+ hudController.Puck.SetMode(LotViewHUD.HUD_UILotModes.Build); // set puck display state to be Build mode
+ hudController.Puck.OnModeChangeRequested += HUD_GameModeRequest;
+ }
+
+ private void HUD_GameModeRequest(object sender, LotViewHUD.ModeChangeEventArgs e)
+ {
+ e.Allow = false; // locked in Build Mode for the time being
+ }
+
+ // Update is called once per frame
+ void Update()
+ {
+ HandleTool();
+ }
+
+ void HandleTool()
+ {
+ if (buildServer.CurrentTool == null) return;
+
+ //set cursor position
+ ReevaluateTerrainMesh();
+ bool successful = viewCamera.TranslateScreen2WorldPos(_loadedLotProvider.Floor, out var terrainPosition, out int currentCursorFloor);
+ if (!successful) return; // do not update the cursor when the mouse leaves the lot boundary. this prevents any events from firing with build tools
+ var buildCursorPos = new Vector3((float)Math.Round(terrainPosition.x, 0), terrainPosition.y, (float)Math.Round(terrainPosition.z, 0));
+ Vector2Int wallcurPos = new Vector2Int((int)buildCursorPos.x, (int)buildCursorPos.z); // flat grid position for wall tool
+
+ //context
+ BuildToolContext context = new BuildToolContext()
+ {
+ Cursor3DWorldPosition = terrainPosition,
+ WandPosition = buildCursorPos,
+ GridPosition = wallcurPos,
+ CursorFloor = currentCursorFloor
+ };
+
+ buildServer.CurrentTool.OnToolUpdate(context); // mouse moved
+ if (Input.GetMouseButtonDown(0)) // mouse left click start
+ { // start drag
+ buildServer.CurrentTool.OnToolStart(context);
+ return;
+ }
+ if (Input.GetKeyDown(KeyCode.Escape))
+ {
+ buildServer.CurrentTool.OnToolCancel("User cancelled using the ESC key.");
+ return;
+ }
+ }
+
+ ///
+ /// Resets the -- this may be necessary when the terrain mesh is updated, for example
+ ///
+ void ReevaluateTerrainMesh()
+ {
+ var floorComp = _architectrue.GetComponentByType(LotArchitecture.ArchitectureGameObjectTypes.floor);
+ for(int floor = 0; floor < _architectrue.BaseFloor + _architectrue.Elevation.Depth; floor++)
+ {
+ int actualFloor = _architectrue.BaseFloor + floor;
+ List collidersByFloor = new List();
+ if (actualFloor == 0)
+ {
+ var terrainObject = GameObject.Find("terrain/Terrain");
+ collidersByFloor.Add(terrainObject.GetComponent());
+ }
+ if(floorComp.TryGetCollidersByFloor(floor, out var colliders))
+ collidersByFloor.AddRange(colliders);
+ viewCamera.SetCameraDetectionMeshes(actualFloor, collidersByFloor);
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Engine/Tests/BuildModeTest.cs.meta b/Assets/Scripts/OpenTS2/Engine/Tests/BuildModeTest.cs.meta
new file mode 100644
index 00000000..96a90ea0
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Engine/Tests/BuildModeTest.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 94cb5833fa52cfc4392f5daf80bc17b3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions.meta b/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions.meta
new file mode 100644
index 00000000..faec23cf
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a72c7287d7d00824197bc42606cacaca
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions/LotArchitectureBuildModeExtensions.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions/LotArchitectureBuildModeExtensions.cs
new file mode 100644
index 00000000..ad48ac49
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions/LotArchitectureBuildModeExtensions.cs
@@ -0,0 +1,201 @@
+using Codice.CM.Triggers;
+using Codice.Utils;
+using log4net.Core;
+using OpenTS2.Content.DBPF;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace OpenTS2.Scenes.Lot.Extensions
+{
+ //JDrocks450 'Bloaty' 12/9/23 -- Contains all build mode functions with respect to LotArchitecture instances in one unit
+
+ ///
+ /// Extensions for manipulating instances for build mode operations
+ ///
+ internal static class LotArchitectureBuildModeExtensions
+ {
+ ///
+ /// Creates a wall.
+ /// This function will ensure the following:
+ /// - The Wall is split into 1 unit long segments
+ /// - The Wall has the wallpaper you provide added to each segment created.
+ /// - The Wall is a straight line, diagonally, horizontally or vertically aligned
+ /// - The Wall is of the type provided.
+ /// - The Wall is added to the instance this is called on
+ ///
+ ///
+ ///
+ /// The point the wall originates
+ /// The point the wall is destined to
+ /// Which floor the wall is on
+ /// The type of wall you wish to create
+ /// The Pattern1 of the wall
+ /// The Pattern2 of the wall
+ /// upon total failure; no walls placed.
+ /// if some walls were placed. if all wall segments placed.
+ public static int[]? CreateWall(this LotArchitecture Architecture, Vector2 From,
+ Vector2 To, int Floor, WallType Type = WallType.Normal, ushort PatternFront = ushort.MaxValue, ushort PatternBack = ushort.MaxValue)
+ {
+ //get the wall's direction vector
+ var dirVector = To - From;
+ dirVector.Normalize();
+
+ bool diagonal = false;
+
+ if (Math.Abs(dirVector.x) == Math.Abs(dirVector.y))
+ {
+ dirVector = new Vector2(dirVector.x < 0 ? -1 : 1, dirVector.y < 0 ? -1 : 1);
+ diagonal = true;
+ }
+ if ((Math.Abs(dirVector.x) != 1 || dirVector.y != 0) && (Math.Abs(dirVector.y) != 1 || dirVector.x != 0) &&
+ (Math.Abs(dirVector.x) != 1 || Math.Abs(dirVector.y) != 1)) // valid directions for a wall
+ return null;
+
+ //subdivide the wall into 1 unit lengths (otherwise wall paints will appear stretchy)
+ var wallLength = (double)Math.Abs(Vector2.Distance(From, To));
+ if (diagonal) wallLength *= 0.7;
+
+ int successfulWallSegments = 0, totalSegments = (int)Math.Round(wallLength);
+ int[] createdWallIDs = new int[totalSegments];
+
+ for (int segment = 0; segment < (wallLength); segment++)
+ {
+ var fooFrom = From + (dirVector * segment);
+ var fooTo = From + (dirVector * (segment + 1));
+ //Add wall to wallgraph
+ if (!Architecture.WallGraphAll.PushWall(fooFrom, fooTo, Floor, out int LayerID)) continue;
+ if (createdWallIDs.Length <= segment)
+ Array.Resize(ref createdWallIDs, segment + 1);
+ createdWallIDs[segment] = LayerID;
+ //Add wall layer data
+ Architecture.WallLayer.Walls.Add(LayerID, new WallLayerEntry()
+ {
+ Id = LayerID,
+ Pattern1 = PatternFront,
+ Pattern2 = PatternBack,
+ WallType = Type
+ });
+ successfulWallSegments++;
+ }
+ if (successfulWallSegments <= 0) return null; // total failure
+ return createdWallIDs; // somewhat / complete success code
+ }
+ ///
+ /// Deletes wall segments between the two points provided on the given floor
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool? DeleteWall(this LotArchitecture Architecture, Vector2 From, Vector2 To, int Floor)
+ {
+ //get the wall's direction vector
+ var dirVector = To - From;
+ dirVector.Normalize();
+
+ bool diagonal = false;
+
+ if (Math.Abs(dirVector.x) == Math.Abs(dirVector.y))
+ {
+ dirVector = new Vector2(dirVector.x < 0 ? -1 : 1, dirVector.y < 0 ? -1 : 1);
+ diagonal = true;
+ }
+ if ((Math.Abs(dirVector.x) != 1 || dirVector.y != 0) && (Math.Abs(dirVector.y) != 1 || dirVector.x != 0) &&
+ (Math.Abs(dirVector.x) != 1 || Math.Abs(dirVector.y) != 1)) // valid directions for a wall
+ return false;
+
+ //subdivide the wall into 1 unit lengths (otherwise wall paints will appear stretchy)
+ var wallLength = (double)Math.Abs(Vector2.Distance(From, To));
+ if (diagonal) wallLength *= 0.7;
+
+ int successfulWallSegments = 0, totalSegments = (int)wallLength;
+ for (int segment = 0; segment < (wallLength); segment++)
+ {
+ var fooFrom = From + (dirVector * segment);
+ var fooTo = From + (dirVector * (segment + 1));
+ //Remove wall from wallgraph
+ if (!Architecture.WallGraphAll.RemoveWall(fooFrom, fooTo, Floor, out int LayerID)) continue;
+ //Remove wall layer data
+ Architecture.WallLayer.Walls.Remove(LayerID);
+ successfulWallSegments++;
+ }
+ if (successfulWallSegments <= 0) return null; // total failure
+ return successfulWallSegments < totalSegments ? false : true; // somewhat / complete success code
+ }
+ public static void DeleteAllWalls(this LotArchitecture Architecture, params int[] WallLayerIDs)
+ {
+ if (WallLayerIDs.Length <= 0) return;
+ foreach(var ID in WallLayerIDs)
+ {
+ //Remove wall from wallgraph
+ if (Architecture.WallGraphAll.RemoveWalls(WallLayerIDs) == 0) continue;
+ //Remove wall layer data
+ foreach(var id in WallLayerIDs)
+ Architecture.WallLayer.Walls.Remove(id);
+ }
+ }
+ ///
+ /// Checks to see if the given pattern name is referenced; if it isn't, will reference it in the
+ /// and returns the PatternID of the (potentially newly) referenced pattern
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// true if the pattern was already referenced
+ ///
+ public static bool EnsurePatternReferenced(this LotArchitecture arch, string PatternName,
+ LotArchitecture.ArchitectureGameObjectTypes Scope, out ushort PatternID, out bool Existed)
+ {
+ Existed = false;
+ PatternID = 0;
+
+ Dictionary Map = Scope switch
+ {
+ LotArchitecture.ArchitectureGameObjectTypes.wall => arch.WallMap.Map,
+ LotArchitecture.ArchitectureGameObjectTypes.floor => arch.FloorMap.Map,
+ _ => null
+ };
+
+ if (Map == null) return false;
+ if (Map.Count == 0)
+ goto create;
+ var hits = Map.Where(x => x.Value.Value == PatternName);
+ if (!hits.Any())
+ goto create;
+ Existed = true;
+ PatternID = hits.FirstOrDefault().Key;
+ return true;
+
+ //lord forgive me for using goto
+ create:
+ PatternID = (ushort)(Map.Count + 1);
+ if (Map.Any())
+ PatternID = Math.Max(PatternID, (ushort)(Map.Keys.Max() + 1));
+ Map.Add(PatternID, new StringMapEntry()
+ {
+ Id = PatternID,
+ Value = PatternName,
+ Unknown = 0
+ });
+ return true;
+ }
+
+ public static float PollElevation(this LotArchitecture architecture, Vector2Int Position, int Floor = 0)
+ {
+ Floor = -architecture.BaseFloor + Floor;
+
+ float[] dataSet = architecture.Elevation.Data[Floor];
+
+ var mPos = Position;
+ int index = (mPos.x * architecture.Elevation.Height) + mPos.y;
+
+ return dataSet[index];
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions/LotArchitectureBuildModeExtensions.cs.meta b/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions/LotArchitectureBuildModeExtensions.cs.meta
new file mode 100644
index 00000000..fb58a6c2
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/Extensions/LotArchitectureBuildModeExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 118886c7f98c90e44b3d7c13cbab0440
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/LotArchitecture.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/LotArchitecture.cs
index 738abc99..f5ded5d4 100644
--- a/Assets/Scripts/OpenTS2/Scenes/Lot/LotArchitecture.cs
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/LotArchitecture.cs
@@ -1,4 +1,5 @@
using OpenTS2.Common;
+using OpenTS2.Components;
using OpenTS2.Content.DBPF;
using OpenTS2.Files.Formats.DBPF;
using OpenTS2.Scenes.Lot.Roof;
@@ -154,27 +155,72 @@ private void Validate()
}
}
+ public T GetComponentByType(ArchitectureGameObjectTypes Type) where T : AssetReferenceComponent => Type switch
+ {
+ ArchitectureGameObjectTypes.roof => _roofComponent as T,
+ ArchitectureGameObjectTypes.wall => _wallComponent as T,
+ ArchitectureGameObjectTypes.floor => _floorComponent as T,
+ ArchitectureGameObjectTypes.terrain => _terrainComponent as T,
+ _ => default
+ };
+
+ public void InvalidateComponent(ArchitectureGameObjectTypes Type) => GetComponentByType(Type)?.Invalidate();
+
///
/// Creates game objects for the lot architecture, and returns them in the provided list.
///
/// List of created game objects
public void CreateGameObjects(List componentList)
{
- var roofObj = new GameObject("roof", typeof(LotRoofComponent));
- _roofComponent = roofObj.GetComponent().CreateFromLotArchitecture(this);
- componentList.Add(roofObj);
-
- var wall = new GameObject("wall", typeof(LotWallComponent));
- _wallComponent = wall.GetComponent().CreateFromLotArchitecture(this);
- componentList.Add(wall);
+ // make gameobjects for each type of object on the lot
+ for(int type = 0; type < Enum.GetValues(typeof(ArchitectureGameObjectTypes)).Length; type++)
+ {
+ ArchitectureGameObjectTypes tType = (ArchitectureGameObjectTypes)type;
+ if (!CreateGameObjectsOfType(componentList, tType))
+ ; // TODO: Handle if this fails
+ }
+ }
- var floor = new GameObject("floor", typeof(LotFloorComponent));
- _floorComponent = floor.GetComponent().CreateFromLotArchitecture(this);
- componentList.Add(floor);
+ public enum ArchitectureGameObjectTypes
+ {
+ roof,
+ wall,
+ floor,
+ terrain
+ }
- var terrain = new GameObject("terrain", typeof(LotTerrainComponent));
- _terrainComponent = terrain.GetComponent().CreateFromLotArchitecture(this);
- componentList.Add(terrain);
+ ///
+ /// Creates the correspondent GameObject for the type of geometry specified by
+ /// Adds it to the given component list automatically
+ ///
+ ///
+ ///
+ ///
+ public bool CreateGameObjectsOfType(List componentList, ArchitectureGameObjectTypes Type)
+ {
+ GameObject obj = default;
+ switch (Type)
+ {
+ case ArchitectureGameObjectTypes.roof:
+ var roofObj = obj = new GameObject("roof", typeof(LotRoofComponent));
+ _roofComponent = roofObj.GetComponent().CreateFromLotArchitecture(this);
+ break;
+ case ArchitectureGameObjectTypes.wall:
+ var wall = obj = new GameObject("wall", typeof(LotWallComponent));
+ _wallComponent = wall.GetComponent().CreateFromLotArchitecture(this);
+ break;
+ case ArchitectureGameObjectTypes.floor:
+ var floor = obj = new GameObject("floor", typeof(LotFloorComponent));
+ _floorComponent = floor.GetComponent().CreateFromLotArchitecture(this);
+ break;
+ case ArchitectureGameObjectTypes.terrain:
+ var terrain = obj = new GameObject("terrain", typeof(LotTerrainComponent));
+ _terrainComponent = terrain.GetComponent().CreateFromLotArchitecture(this);
+ break;
+ default: return false;
+ }
+ componentList.Add(obj);
+ return true;
}
private static int CalculateElevationIndex(int height, int x, int y)
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/LotFloorComponent.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/LotFloorComponent.cs
index 23b11477..70d0fe7d 100644
--- a/Assets/Scripts/OpenTS2/Scenes/Lot/LotFloorComponent.cs
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/LotFloorComponent.cs
@@ -24,10 +24,12 @@ public class LotFloorComponent : AssetReferenceComponent
private LotArchitecture _architecture;
private PatternMeshCollection _patterns;
+ private Dictionary> _colliders;
public LotFloorComponent CreateFromLotArchitecture(LotArchitecture architecture)
{
_architecture = architecture;
+ _colliders = new Dictionary>();
LoadPatterns();
BuildFloorMeshes();
@@ -136,6 +138,7 @@ private void BuildFloorMeshes()
int height = patternData.Height;
int eHeight = height + 1;
+ _colliders.Clear();
_patterns.ClearAll();
Vector3[] tileVertices = new Vector3[5];
@@ -196,6 +199,8 @@ void AddThickness(int from, int to)
PatternMesh thickness = floor.Get(0);
thickComp = thickness.Component;
}
+ if (!_colliders.ContainsKey(i))
+ _colliders.Add(i, new HashSet());
// Pattern is present.
float e0 = elevation[ei];
@@ -225,6 +230,8 @@ void AddThickness(int from, int to)
comp.AddVertices(tileVertices, tileUVs);
comp.AddTriangle(m1Base, 1, 0, 4);
+
+ _colliders[i].Add(mesh1.Object.GetComponent());
}
PatternMesh mesh2 = floor.Get(p.z + 1);
@@ -245,6 +252,7 @@ void AddThickness(int from, int to)
}
comp.AddTriangle(m2Base, 2, 1, 4);
+ _colliders[i].Add(mesh2.Object.GetComponent());
}
PatternMesh mesh3 = floor.Get(p.y + 1);
@@ -269,6 +277,7 @@ void AddThickness(int from, int to)
}
comp.AddTriangle(m3Base, 3, 2, 4);
+ _colliders[i].Add(mesh3.Object.GetComponent());
}
PatternMesh mesh4 = floor.Get(p.x + 1);
@@ -296,7 +305,8 @@ void AddThickness(int from, int to)
comp.AddVertices(tileVertices, tileUVs);
}
- comp.AddTriangle(m4Base, 0, 3, 4);
+ comp.AddTriangle(m4Base, 0, 3, 4);
+ _colliders[i].Add(mesh4.Object.GetComponent());
}
if (i > -baseFloor)
@@ -364,5 +374,12 @@ public void UpdateDisplay(WorldState state)
{
_patterns.UpdateDisplay(state, _architecture.BaseFloor);
}
+
+ internal bool TryGetCollidersByFloor(int floor, out IEnumerable Colliders)
+ {
+ var success = _colliders.TryGetValue(floor, out var value);
+ Colliders = value; // cringe out parameter doesn't do implicit casts ;_;
+ return success;
+ }
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/LotLoad.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/LotLoad.cs
new file mode 100644
index 00000000..056ae6d7
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/LotLoad.cs
@@ -0,0 +1,345 @@
+// JDrocks450 11-22-2023 on GitHub
+
+#define NEW_INVALIDATE
+#undef NEW_INVALIDATE
+
+using OpenTS2.Common;
+using OpenTS2.Content;
+using OpenTS2.Content.DBPF;
+using OpenTS2.Content.DBPF.Scenegraph;
+using OpenTS2.Files;
+using OpenTS2.Files.Formats.DBPF;
+using OpenTS2.Scenes.Lot.State;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UnityEngine;
+
+namespace OpenTS2.Scenes.Lot
+{
+ ///
+ /// Standard behavior for loading into the lot view.
+ /// Provides standard functionality for placing terrain, walls, floors, objects, etc. into the Scene
+ ///
+ public class LotLoad : MonoBehaviour
+ {
+ #region private
+ private WorldState _state = new WorldState(1, WallsMode.Roof);
+
+ private string _nhood;
+ private int _lotId;
+
+ private DBPFFile _lotPackage;
+
+ private List _lotObject = new List();
+ private List _testObjects = new List();
+
+ private RuntimeWallController _wallController = new RuntimeWallController();
+ #endregion
+
+ #region public
+ ///
+ /// Settings to dictate how the lot should be loaded
+ ///
+ public struct LotLoadSettings
+ {
+ [Flags]
+ public enum LotViewLayers : int
+ {
+ ///
+ /// Default - Do not display any architecture or objects
+ ///
+ None = 0,
+ ///
+ /// Flag - Loads terrain when set
+ ///
+ Terrain = 1,
+ ///
+ /// Flag - Loads walls when set
+ ///
+ Walls = 2,
+ ///
+ /// Flag - Loads floors when set
+ ///
+ Floors = 4,
+ ///
+ /// Flag - Loads objects when set
+ ///
+ Objects = 8
+ }
+ ///
+ /// Generates the flags necessary to load all layers
+ ///
+ public const LotViewLayers AllLayers =
+ LotViewLayers.None | LotViewLayers.Terrain | LotViewLayers.Walls | LotViewLayers.Floors | LotViewLayers.Objects;
+ ///
+ /// Specifies which layers of the lot geometry should be loaded
+ ///
+ public LotViewLayers LoadLayers;
+ ///
+ /// Makes a new using the specified layers.
+ /// Default is value -- to load all layers on the lot
+ ///
+ ///
+ public LotLoadSettings(LotViewLayers loadLayers = AllLayers)
+ {
+ LoadLayers = loadLayers;
+ }
+ }
+ ///
+ /// (Debug) N001
+ ///
+ public const string Default_NeighborhoodPrefix = "N001";
+ ///
+ /// (Debug) 82
+ ///
+ public const int Default_LotID = 82;
+
+ ///
+ /// The current floor selected in the
+ ///
+ public int Floor => _state.Level;
+ ///
+ /// The current ViewMode in the
+ ///
+ ///
+ public WallsMode ViewMode = WallsMode.Roof;
+
+ public int BaseFloor => Architecture?.BaseFloor ?? 0;
+ public int MaxFloor => Architecture?.FloorPatterns.Depth ?? 1;
+ ///
+ /// The current which indicates how the level should appear
+ /// Changing this directly will log in the console, but should be followed up with to ensure
+ /// changes can be perceived in the world.
+ ///
+ public WorldState WorldState
+ {
+ get => _state;
+ set
+ {
+ _state = value;
+ Debug.Log($"World state updated: {value}");
+ }
+ }
+
+ ///
+ /// The currently selected Neighborhood Prefix (N001 by default)
+ ///
+ public string NeighborhoodPrefix => _nhood;
+ ///
+ /// The currently selected Lot ID in the selected (82 by default)
+ ///
+ public int LotID => _lotId;
+ ///
+ /// The base that this instance used to load the scene
+ ///
+ public LotArchitecture Architecture { get; private set; }
+ #endregion
+
+ ///
+ /// Loads the default lot
+ /// See: and
+ ///
+ public void Load()
+ {
+ Load(Default_NeighborhoodPrefix, Default_LotID);
+ }
+
+ ///
+ /// Creates a new instance (that can be reused) with the specified Neighborhood and LotID to load
+ ///
+ ///
+ ///
+ public void Load(string Nhood, int LotID)
+ {
+ LoadLot(Nhood, LotID);
+ }
+ ///
+ /// Unloads the current lot and destroys each object instance in memory.
+ ///
+ public void UnloadLot()
+ {
+ foreach (var obj in _lotObject)
+ {
+ Destroy(obj);
+ }
+
+ _lotObject.Clear();
+ _testObjects.Clear();
+ }
+
+ ///
+ /// Updates and and loads the lot.
+ /// See:
+ ///
+ ///
+ ///
+ public void LoadLot(string neighborhoodPrefix, int id, LotLoadSettings Settings = default)
+ {
+ _nhood = neighborhoodPrefix;
+ _lotId = id;
+
+ LoadLot(Settings);
+ }
+ ///
+ /// Unloads the current lot (if applicable) and loads the lot provided by
+ ///
+ /// The settings to use to load the lot
+ public void LoadLot(LotLoadSettings Settings = default)
+ {
+ //Unload previous session
+ UnloadLot();
+
+ var contentProvider = ContentManager.Instance;
+ //Find package file for the lot given
+ var lotsFolderPath = Path.Combine(Filesystem.UserDataDirectory, $"Neighborhoods/{NeighborhoodPrefix}/Lots");
+ var lotFilename = $"{NeighborhoodPrefix}_Lot{LotID}.package";
+ var lotFullPath = Path.Combine(lotsFolderPath, lotFilename);
+
+ if (!File.Exists(lotFullPath))
+ {
+ throw new FileNotFoundException($"LoadLot(): File not found. {lotFullPath}");
+ }
+
+ _lotPackage = contentProvider.AddPackage(lotFullPath);
+
+ //add objects from scenegraph (primitive need to change to more robust solution later)
+ InvalidateObjects();
+
+ Architecture = new LotArchitecture();
+
+ Architecture.LoadFromPackage(_lotPackage);
+ Architecture.CreateGameObjects(_lotObject);
+
+ ViewMode = WallsMode.Roof; // Default value is Roof
+ _state = new WorldState(Floor, ViewMode);
+ InvalidateState();
+ }
+ ///
+ /// Flushes all currently loaded objects and reloads them from package
+ ///
+ public void InvalidateObjects()
+ {
+ //Clear any objects on the scene
+ UnloadLot();
+
+ var contentProvider = ContentManager.Instance;
+ // Go through each lot object.
+ foreach (var entry in _lotPackage.Entries)
+ {
+ if (entry.GlobalTGI.TypeID != TypeIDs.LOT_OBJECT)
+ {
+ continue;
+ }
+
+ try
+ {
+ var lotObject = entry.GetAsset();
+ var resource = contentProvider.GetAsset(
+ new ResourceKey(lotObject.Object.ResourceName + "_cres", GroupIDs.Scenegraph,
+ TypeIDs.SCENEGRAPH_CRES));
+ if (resource == null)
+ {
+ Debug.Log($"Could not find lot object: {lotObject.Object.ResourceName}");
+ continue;
+ }
+
+ var model = resource.CreateRootGameObject();
+ model.transform.GetChild(0).localPosition = lotObject.Object.Position;
+ model.transform.GetChild(0).localRotation = lotObject.Object.Rotation;
+
+ _lotObject.Add(model);
+ _testObjects.Add(model);
+ }
+ catch (Exception e)
+ {
+ Debug.LogException(e);
+ }
+ }
+ }
+ ///
+ /// Invalidates the specified group supplied by
+ ///
+ ///
+ void BaseInvalidateGroup(LotArchitecture.ArchitectureGameObjectTypes group)
+ {
+#if NEW_INVALIDATE
+ architecture.InvalidateComponent(group);
+ return;
+#endif
+ if (group == LotArchitecture.ArchitectureGameObjectTypes.roof)
+ {
+ Architecture.InvalidateComponent(group);
+ return;
+ }
+ //old
+ string objectName = group.ToString();
+ //destroy existing object
+ var groupParent = _lotObject.Where(x => x.name == objectName).FirstOrDefault();
+ if (groupParent != null)
+ { // clean walls
+ Destroy(groupParent);
+ _lotObject.Remove(groupParent);
+ }
+ //rebuild new mesh from memory
+ Architecture.CreateGameObjectsOfType(_lotObject, group);
+ }
+
+ public void InvalidateFloors()
+ {
+ BaseInvalidateGroup(LotArchitecture.ArchitectureGameObjectTypes.floor);
+ InvalidateTerrain();
+ }
+ public void InvalidateWalls() => BaseInvalidateGroup(LotArchitecture.ArchitectureGameObjectTypes.wall);
+ public void InvalidateTerrain() => BaseInvalidateGroup(LotArchitecture.ArchitectureGameObjectTypes.terrain);
+
+ ///
+ /// After changing the , use this method to update the visual
+ ///
+ public void InvalidateState()
+ {
+ Architecture.UpdateState(_state);
+ UpdateObjectVisibility(_state.Level);
+ }
+ ///
+ /// Sets the given object's visibility to be Visible/Invisible
+ ///
+ ///
+ ///
+ private void SetObjectVisiblity(GameObject obj, bool visible)
+ {
+ foreach (MeshRenderer renderer in obj.GetComponentsInChildren())
+ {
+ renderer.shadowCastingMode = visible ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly;
+ }
+
+ foreach (SkinnedMeshRenderer renderer in obj.GetComponentsInChildren())
+ {
+ renderer.shadowCastingMode = visible ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.ShadowsOnly;
+ }
+ }
+ ///
+ /// Sets objects to be visible based on if they should be seen by the player or not.
+ /// This uses the to determine up to which floor an object should be viewable.
+ ///
+ ///
+ private void UpdateObjectVisibility(int viewLevel)
+ {
+ foreach (GameObject obj in _testObjects)
+ {
+ int level = Architecture.GetLevelAt(obj.transform.GetChild(0).localPosition);
+
+ SetObjectVisiblity(obj, level < viewLevel);
+ }
+ }
+
+ ///
+ /// Regenerates the RoomMap from scratch. [BETA]
+ ///
+ internal void EvaluateRoomMap()
+ {
+ _wallController.GenerateAll(Architecture);
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/LotLoad.cs.meta b/Assets/Scripts/OpenTS2/Scenes/Lot/LotLoad.cs.meta
new file mode 100644
index 00000000..f1b1675a
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/LotLoad.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bfcccda4cb67ae846aeffc27284e6ded
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/LotTerrainComponent.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/LotTerrainComponent.cs
index ca6d0dd9..b5b0800d 100644
--- a/Assets/Scripts/OpenTS2/Scenes/Lot/LotTerrainComponent.cs
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/LotTerrainComponent.cs
@@ -47,9 +47,22 @@ public LotTerrainComponent CreateFromLotArchitecture(LotArchitecture architectur
BindMaterialAndTextures();
+ // Make Mesh Collider for terrain
+ MakeTerrainCollider();
+
return this;
}
+ ///
+ /// Adds a mesh collider matching the shape of the terrain to the current object
+ ///
+ ///
+ private void MakeTerrainCollider()
+ {
+ var comp = _terrain.Component.gameObject.AddComponent();
+ comp.isTrigger = false;
+ }
+
private (ScenegraphTextureAsset color, ScenegraphTextureAsset bump) LoadTexture(ContentManager contentManager, string name)
{
var result = (
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/LotWallComponent.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/LotWallComponent.cs
index 68689b62..8a23a26b 100644
--- a/Assets/Scripts/OpenTS2/Scenes/Lot/LotWallComponent.cs
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/LotWallComponent.cs
@@ -19,7 +19,7 @@ public class LotWallComponent : AssetReferenceComponent, IPatternMaterialConfigu
private const string ThicknessTexture = "wall_top";
private const string FallbackMaterial = "wall_wallboard";
private const float HalfWallHeight = 1f;
- private const float WallHeight = 3f;
+ public const float WallHeight = 3f;
private const float WallsDownHeight = 0.2f;
private const float YBias = 0.001f;
private const float Thickness = 0.075f;
@@ -65,6 +65,7 @@ public WallIntersection(WallGraphPositionEntry position)
private LotArchitecture _architecture;
private PatternMeshCollection _patterns;
+ private PatternDescriptor[] _loadedPatternDescriptions;
private Dictionary _intersections;
@@ -74,16 +75,21 @@ public LotWallComponent CreateFromLotArchitecture(LotArchitecture architecture)
{
_architecture = architecture;
- LoadPatterns();
- BuildWallIntersections();
- BuildWallMeshes();
- AddFencePosts();
+ Invalidate();
gameObject.transform.position = new Vector3(0, -YBias, 0);
return this;
}
+ public override void Invalidate()
+ {
+ LoadPatterns();
+ BuildWallIntersections();
+ BuildWallMeshes();
+ AddFencePosts();
+ }
+
private ScenegraphMaterialDefinitionAsset LoadMaterial(ContentManager contentManager, string name)
{
var material = contentManager.GetAsset(new ResourceKey($"{name}_txmt", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_TXMT));
@@ -104,8 +110,22 @@ private void LoadPatterns()
ushort highestId = patternMap.Count == 0 ? (ushort)0 : patternMap.Keys.Max();
PatternDescriptor[] patterns = new PatternDescriptor[highestId + 2];
+ bool changesMade = false;
+ bool previousStateExisting = _loadedPatternDescriptions != null;
+
foreach (StringMapEntry entry in patternMap.Values)
{
+ //Persistent data -- calls from Invalidate() will call this with data already loaded, in which case
+ //We can save time by not reloading the same loaded data
+ // TODO
+ if (previousStateExisting && _loadedPatternDescriptions.Length == patterns.Length &&
+ _loadedPatternDescriptions[entry.Id + 1].Name != null) // loaded
+ { // PatternDescriptor is a struct -- check the name see if it's null, if not, the pattern is loaded already
+ patterns[entry.Id + 1] = _loadedPatternDescriptions[entry.Id + 1];
+ continue;
+ }
+ changesMade = true;
+
string materialName;
if (uint.TryParse(entry.Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint guid))
{
@@ -142,11 +162,22 @@ private void LoadPatterns()
}
}
+ if (previousStateExisting)
+ {
+ if (!changesMade) return; // no changes to wall patterns since last load
+
+ patterns[0] = _loadedPatternDescriptions[0];
+ _loadedPatternDescriptions = patterns;
+ _patterns.UpdatePatterns(patterns);
+ return;
+ }
+
patterns[0] = new PatternDescriptor(
ThicknessTexture,
LoadMaterial(contentManager, ThicknessTexture).GetAsUnityMaterial()
);
+ _loadedPatternDescriptions = patterns;
_patterns = new PatternMeshCollection(gameObject, patterns, Array.Empty(), this, _architecture.WallGraphAll.Floors);
}
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshCollection.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshCollection.cs
index a02c2213..9e43e498 100644
--- a/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshCollection.cs
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshCollection.cs
@@ -54,6 +54,7 @@ public PatternMeshFloor GetFloor(int floor)
public void UpdatePatterns(PatternDescriptor[] patterns)
{
+ _patterns = patterns;
foreach (var floor in _floors)
{
floor?.UpdatePatterns(patterns);
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshFloor.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshFloor.cs
index a000d301..21d97757 100644
--- a/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshFloor.cs
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/PatternMeshFloor.cs
@@ -95,6 +95,7 @@ public PatternMesh Get(int id, int variantId = 0)
result = new PatternMesh(Object, descriptor.Name, descriptor.Material, _matConfig);
}
+ result.Object.AddComponent(); // add collider to floors
_patternMeshes[key] = result;
}
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/Room.meta b/Assets/Scripts/OpenTS2/Scenes/Lot/Room.meta
new file mode 100644
index 00000000..8c428f6d
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/Room.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ba84f982724998349b0988abe41c5515
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/Room/RuntimeWallController.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/Room/RuntimeWallController.cs
new file mode 100644
index 00000000..ffeaae71
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/Room/RuntimeWallController.cs
@@ -0,0 +1,427 @@
+// JDrocks450 11-22-2023 on GitHub
+
+using OpenTS2.Content.DBPF;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Unity.Plastic.Newtonsoft.Json;
+using UnityEngine;
+
+namespace OpenTS2.Scenes.Lot
+{
+ ///
+ /// BETA implementation.
+ /// Needs a lot of work to be shippable.
+ /// Will need to revisit this concept.
+ ///
+ internal class RuntimeWallController
+ {
+ [Serializable]
+ public struct GraphPlot
+ {
+ public GraphPlot(float x, float y)
+ {
+ X = x;
+ Y = y;
+ }
+ public float X { get; set; }
+ public float Y { get; set; }
+ }
+ [Serializable]
+ public struct GraphLine
+ {
+ public GraphLine(int fromID, int toID)
+ {
+ FromPlotID = fromID;
+ ToPlotID = toID;
+ }
+
+ ///
+ /// The this line originates
+ ///
+ public int FromPlotID { get; set; }
+ ///
+ /// The this line ends at
+ ///
+ public int ToPlotID { get; set; }
+
+ public override string ToString() => $"({FromPlotID} -> {ToPlotID})";
+ }
+
+ ///
+ /// A map of all positions in the WallGraph
+ /// For only points that are on line segments, see:
+ ///
+ public Dictionary Positions { get; private set; } = new Dictionary();
+ ///
+ /// that are at the corners of two connected line segments
+ ///
+ public HashSet JointPositions { get; private set; } = new HashSet();
+ ///
+ /// Lines between that form a straight wall of at least 1 unit length
+ ///
+ public Dictionary Lines { get; private set; } = new Dictionary();
+ ///
+ /// Groups of that all form one enclosed shape (Room) on the lineplot
+ ///
+ public Dictionary> Rooms { get; private set; } = new Dictionary>();
+
+ ///
+ /// A serialized RoomState -- the current state of the walls on the lot
+ ///
+ [Serializable]
+ public class RoomPackage
+ {
+ public Dictionary Positions;
+ public HashSet JointPositions;
+ public Dictionary Lines;
+ public Dictionary> Rooms;
+ public int Width, Height;
+ }
+ ///
+ /// Simple distance formula, calculates the distance in units between two coordinates
+ ///
+ ///
+ ///
+ ///
+ double Distance(Vector2 p1, Vector2 p2)
+ {
+ return Math.Sqrt(Math.Pow(p1.x - p2.x, 2) + Math.Pow(p1.y - p2.y, 2));
+ }
+ ///
+ /// Determines if three points create a triangle, can be used to detect straight walls versus joints
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool IsTriangle(Vector2 p1, Vector2 p2, Vector2 p3)
+ {
+ // Calculate the lengths of the sides
+ double a = Distance(p1, p2);
+ double b = Distance(p2, p3);
+ double c = Distance(p3, p1);
+
+ // Check if the triangle inequality theorem holds for all sides
+ return a + b > c && b + c > a && c + a > b;
+ }
+ ///
+ /// Regenerates the room map by discarding the previous data
+ ///
+ ///
+ public void GenerateAll(LotArchitecture architecture)
+ {
+ var graph = architecture.WallGraphAll;
+ Dictionary positionsData = graph.Positions;
+ WallGraphLineEntry[] wallData = graph.Lines;
+
+ if (!wallData.Any()) return;
+ if (!positionsData.Any()) return;
+
+ System.Diagnostics.Stopwatch debugTimer = new System.Diagnostics.Stopwatch();
+ debugTimer.Start();
+
+ GenerateLineGraph(positionsData, wallData);
+ long genLineGraph = debugTimer.ElapsedMilliseconds;
+ debugTimer.Restart();
+
+ GenerateRoomMap();
+ long genRoomMap = debugTimer.ElapsedMilliseconds;
+ debugTimer.Restart();
+
+ PackageRoomMap(graph.Width, graph.Height);
+ long genPkg = debugTimer.ElapsedMilliseconds;
+ debugTimer.Stop();
+
+ long tTime = genLineGraph + genRoomMap + genPkg;
+
+ UnityEngine.Debug.Log($"RoomMap(): Lines took {genLineGraph}ms, Rooms took {genRoomMap}ms, Package took {genPkg}ms" +
+ $" and altogether took {tTime}ms. ");
+ //GenerateImage(positionsData, graph.Width, graph.Height);
+ }
+
+ private void PackageRoomMap(int Width, int Height)
+ {
+ //Serialize this to file
+ using (StringWriter strJson = new StringWriter())
+ {
+ JsonSerializer.CreateDefault().Serialize(strJson, new RoomPackage()
+ {
+ JointPositions = JointPositions,
+ Lines = Lines,
+ Positions = Positions,
+ Rooms = Rooms,
+ Width = Width,
+ Height = Height
+ }, typeof(RoomPackage));
+ }
+ }
+
+ ///
+ /// This function takes the wall graph from package and processes it into a lineplot
+ /// of only significant points -- as in points that form corners to build shapes.
+ /// This function will also create the network of lines between the points.
+ ///
+ ///
+ ///
+ private void GenerateLineGraph(Dictionary positionsData, WallGraphLineEntry[] wallData)
+ {
+ //build a map of points that are referenced by lines.
+ //Position ID to List of LineIDs in WallGraph
+ Dictionary> PositionIDReferences = new Dictionary>();
+
+ void AddPositionReference(int PositionID, int LayerID)
+ {
+ //Check if the point exists in our wall graph
+ if (!positionsData.ContainsKey(PositionID)) return;
+ //position existing
+ if (PositionIDReferences.TryGetValue(PositionID, out HashSet references))
+ references.Add(LayerID);
+ else
+ {
+ PositionIDReferences.Add(PositionID, new HashSet() { LayerID });
+ if (!Positions.ContainsKey(PositionID))
+ Positions.Add(PositionID, new GraphPlot(positionsData[PositionID].XPos,
+ positionsData[PositionID].YPos));
+ }
+ }
+
+ //find all points that are referenced by wall lines
+ foreach (var wall in wallData)
+ {
+ AddPositionReference(wall.FromId, wall.LayerId);
+ AddPositionReference(wall.ToId, wall.LayerId);
+ }
+
+ //map lines to their layerIds
+ Dictionary lineMap = new Dictionary();
+ foreach (var line in wallData)
+ lineMap.Add(line.LayerId, line);
+
+ List processed = new List();
+ HashSet WallLayerIDsOnLine = new HashSet();
+ Lines.Clear();
+
+ //filter out positions that form straight lines to get a
+ //lineplot of all significant points -- e.g. the ones that form
+ //corners
+ //--
+ //This is done by taking two connected wall lines and seeing if all three points
+ //on these two lines form a triangle with any area -- if they do, it's a bend
+ foreach (var positionItem in PositionIDReferences)
+ {
+ if (positionItem.Value.Count <= 1) // 1 or less connections can never form a wall
+ continue; // this is due to them being open-ended
+ else if (positionItem.Value.Count == 2 && processed.Contains(positionItem.Key))
+ continue; // only two connections means it's already been processed and cannot be anything more
+
+ int lineFrom = -1, lineTo = -1;
+
+ //tunnelling function -- watch out for stack overflow by using cautiously
+ //keep scope to only one level on the lot at a time, and this function tree will
+ //end as soon as a line segment is found
+ // -- todo: set limit on tunnelling level to be diagonal line from lot corner to corner
+ void Try(int meID, int tunneled)
+ {
+ if (JointPositions.Contains(meID))
+ { // already a joint -- don't bother with the detection
+ lineTo = meID;
+ return;
+ }
+ var references = PositionIDReferences[meID]; // find all wall lines that contain this point
+ if (references.Count <= 1) goto exit; // none references!!
+ if (references.Count > 2)
+ {
+ JointPositions.Add(meID);
+ lineTo = meID;
+ if (tunneled == 0)
+ return;
+ }
+ //very bad -- change ASAP
+ WallGraphLineEntry line1 = lineMap[references.ElementAt(0)];
+ WallGraphLineEntry line2 = lineMap[references.ElementAt(1)];
+ int other1 = line1.ToId == meID ? line1.FromId : line1.ToId; // which end isn't the current point?
+ if (other1 == meID)
+ goto exit; // catastropic error -- both ends are the same and also this point... how would this happen?
+ int other2 = line2.ToId == meID ? line2.FromId : line2.ToId; // which end isn't the current point or other point?
+ if (other2 == meID)
+ goto exit; // catastropic error -- see above
+ if (other1 == other2)
+ goto exit; // catastropic error -- see above
+
+ //form a triangle using all three points
+ Vector2 pos1 = new Vector2(positionsData[other1].XPos, positionsData[other1].YPos);
+ Vector2 pos2 = new Vector2(positionsData[other2].XPos, positionsData[other2].YPos);
+ Vector2 center = new Vector2(positionsData[meID].XPos, positionsData[meID].YPos);
+
+ //check if this is a bend -- has an area
+ if (!IsTriangle(center, pos1, pos2))
+ {
+ processed.Add(meID); // point is spent
+ int next = processed.Contains(other2) ? other1 : other2; // pick the point we haven't visited yet
+ if (lineFrom == -1)
+ lineFrom = next == other2 ? other1 : other2; // from point is the origin point
+ Try(next, tunneled + 1); // tunnel until bend is found to complete the segment
+ return;
+ }
+ //found a bend ... close and complete this tunnel
+ JointPositions.Add(meID);
+ lineTo = meID;
+ return;
+ exit:;
+ }
+
+ lineFrom = lineTo = -1;
+ //begin -- walk the current line until it finds a bend
+ //adds what it finds to Lines if it is indeed a line
+ //also adds to Joints collection
+ Try(positionItem.Key, 0);
+
+ if (lineFrom != -1 && lineTo != -1 && lineFrom != lineTo)
+ Lines.Add(Lines.Count + 1, new GraphLine(lineFrom, lineTo));
+ }
+ }
+
+ ///
+ /// This function will walk the lineplot created during
+ /// finding lines that connect to one another to form shapes making up enclosed spaces.
+ ///
+ private void GenerateRoomMap()
+ {
+ IDictionary> dirtyRooms = new Dictionary>();
+ Stack currentlyVisitingRoomPositions = new Stack();
+
+ foreach (var line in Lines)
+ {
+ int findingPositionID = line.Value.FromPlotID;
+ bool FindRoom(int currentLineID, int fromPositionID)
+ {
+ currentlyVisitingRoomPositions.Push(currentLineID);
+ var lineValue = Lines[currentLineID];
+ var nextPoint = lineValue.ToPlotID != fromPositionID ? lineValue.ToPlotID : lineValue.FromPlotID;
+ if (nextPoint == findingPositionID)
+ {
+ //found a room
+ int[] points = currentlyVisitingRoomPositions.ToArray();
+ //ensure room is valid
+ //AssertRoomValid(points);
+ //does this room already exist -- also calls AssertRoomValid()
+ if (!RoomExisting(-1, dirtyRooms, points))
+ dirtyRooms.Add(dirtyRooms.Count + 1, points); // doesn't, add it
+ return true;
+ }
+ var results = Lines.Where(x => x.Value.FromPlotID == nextPoint || x.Value.ToPlotID == nextPoint);
+ int nextLineID = -1;
+ foreach (var result in results)
+ {
+ if (result.Key == currentLineID) continue;
+ nextLineID = result.Key;
+ }
+ if (nextLineID == -1) return false;
+ return FindRoom(nextLineID, nextPoint);
+ }
+ currentlyVisitingRoomPositions.Clear();
+ FindRoom(line.Key, findingPositionID);
+ }
+
+ Rooms.Clear();
+ foreach(var item in dirtyRooms)
+ Rooms.Add(item.Key, item.Value);
+ }
+
+ ///
+ /// Assures the supplied set of points can make a valid room.
+ ///
+ ///
+ ///
+ ///
+ public void AssertRoomValid(params int[] RoomPoints)
+ {
+ if (RoomPoints == default)
+ throw new NullReferenceException("RoomPoints passed as null reference.");
+ if (RoomPoints.Length < 4)
+ throw new InvalidDataException($"Rooms must have at least four points. This one supplied has {RoomPoints.Length}");
+ }
+
+ ///
+ /// Tests to see if the given room exists in
+ /// Supplying the RoomID will ensure the current room isn't compared to itself in the map.
+ /// It is determined to be the same room if all the points supplied match all the points on a given
+ /// Room in the room map.
+ ///
+ ///
+ ///
+ ///
+ public bool RoomExisting(int RoomID, params int[] RoomPoints) => RoomExisting(RoomID, Rooms, RoomPoints);
+
+ ///
+ /// Tests to see if the given room exists in
+ /// Supplying the RoomID will ensure the current room isn't compared to itself in the map.
+ /// It is determined to be the same room if all the points supplied match all the points on a given
+ /// Room in the room map.
+ ///
+ ///
+ ///
+ ///
+ public bool RoomExisting(int RoomID, IDictionary> RoomCollection, params int[] RoomPoints)
+ {
+ AssertRoomValid(RoomPoints);
+ bool duplicate = false;
+ foreach (var otherRoom in RoomCollection)
+ {
+ if (otherRoom.Key == RoomID)
+ continue; // same as the room I'm comparing
+ if (otherRoom.Value.Count() != RoomPoints.Length)
+ continue; // not the same number of points
+ foreach (var point in otherRoom.Value)
+ {
+ duplicate = RoomPoints.Contains(point);
+ if (!duplicate) break;
+ }
+ if (duplicate) break;
+ }
+ return duplicate;
+ }
+
+ public void GenerateImage(string exportFileName, Dictionary positionsData, int Width, int Height)
+ {
+ Texture2D roomMap = new Texture2D(Width, Height);
+
+ Color[] colors = new Color[] {
+ Color.green,
+ Color.cyan,
+ Color.magenta,
+ Color.grey,
+ Color.yellow,
+ Color.blue
+ };
+
+ foreach (var drawLine in Lines.Values)
+ {
+ var position = positionsData[drawLine.FromPlotID];
+ roomMap.SetPixel((int)position.XPos, (int)position.YPos, Color.red);
+ position = positionsData[drawLine.ToPlotID];
+ roomMap.SetPixel((int)position.XPos, (int)position.YPos, Color.red);
+ }
+
+ int index = -1;
+ foreach (var room in Rooms)
+ {
+ index++;
+
+ Color drawColor = colors[index];
+ foreach (var lineID in room.Value)
+ {
+ var drawLine = Lines[lineID];
+ var position = positionsData[drawLine.FromPlotID];
+ roomMap.SetPixel((int)position.XPos, (int)position.YPos, drawColor);
+ position = positionsData[drawLine.ToPlotID];
+ roomMap.SetPixel((int)position.XPos, (int)position.YPos, drawColor);
+ }
+ }
+
+ File.WriteAllBytes(exportFileName, roomMap.EncodeToPNG());
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/Room/RuntimeWallController.cs.meta b/Assets/Scripts/OpenTS2/Scenes/Lot/Room/RuntimeWallController.cs.meta
new file mode 100644
index 00000000..eae74992
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/Room/RuntimeWallController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 989c8164467e1524d9013a6579681d65
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/Scenes/Lot/State/WorldState.cs b/Assets/Scripts/OpenTS2/Scenes/Lot/State/WorldState.cs
index 6ba48144..dc6770ea 100644
--- a/Assets/Scripts/OpenTS2/Scenes/Lot/State/WorldState.cs
+++ b/Assets/Scripts/OpenTS2/Scenes/Lot/State/WorldState.cs
@@ -18,5 +18,10 @@ public WorldState(int level, WallsMode walls)
Level = level;
Walls = walls;
}
+
+ public override string ToString()
+ {
+ return $"Walls: {Walls}, Level: {Level}";
+ }
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/OpenTS2/UI/Layouts/Lot.meta b/Assets/Scripts/OpenTS2/UI/Layouts/Lot.meta
new file mode 100644
index 00000000..da027278
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/UI/Layouts/Lot.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 36c3b264b4416d742b336bab949d1995
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewBuildModeHUD.cs b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewBuildModeHUD.cs
new file mode 100644
index 00000000..7e2ed723
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewBuildModeHUD.cs
@@ -0,0 +1,631 @@
+using OpenTS2.Common;
+using OpenTS2.Content;
+using OpenTS2.Content.DBPF;
+using OpenTS2.Content.DBPF.Scenegraph;
+using OpenTS2.Engine.Modes.Build;
+using OpenTS2.Engine.Modes.Build.Tools;
+using OpenTS2.Files.Formats.DBPF;
+using OpenTS2.Files.Formats.XML;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace OpenTS2.UI.Layouts.Lot
+{
+ ///
+ /// This is a sub-control of the with functionality serving the Build Mode menus in the Lot View UI puck
+ ///
+ public class LotViewBuildModeHUD : UILayoutInstance
+ {
+ class BuildHUD_CatalogItemComponent : UILayoutInstance
+ {
+ //PRIVATE
+ const uint BaseID = 0xDAFE6503;
+ const uint PreviewID = 0x00000001;
+ private CatalogObjectAsset item;
+ public CatalogObjectAsset SelectedItem => item;
+
+ Material displayMaterial;
+
+ //PROTECTED
+ protected override ResourceKey UILayoutResourceKey => new ResourceKey(BaseID, 0xA99D8A11, TypeIDs.UI);
+
+ //PUBLIC
+ public event EventHandler Clicked;
+
+ public BuildHUD_CatalogItemComponent(Transform parent) : base(parent)
+ {
+ Init();
+ }
+
+ void Init()
+ {
+ Components[0].GetChildByID(0x03).OnClick += delegate
+ {
+ Clicked?.Invoke(this, SelectedItem);
+ };
+ }
+
+ ///
+ /// Changes this component's displayed texture and attached catalog asset
+ ///
+ ///
+ public void Display(CatalogObjectAsset Asset)
+ {
+ if(displayMaterial != null)
+ {
+ UnityEngine.Object.Destroy(displayMaterial);
+ displayMaterial = null;
+ }
+
+ item = Asset;
+
+ var content = ContentManager.Instance;
+ var material = content.GetAsset(
+ new ResourceKey($"{Asset.Material}_txmt", GroupIDs.Scenegraph, TypeIDs.SCENEGRAPH_TXMT));
+
+ var preview = Components[0].GetChildByID(PreviewID);
+ preview.RectTransformComponent.localPosition = new Vector3(8, -8);
+ preview.RectTransformComponent.sizeDelta = new Vector2(34,34);
+ preview.gameObject.SetActive(true);
+
+ //Set preview image
+ var bmp = preview.GetComponent();
+ displayMaterial = material.GetAsUnityMaterial();
+ bmp.texture = displayMaterial.mainTexture;
+ }
+ }
+
+ ///
+ /// Contains the state of the catelog UI flyout
+ ///
+ class BuildHUD_CatalogComponent
+ {
+ const int XMARGIN = 10, YMARGIN = 5; // Margins between catalog items in the menu
+ internal const uint CatalogItemsExtender = 0xAD502BBF;
+ const uint CatalogItemsComponent = 0x00001006;
+ const uint PreviousButton = 0x00001004;
+ const uint NextButton = 0x00001005;
+
+ UIComponent rootElement;
+ internal bool opened { get; private set; }
+
+ private string subsort = "all";
+ int itemsPerPage = 6, currentPage = 1;
+
+ List items = new List();
+ ///
+ /// The list of reusable UIComponent instances that will dynamically update their previews depending on which page we're on
+ ///
+ List controls = new List();
+
+ public BuildHUD_CatalogComponent(UIComponent CatalogComponent)
+ {
+ rootElement = CatalogComponent;
+
+ Init();
+ Close();
+ }
+
+ void Init()
+ {
+ var catalogItemsComponent = rootElement.GetChildByID(CatalogItemsComponent);
+ catalogItemsComponent.gameObject.SetActive(true);
+ catalogItemsComponent.GetComponent().enabled = false; // background is buggy turn it off
+
+ for(int item = 0; item < itemsPerPage; item++) // create dynamic items
+ {
+ int x = item / 2;
+ int y = item - (x * 2);
+
+ var control = new BuildHUD_CatalogItemComponent(catalogItemsComponent.transform);
+ var ctrlTransform = control.Components[0].RectTransformComponent;
+ ctrlTransform.localPosition = new Vector3( // handle padding here
+ (ctrlTransform.sizeDelta.x + XMARGIN) * x, -(ctrlTransform.sizeDelta.y + YMARGIN) * y);
+ controls.Add(control);
+
+ //CLICK EVENT
+ control.Clicked += CatalogItemSelected;
+ }
+
+ //Rig up next and prev buttons
+ var prevButton = rootElement.GetChildByID(PreviousButton);
+ prevButton.OnClick += Previous;
+ var nextButton = rootElement.GetChildByID(NextButton);
+ nextButton.OnClick += Advance;
+ }
+
+ private void CatalogItemSelected(object sender, CatalogObjectAsset e)
+ {
+ //If the current build tool is compatible with patterns -- invoke it
+ (BuildModeServer.Get().CurrentTool as IPatternSelectableBuildTool)?.SetPaintPattern(e.Material);
+ }
+
+ void Advance() => SwitchPage(currentPage + 1);
+ void Previous() => SwitchPage(currentPage - 1);
+ ///
+ /// Switches to the given page in the catalog by updating the dynamic controls to the new page's contents
+ ///
+ ///
+ void SwitchPage(int PageNumber)
+ {
+ void EnableButtons()
+ {
+ var button = rootElement.GetChildByID(PreviousButton);
+ button.gameObject.SetActive(true);
+ button = rootElement.GetChildByID(NextButton);
+ button.gameObject.SetActive(true);
+ }
+ void DisableForward()
+ {
+ var button = rootElement.GetChildByID(NextButton);
+ button.gameObject.SetActive(false);
+ }
+ void DisableBackward()
+ {
+ var button = rootElement.GetChildByID(PreviousButton);
+ button.gameObject.SetActive(false);
+ }
+
+ EnableButtons(); // any buttons that are disabled should be re-enabled for safety.
+
+ currentPage = PageNumber;
+ if (currentPage <= 0)
+ {
+ currentPage = 0;
+ DisableBackward(); // first page, disable back button
+ }
+
+ int startIndex = currentPage * itemsPerPage;
+
+ var itemsFilter = (IEnumerable)items;
+ if (subsort == "ots2_hiddentiles")
+ {
+ itemsFilter = items.Where(x => ((Uint32Prop)x.Properties.Properties["showincatalog"]).Value == 0);
+ }
+ else if (subsort != "all") // filter the itemssource if applicable (ignore this filter if "all" is selected)
+ itemsFilter = items.Where(x => ((StringProp)x.Properties.Properties["subsort"]).Value == subsort);
+
+ for (int item = 0; item < itemsPerPage; item++)
+ {
+ //Set buttons hidden at first until some content to show has been found
+ var control = controls[item];
+ control.Components[0].gameObject.SetActive(false);
+
+ //attempt to load the content item
+ var assetRef = itemsFilter.ElementAtOrDefault(startIndex + item);
+ if (assetRef == null) continue; // no content here
+ //found some content, make the button visible and display a preview of the catalog item
+ control.Components[0].gameObject.SetActive(true);
+ control.Display(assetRef);
+ }
+
+ if (startIndex + itemsPerPage >= itemsFilter.Count()) // end of last page
+ DisableForward(); // disable forward button
+ }
+
+ public void Close()
+ {
+ rootElement.gameObject.SetActive(false);
+ opened = false;
+ }
+ public void Open(string CatelogItemType, string Subsort = "all")
+ {
+ if (string.IsNullOrWhiteSpace(Subsort))
+ Subsort = "all";
+
+ rootElement.gameObject.SetActive(true);
+ opened = true;
+
+ //Update the selection of items to match the definition
+ subsort = Subsort;
+ UpdateItemsSource(CatalogManager.Instance.Objects.Where(x => x.Type == CatelogItemType));
+ }
+ public void SetLocalPosition(Vector3 Position) =>
+ rootElement.RectTransformComponent.localPosition = Position;
+
+ ///
+ /// Updates the catalog control's items source property.
+ /// Use this when changing the selection of items being shown in the catelog.
+ /// You do not need to account for multiple pages of items, that will be handled automatically by the control.
+ ///
+ void UpdateItemsSource(IEnumerable CatalogItems)
+ {
+ items.Clear();
+ items.AddRange(CatalogItems);
+
+ SwitchPage(0); // switch to page one of the new data
+ }
+
+ ///
+ /// Sets a filter on the ItemsSource.
+ /// See: and
+ ///
+ ///
+ ///
+ internal void SetSubsort(string Subsort)
+ {
+ subsort = Subsort;
+ SwitchPage(0);
+ }
+ }
+
+ #region TopLevelHUDElements
+
+ ///
+ /// Sorts (pages) for different tools in the build mode hud
+ ///
+ enum BuildHUD_Sorts : uint
+ {
+ TopLevel = 0x0000A000,
+ Walls = 0x0000B100,
+ Foundations = 0x0000B200,
+ DoorsWindowsArches = 0x0000B300,
+ Floors = 0x0000B400,
+ WallPaints = 0x0000B500,
+ Stairs = 0x0000B600,
+ Terrain = 0x0000B700,
+ GardenCenter = 0x0000B800,
+ Roofs = 0x0000B900,
+ Garages = 0x0000BB00,
+ Architecture = 0x0000BC00,
+ Misc = 0x0000BA00
+ }
+ enum BuildHUD_CollectionPages : uint
+ {
+ Normal = 0x000000A8
+ }
+ enum BuildHUD_BasicToolButtons : uint
+ {
+ Hand = 0x000000A5,
+ EyedropperTool = 0x000000A4
+ }
+
+ #endregion
+
+ #region Subsorts
+
+ enum BuildHUD_TopLevelButtons : uint
+ {
+ TopLevelSortButton = 0x000000A6,
+ WallsSortButton = 0x0000A100,
+ FoundationsSortButton = 0x0000A200,
+ DoorsAndWindowsSortButton = 0x0000A300,
+ FloorsSortButton = 0x0000A400,
+ WallPaintsSortButton = 0x0000A500,
+ StairsSortButton = 0x0000A600,
+ TerrainCenterButton = 0x0000A700,
+ GardenCenterButton = 0x0000A800,
+ RoofsSortButton = 0x0000A900,
+ MiscButton = 0x0000AA00,
+ GarageButton = 0x0000AB00,
+ ArchitectureSortButton = 0x0000AC00,
+ }
+ enum BuildHUD_WallsSortButtons : uint
+ {
+ WallsNRooms = 0x0000B120,
+ HalfWalls = 0x0000B130
+ }
+ enum BuildHUD_TerrainSortButtons : uint
+ {
+ ElevationTools = 0x0000B720,
+ TerrainPaints = 0x0000B730, // called 'grass' in UI, pretty cool :0
+ Water = 0x0000B740,
+ FlattenLot = 0x000000AD,
+ }
+ enum BuildHUD_TerrainBrushSizeButtons : uint
+ {
+ SmallSizeButton = 0x0000B751,
+ MedSizeButton = 0x0000B752,
+ LgSizeButton = 0x0000B753,
+ }
+ enum BuildHUD_FloorsSubsortButtons : uint
+ {
+ StoneButton = 0x0000B420,
+ BrickButton = 0x0000B430,
+ WoodButton = 0x0000B440,
+ CarpetButton = 0x0000B450,
+ TileButton = 0x0000B460,
+ LinoleumButton = 0x0000B470,
+ PouredButton = 0x0000B480,
+ OtherButton = 0x0000B490,
+ AllButton = 0x0000B4A0,
+ }
+
+ #endregion
+
+ const uint BaseID = 0x49060003;
+ const uint TileBG = 0xB4; // UI Background Panel
+ const uint EndCap = 0x000000B5; // UI Curvy End Cap
+ const uint ProdCatelogFlyout = 0x49063004;
+ const uint AutoRoofOptionsExtender = 0x000000AC;
+ const uint TerrainPageSizesGroup = 0x0000B750;
+
+ protected override ResourceKey UILayoutResourceKey => new ResourceKey(BaseID, 0xA99D8A11, TypeIDs.UI);
+ UIComponent rootElement => Components[0];
+
+ //Pages (Sorts)
+
+ ///
+ /// Contains information for each sort page in build mode to display it correctly
+ ///
+ class BuildHUDSort
+ {
+ ///
+ /// The enum type that has the IDs for the buttons that are inside of this sort that should be Toggled/Untoggled
+ /// after the sort is opened and dismissed
+ ///
+ public Type ButtonIDsEnum { get; set; } = null;
+ ///
+ /// The UI ID for this sort
+ ///
+ public uint SortGroupID { get; set; }
+
+ //CATALOG PROVIDER
+ public string CatalogItemType { get; set; }
+ public bool HasCatalogItems => CatalogItemType != null;
+ public string CatalogDefaultSubsort { get; set; } = "all";
+
+ public BuildHUDSort(Type buttonIDsEnum, uint sortGroupID)
+ {
+ ButtonIDsEnum = buttonIDsEnum;
+ SortGroupID = sortGroupID;
+ }
+ }
+
+ BuildHUDSort CurrentSort;
+ ///
+ /// Links Sort buttons to their corresponding pages in the interface
+ /// Wall Button links to the Wall Subpage with Half Walls, Regular Walls, Diagonal Room, etc.
+ ///
+ Dictionary sortPageMap = new Dictionary()
+ {
+ { BuildHUD_Sorts.TopLevel, new BuildHUDSort(typeof(BuildHUD_TopLevelButtons), (uint)BuildHUD_Sorts.TopLevel) },
+ { BuildHUD_Sorts.Walls, new BuildHUDSort(typeof(BuildHUD_WallsSortButtons), (uint)BuildHUD_Sorts.Walls) },
+ { BuildHUD_Sorts.Terrain, new BuildHUDSort(typeof(BuildHUD_TerrainSortButtons), (uint)BuildHUD_Sorts.Terrain) },
+ { BuildHUD_Sorts.Floors, new BuildHUDSort(typeof(BuildHUD_FloorsSubsortButtons), (uint)BuildHUD_Sorts.Floors)
+ { // activate the catelog page for this sort
+ CatalogItemType = CatalogObjectType.Floor,
+ }
+ },
+ };
+
+ // UI buttons and event system
+ Dictionary buildHUDButtonCallbacks; // built at runtime
+ ///
+ /// All UI buttons added to the event system (See: ) will set this value to
+ /// themselves prior to the event being called (May change later)
+ ///
+ UIButtonComponent CurrentButtonEventContext;
+ BuildHUD_CatalogComponent Catalog;
+
+ public bool CatalogOpen => Catalog.opened;
+
+ public LotViewBuildModeHUD() : this(MainCanvas)
+ {
+
+ }
+ public LotViewBuildModeHUD(Transform Canvas) : base(Canvas)
+ {
+ Init();
+ BuildUIEventsMap();
+ WireUpUIEvents();
+ }
+
+ void Init()
+ {
+ //Position in bot left
+ var root = Components[0];
+ root.SetAnchor(UIComponent.AnchorType.BottomLeft);
+ root.transform.position = new Vector3(105, root.transform.position.y, 0);
+
+ //fix sizing
+ var iconButton = root.GetChildByID((uint)BuildHUD_BasicToolButtons.Hand);
+ iconButton.RectTransformComponent.sizeDelta = new Vector2(30, 30);
+ iconButton = root.GetChildByID((uint)BuildHUD_BasicToolButtons.EyedropperTool);
+ iconButton.RectTransformComponent.sizeDelta = new Vector2(30, 30);
+
+ //turn off all controls except necessary ones
+ var autoRoofOptions = root.GetChildByID(AutoRoofOptionsExtender);
+ autoRoofOptions.gameObject.SetActive(false);
+ Catalog = new BuildHUD_CatalogComponent(root.GetChildByID(BuildHUD_CatalogComponent.CatalogItemsExtender));
+ }
+
+ void BuildUIEventsMap()
+ {
+ buildHUDButtonCallbacks = new Dictionary()
+ {
+ // BASIC BUTTONS
+ { (uint)BuildHUD_BasicToolButtons.Hand, delegate { ActionChangeTool(BuildTools.Hand); } },
+
+ // TOP LEVEL SORTS BUTTONS
+ { (uint)BuildHUD_TopLevelButtons.TopLevelSortButton, delegate { ActionChangeSort(BuildHUD_Sorts.TopLevel); } },
+ { (uint)BuildHUD_TopLevelButtons.WallsSortButton, delegate { ActionChangeSort(BuildHUD_Sorts.Walls); } },
+ { (uint)BuildHUD_TopLevelButtons.DoorsAndWindowsSortButton, delegate { ActionChangeSort(BuildHUD_Sorts.DoorsWindowsArches); } },
+ { (uint)BuildHUD_TopLevelButtons.FloorsSortButton, delegate
+ {
+ ActionChangeSort(BuildHUD_Sorts.Floors);
+ ActionChangeTool(BuildTools.Floor);
+ }
+ },
+ { (uint)BuildHUD_TopLevelButtons.WallPaintsSortButton, delegate { ActionChangeSort(BuildHUD_Sorts.WallPaints); } },
+ { (uint)BuildHUD_TopLevelButtons.ArchitectureSortButton, delegate { ActionChangeSort(BuildHUD_Sorts.Architecture); } },
+ { (uint)BuildHUD_TopLevelButtons.GardenCenterButton, delegate { ActionChangeSort(BuildHUD_Sorts.GardenCenter); } },
+ { (uint)BuildHUD_TopLevelButtons.TerrainCenterButton, delegate { ActionChangeSort(BuildHUD_Sorts.Terrain); } },
+ { (uint)BuildHUD_TopLevelButtons.GarageButton, delegate { ActionChangeSort(BuildHUD_Sorts.Garages); } },
+ { (uint)BuildHUD_TopLevelButtons.MiscButton, delegate { ActionChangeSort(BuildHUD_Sorts.Misc); } },
+ { (uint)BuildHUD_TopLevelButtons.RoofsSortButton, delegate { ActionChangeSort(BuildHUD_Sorts.Roofs); } },
+ { (uint)BuildHUD_TopLevelButtons.FoundationsSortButton, delegate {
+ ActionChangeSort(BuildHUD_Sorts.Foundations);
+ ActionChangeTool(BuildTools.Foundation);
+ }
+ },
+ { (uint)BuildHUD_TopLevelButtons.StairsSortButton, delegate { ActionChangeSort(BuildHUD_Sorts.Stairs); } },
+
+ // Walls N' Rooms
+ { (uint)BuildHUD_WallsSortButtons.WallsNRooms, delegate { ActionChangeTool(BuildTools.Wall); } },
+
+ // Terrain
+ { (uint)BuildHUD_TerrainSortButtons.ElevationTools, delegate { ActionSetTerrainBrushTool(BuildModeServer.TerrainModificationModes.Raise); } },
+ { (uint)BuildHUD_TerrainSortButtons.Water, delegate { ActionSetTerrainBrushTool(BuildModeServer.TerrainModificationModes.Water); } },
+ { (uint)BuildHUD_TerrainSortButtons.FlattenLot, delegate { BuildModeServer.Get().FlattenLot(); } },
+ { (uint)BuildHUD_TerrainBrushSizeButtons.SmallSizeButton, delegate { ActionModifyTerrainBrushSize(0); } },
+ { (uint)BuildHUD_TerrainBrushSizeButtons.MedSizeButton, delegate { ActionModifyTerrainBrushSize(TerrainBrushTool.TerrainBrushSizes.Medium); } },
+ { (uint)BuildHUD_TerrainBrushSizeButtons.LgSizeButton, delegate { ActionModifyTerrainBrushSize(TerrainBrushTool.TerrainBrushSizes.Large); } },
+
+ //Floors
+ { (uint)BuildHUD_FloorsSubsortButtons.StoneButton, delegate { ActionInvokeSubSort("stone"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.BrickButton, delegate { ActionInvokeSubSort("brick"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.WoodButton, delegate { ActionInvokeSubSort("wood"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.PouredButton, delegate { ActionInvokeSubSort("poured"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.CarpetButton, delegate { ActionInvokeSubSort("carpet"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.OtherButton, delegate { ActionInvokeSubSort("other"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.AllButton, delegate { ActionInvokeSubSort("all"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.TileButton, delegate { ActionInvokeSubSort("tile"); } },
+ { (uint)BuildHUD_FloorsSubsortButtons.LinoleumButton, delegate { ActionInvokeSubSort("lino"); } },
+ };
+ }
+
+ void WireUpUIEvents()
+ {
+ foreach(var keyValuePair in buildHUDButtonCallbacks)
+ {
+ uint compID = keyValuePair.Key;
+ var button = rootElement.GetChildByID(compID);
+ if (!button)
+ {
+ Debug.Log($"No button for 0x{compID:X}");
+ continue;
+ }
+ button.OnClick += delegate
+ {
+ //Set context
+ CurrentButtonEventContext = button;
+ //Invoke event
+ keyValuePair.Value();
+ //Remove context
+ CurrentButtonEventContext = null;
+ };
+ }
+ }
+ void EnsureGroupToggled(Type EnumTypeGroup, bool ToggleState = false) => EnsureGroup(EnumTypeGroup, (UIButtonComponent comp) => comp.SetToggled(ToggleState));
+ void EnsureGroupHidden(Type EnumUIGroup) => EnsureGroup(EnumUIGroup, (UIComponent comp) => comp.gameObject.SetActive(false));
+ void EnsureGroup(Type EnumUIGroup, Action Modification) => EnsureGroup(EnumUIGroup, Modification);
+ void EnsureGroup(Type EnumUIGroup, Action Modification) where T : UIComponent
+ {
+ if (EnumUIGroup == null) return;
+ foreach (uint groupID in Enum.GetValues(EnumUIGroup))
+ Modification(rootElement.GetChildByID(groupID));
+ }
+ void CloseCatalog()
+ {
+ Catalog.Close();
+ //Set the endcap visible again
+ rootElement.GetChildByID(EndCap).gameObject.SetActive(true);
+ }
+ void OpenCatalog(string ObjectType, string Subsort = "all")
+ {
+ Catalog.Open(ObjectType, Subsort);
+ //Set the endcap invisible
+ rootElement.GetChildByID(EndCap).gameObject.SetActive(false);
+ }
+
+ void ActionChangeTool(BuildTools Tool)
+ {
+ //Untoggle all basic buttons before transitioning
+ EnsureGroupToggled(typeof(BuildHUD_BasicToolButtons));
+ if (CurrentSort != default)
+ EnsureGroupToggled(CurrentSort.ButtonIDsEnum);
+ //set tool button toggled
+ if (CurrentButtonEventContext != default)
+ CurrentButtonEventContext.SetToggled(true);
+
+ BuildModeServer.Get().ChangeTool(Tool);
+ }
+ ///
+ /// Updates the User Interface to show the selected sort
+ /// See: to change the Subsort for this Sort
+ /// Note: This will cause whatever tool is currently being used to be cancelled.
+ ///
+ ///
+ void ActionChangeSort(BuildHUD_Sorts SortPage)
+ {
+ ActionChangeTool(BuildTools.None); // drop current tool
+
+ //Hide all other pages before transitioning
+ EnsureGroupHidden(typeof(BuildHUD_Sorts));
+ CloseCatalog();
+
+ var sortRoot = rootElement.GetChildByID((uint)SortPage);
+ sortRoot.gameObject.SetActive(true);
+ // stretch the ui panel to accomodate for the page we just opened up
+ float desiredWidth = sortRoot.RectTransformComponent.sizeDelta.x;
+ var TileBGComponent = rootElement.GetChildByID(TileBG).RectTransformComponent; // UI background panel
+ TileBGComponent.sizeDelta = new Vector2(desiredWidth, TileBGComponent.sizeDelta.y);
+ //move the endcap over so there's no gaps
+ var EndCapComp = rootElement.GetChildByID(EndCap).RectTransformComponent; // UI background panel
+ EndCapComp.localPosition = TileBGComponent.localPosition + new Vector3(desiredWidth, 0, 0);
+ //move the catelog as well -- whether it is active or not
+ Catalog.SetLocalPosition(new Vector3(TileBGComponent.localPosition.x + desiredWidth, 0, 0));
+ //catelogComp.sizeDelta = new Vector2(catelogComp.position.x - (MainCanvas.transform as RectTransform).sizeDelta.x - 10,
+ // catelogComp.sizeDelta.y);
+
+ if (sortPageMap.TryGetValue(SortPage, out var map))
+ CurrentSort = map;
+ //Untoggle all buttons in the destination sort as well
+ EnsureGroupToggled(CurrentSort.ButtonIDsEnum);
+
+ //open the catelog (if applicable)
+ if (CurrentSort.HasCatalogItems)
+ OpenCatalog(CurrentSort.CatalogItemType, CurrentSort.CatalogDefaultSubsort);
+ }
+
+ ///
+ /// Sorts such as Flooring have subsorts (carpet, stone, etc.), this function will update the
+ /// subsort for the
+ ///
+ ///
+ void ActionInvokeSubSort(string Subsort)
+ {
+ EnsureGroupToggled(CurrentSort.ButtonIDsEnum);
+ if (Subsort == "all" && Input.GetKey(KeyCode.LeftShift)) // BuildDEBUG
+ Subsort = "ots2_hiddentiles";
+ Catalog.SetSubsort(Subsort);
+ CurrentButtonEventContext.SetToggled(true);
+ }
+ ///
+ /// Invokes the Terrain brush tool and sets the size to Small by default
+ ///
+ ///
+ void ActionSetTerrainBrushTool(BuildModeServer.TerrainModificationModes BrushMode)
+ {
+ ActionChangeTool(BuildTools.TerrainBrush);
+ (BuildModeServer.Get().CurrentTool as TerrainBrushTool).CurrentBrushMode = BrushMode;
+ ActionModifyTerrainBrushSize(TerrainBrushTool.TerrainBrushSizes.Small);
+ }
+ ///
+ /// Updates the User Interface and the Terrain tool to match the selected size
+ ///
+ ///
+ void ActionModifyTerrainBrushSize(TerrainBrushTool.TerrainBrushSizes Size)
+ {
+ EnsureGroupToggled(typeof(BuildHUD_TerrainBrushSizeButtons));
+ UIButtonComponent button = rootElement.GetChildByID((uint)BuildHUD_TerrainBrushSizeButtons.SmallSizeButton);
+ switch (Size)
+ {
+ case TerrainBrushTool.TerrainBrushSizes.Medium: // med
+ button = rootElement.GetChildByID((uint)BuildHUD_TerrainBrushSizeButtons.MedSizeButton);
+ break;
+ case TerrainBrushTool.TerrainBrushSizes.Large: // lg
+ button = rootElement.GetChildByID((uint)BuildHUD_TerrainBrushSizeButtons.LgSizeButton);
+ break;
+ default:
+ Size = TerrainBrushTool.TerrainBrushSizes.Small; break;
+ }
+ button.SetToggled(true);
+ (BuildModeServer.Get().CurrentTool as TerrainBrushTool).BrushSize = (int)Size;
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewBuildModeHUD.cs.meta b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewBuildModeHUD.cs.meta
new file mode 100644
index 00000000..7615775c
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewBuildModeHUD.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 81a357d2aeaa898479699bb2890f9123
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUD.cs b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUD.cs
new file mode 100644
index 00000000..29b0337a
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUD.cs
@@ -0,0 +1,182 @@
+using OpenTS2.Common;
+using OpenTS2.Engine.Modes.Build;
+using OpenTS2.Files.Formats.DBPF;
+using OpenTS2.Scenes.Lot.State;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace OpenTS2.UI.Layouts
+{
+
+ ///
+ /// The HUD found when the user is playing in a lot
+ ///
+ public class LotViewHUD : UILayoutInstance
+ {
+ //CONSTANTS
+ public enum HUD_UILotModes
+ {
+ None,
+ Live,
+ Buy,
+ Build,
+ Memories,
+ Settings
+ }
+
+ const uint WallViewSelectorGadgetID = 0x00004000;
+ ///
+ /// probably not necessary since it's the only control in this UIData but im gonna roll w it
+ ///
+ const uint LotViewPuckID = 0xF15C6B85;
+ //Wall View button that invokes the Wall View Selector has 4 states for each wall view mode
+ const uint FoldButton_WallsDown = 0x00001100, FoldButton_WallsCutaway = 0x00001101,
+ FoldButton_WallsUp = 0x00001102, FoldButton_Roof = 0x00001103;
+ //Buttons inside the Wall View selector that change the Wall View Mode
+ const uint Button_WallsDown = 0x00000001, Button_WallsCutaway = 0x00000002,
+ Button_WallsUp = 0x00000003, Button_WallsRoof = 0x00000004;
+ const uint BuildButton = 0x77D97B28, BuyButton = 0x67D97B2A, LiveButton = 0x37D97B25, SettingsButton = 0x67D97B2F;
+ const uint FloorUpButton = 0x00002001, FloorDownButton = 0x00002000;
+
+ //PRIVATE
+ UIComponent WallsUpDownSelector;
+ UIComponent rootElement => Components[0];
+
+ HUD_UILotModes CurrentMode = HUD_UILotModes.None;
+
+ //EVENTS
+ public class ModeChangeEventArgs
+ {
+ public bool Allow = false;
+ HUD_UILotModes RequestedMode;
+
+ public ModeChangeEventArgs(HUD_UILotModes requestedMode)
+ {
+ RequestedMode = requestedMode;
+ }
+ }
+ public event EventHandler OnModeChangeRequested;
+ public event EventHandler OnModeChanged;
+
+ protected override ResourceKey UILayoutResourceKey => new ResourceKey(0x4905F85B, 0xA99D8A11, TypeIDs.UI);
+ public LotViewHUD() : this(MainCanvas)
+ {
+
+ }
+ public LotViewHUD(Transform Canvas) : base(Canvas)
+ {
+ Init();
+ WireUpUIEvents();
+ }
+
+ void Toggle(UIComponent comp) => comp.transform.gameObject.SetActive(!comp.transform.gameObject.activeSelf);
+
+ ///
+ /// Sets the hud elements to default settings for first run
+ ///
+ void Init()
+ {
+ var root = Components[0];
+ root.SetAnchor(UIComponent.AnchorType.Stretch);
+ var puck = root.GetChildByID(LotViewPuckID);
+ puck.SetAnchor(UIComponent.AnchorType.BottomLeft); // set gizmo to botleft
+ puck.transform.position = new Vector3(0, 200, 0);
+
+ WallsUpDownSelector = root.GetChildByID(WallViewSelectorGadgetID, false);
+ Toggle(WallsUpDownSelector);
+ }
+
+ ///
+ /// Sets up all button click event messages
+ ///
+ void WireUpUIEvents()
+ {
+ void RigUpSequential(uint Base, int count, Action Callback)
+ {
+ if (count < 0) return; // :|
+ if (Base + count > uint.MaxValue) throw new Exception($"UI Element ID overflow in WireUpUIEvents RigUpSeq method! {Base} + {count}");
+ for (int i = 0; i < count; i++)
+ {
+ var button = rootElement.GetChildByID((uint)(Base + i)); // downcast doesn't matter per above
+ button.OnClick += Callback;
+ }
+ }
+ //Rig up Wall View Mode tray thing
+ RigUpSequential(FoldButton_WallsDown, 4, ShowHideWallViewOptions);
+ var modeButton = rootElement.GetChildByID(BuildButton);
+ modeButton.OnClick += delegate { SetMode(HUD_UILotModes.Build); }; // build mode button clicked
+
+ //Wall view modes buttons
+ var wallButton = rootElement.GetChildByID(Button_WallsDown);
+ wallButton.OnClick += delegate { SetWallsCutawayMode(WallsMode.Down); };
+ wallButton = rootElement.GetChildByID(Button_WallsCutaway);
+ wallButton.OnClick += delegate { SetWallsCutawayMode(WallsMode.Cutaway); };
+ wallButton = rootElement.GetChildByID(Button_WallsUp);
+ wallButton.OnClick += delegate { SetWallsCutawayMode(WallsMode.Up); };
+ wallButton = rootElement.GetChildByID(Button_WallsRoof);
+ wallButton.OnClick += delegate { SetWallsCutawayMode(WallsMode.Roof); };
+
+ //floor buttons
+ var floorButton = rootElement.GetChildByID(FloorUpButton);
+ floorButton.OnClick += delegate { SetFloorLevel(1); };
+ floorButton = rootElement.GetChildByID(FloorDownButton);
+ floorButton.OnClick += delegate { SetFloorLevel(-1); };
+ }
+
+ void ShowHideWallViewOptions()
+ {
+ Toggle(WallsUpDownSelector);
+ }
+
+ void SetWallsCutawayMode(WallsMode Mode)
+ {
+ var server = BuildModeServer.Get();
+ var state = server.WorldState;
+ server.WorldState = new WorldState(state.Level, Mode);
+ server.InvalidateLotState();
+ ShowHideWallViewOptions();
+ }
+
+ void SetFloorLevel(int Change)
+ {
+ var server = BuildModeServer.Get();
+ var state = server.WorldState;
+ int level = Math.Max(0,state.Level + Change); // TODO: Make this base floor value...
+ server.WorldState = new WorldState(level, state.Walls);
+ server.InvalidateLotState();
+ }
+
+ ///
+ /// Sets the mode shown in the HUD
+ /// Note: If there are no subscribers to , the mode change will always be successful without verification.
+ ///
+ ///
+ public void SetMode(HUD_UILotModes Mode)
+ {
+ if (OnModeChangeRequested != null)
+ {
+ var args = new ModeChangeEventArgs(Mode);
+ OnModeChangeRequested(this, args);
+ if (!args.Allow) return;
+ }
+ //Change allowed
+
+ CurrentMode = Mode;
+ var buildModeButton = rootElement.GetChildByID(BuildButton);
+ buildModeButton.SetToggled(false);
+
+ switch(Mode)
+ {
+ case HUD_UILotModes.Build:
+ buildModeButton.SetToggled(true);
+ break;
+ }
+
+ OnModeChanged?.Invoke(this, Mode);
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUD.cs.meta b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUD.cs.meta
new file mode 100644
index 00000000..b9dc79d2
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUD.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5b5d074308e95ce42b13b0c46dd9eaed
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUDController.cs b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUDController.cs
new file mode 100644
index 00000000..278e11aa
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUDController.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace OpenTS2.UI.Layouts.Lot
+{
+ public class LotViewHUDController : MonoBehaviour
+ {
+ //UI Parts
+ public LotViewBuildModeHUD BuildModePanel { get; private set; }
+ public LotViewHUD Puck { get; private set; }
+
+ void Start()
+ {
+ BuildModePanel = new LotViewBuildModeHUD();
+ Puck = new LotViewHUD();
+ Puck.OnModeChanged += GizmoModeChanged;
+ }
+
+ private void GizmoModeChanged(object sender, LotViewHUD.HUD_UILotModes e)
+ {
+ //TODO: Make UI react to mode changes ... for now not really necessary
+ }
+
+ void Update()
+ {
+
+ }
+ }
+}
diff --git a/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUDController.cs.meta b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUDController.cs.meta
new file mode 100644
index 00000000..895daa9a
--- /dev/null
+++ b/Assets/Scripts/OpenTS2/UI/Layouts/Lot/LotViewHUDController.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9727801f799a10a41875182ace337639
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: -10
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/OpenTS2/UI/UIButtonComponent.cs b/Assets/Scripts/OpenTS2/UI/UIButtonComponent.cs
index 4ade9a2b..714d4f8e 100644
--- a/Assets/Scripts/OpenTS2/UI/UIButtonComponent.cs
+++ b/Assets/Scripts/OpenTS2/UI/UIButtonComponent.cs
@@ -24,6 +24,7 @@ public class UIButtonComponent : UIComponent, IPointerEnterHandler, IPointerExit
public ResourceKey ClickSound;
private bool _hovering = false;
private bool _held = false;
+ private bool _toggled = false; // some buttons are toggle buttons -- adding this here to account for this scenario
private TextureAsset[] _textures;
public RawImage RawImageComponent => GetComponent();
public void SetTexture(TextureAsset texture)
@@ -52,6 +53,11 @@ void UpdateTexture()
RawImageComponent.texture = _textures[0].Texture;
else
{
+ if (_toggled) // Toggle Button texture here
+ {
+ RawImageComponent.texture = _textures[2].Texture;
+ return;
+ }
RawImageComponent.texture = _textures[1].Texture;
if (_hovering && !UIManager.AnyMouseButtonHeld)
RawImageComponent.texture = _textures[3].Texture;
@@ -99,5 +105,16 @@ public void OnPointerUp(PointerEventData eventData)
OnClick?.Invoke();
_held = false;
}
+
+ void Click()
+ {
+ OnClick?.Invoke();
+ }
+
+ public void SetToggled(bool Toggled)
+ {
+ _toggled = Toggled;
+ UpdateTexture();
+ }
}
}
diff --git a/Assets/Shaders/Lot/Billboard.shader b/Assets/Shaders/Lot/Billboard.shader
new file mode 100644
index 00000000..a100701b
--- /dev/null
+++ b/Assets/Shaders/Lot/Billboard.shader
@@ -0,0 +1,69 @@
+Shader "Unlit/Billboard"
+{
+ Properties
+ {
+ _MainTex ("Texture", 2D) = "white" {}
+ }
+ SubShader
+ {
+ Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "DisableBatching" = "True" }
+
+ ZWrite Off
+ Blend SrcAlpha OneMinusSrcAlpha
+
+ Pass
+ {
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ // make fog work
+ #pragma multi_compile_fog
+
+ #include "UnityCG.cginc"
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ float2 uv : TEXCOORD0;
+ };
+
+ struct v2f
+ {
+ float2 uv : TEXCOORD0;
+ UNITY_FOG_COORDS(1)
+ float4 pos : SV_POSITION;
+ };
+
+ sampler2D _MainTex;
+ float4 _MainTex_ST;
+
+ v2f vert (appdata v)
+ {
+ v2f o;
+ o.pos = UnityObjectToClipPos(v.vertex);
+ o.uv = v.uv.xy;
+
+ // billboard mesh towards camera
+ float3 vpos = mul((float3x3)unity_ObjectToWorld, v.vertex.xyz);
+ float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23, 1);
+ float4 viewPos = mul(UNITY_MATRIX_V, worldCoord) + float4(vpos, 0);
+ float4 outPos = mul(UNITY_MATRIX_P, viewPos);
+
+ o.pos = outPos;
+
+ UNITY_TRANSFER_FOG(o,o.vertex);
+ return o;
+ }
+
+ fixed4 frag (v2f i) : SV_Target
+ {
+ // sample the texture
+ fixed4 col = tex2D(_MainTex, i.uv);
+ // apply fog
+ UNITY_APPLY_FOG(i.fogCoord, col);
+ return col;
+ }
+ ENDCG
+ }
+ }
+}
diff --git a/Assets/Shaders/Lot/Billboard.shader.meta b/Assets/Shaders/Lot/Billboard.shader.meta
new file mode 100644
index 00000000..775a27a2
--- /dev/null
+++ b/Assets/Shaders/Lot/Billboard.shader.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 9027c75630615624b8d7e4b02852aa4e
+ShaderImporter:
+ externalObjects: {}
+ defaultTextures: []
+ nonModifiableTextures: []
+ preprocessorOverride: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant: