SceneLoadingUtility.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.Formats.Tar;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Microsoft.CSharp.RuntimeBinder;
using Sandbox;
using Sandbox.ActionGraphs;
namespace SceneLoading
{
	public class SceneLoadingUtility
	{
		public static void LoadScene( SceneFile sceneFile, SceneLoadingResource sceneLoadingResource )
		{
			var objects = sceneFile.GameObjects;
			Game.ActiveScene.Load( new SceneFile() );

			foreach ( var obj in objects )
			{
				var gameObject = Game.ActiveScene.CreateObject();
				gameObject.Deserialize( obj );
			}

			foreach ( var clone in sceneLoadingResource.SceneLoadingClasses )
			{
				bool gameObjectSpawned = false;

				foreach ( var componentType in clone.ComponentTypes )
				{

					if ( clone.Flags == LoadingFlags.CheckForComponents && Game.ActiveScene.Components.GetAll( componentType, FindMode.EverythingInSelfAndDescendants ).Count() > 0 )
					{
						Log.Info( "Component found, skipping" );
						continue;
					}
					else if ( clone.Flags == LoadingFlags.DestroyFirst && Game.ActiveScene.Components.GetAll( componentType, FindMode.EverythingInSelfAndDescendants ).Count() > 0 )
					{
						Log.Info( "Component found, replacing" );
						Game.ActiveScene.Components.GetAll( componentType, FindMode.EverythingInSelfAndDescendants ).FirstOrDefault()?.GameObject.Destroy();
						if ( gameObjectSpawned ) return;
						var gb = clone.Prefab.Clone();
						gb.BreakFromPrefab();
						if ( clone.NetworkSpawn ) gb.NetworkSpawn( null );
						gameObjectSpawned = true;
						continue;
					}
					else if ( clone.Flags == LoadingFlags.DestroyAll && Game.ActiveScene.Components.GetAll( componentType, FindMode.EverythingInSelfAndDescendants ).Count() > 0 )
					{
						Log.Info( "Component found, replacing all" );
						foreach ( var component in Game.ActiveScene.Components.GetAll( componentType, FindMode.EverythingInSelfAndDescendants ) )
						{
							component.GameObject.Destroy();
						}
						if ( gameObjectSpawned ) return;
						var gb = clone.Prefab.Clone();
						gb.BreakFromPrefab();
						if ( clone.NetworkSpawn ) gb.NetworkSpawn( null );
						gameObjectSpawned = true;
						continue;
					}

					if ( !gameObjectSpawned )
					{
						var obj = clone.Prefab.Clone();
						obj.BreakFromPrefab();
						if ( clone.NetworkSpawn ) obj.NetworkSpawn( null );
						gameObjectSpawned = true;
					}
				}
			}
		}
	}
	public enum LoadingFlags
	{
		None,
		[Description( "Checks if a component is in the scene, if it is, the prefab will not be spawned" )]
		CheckForComponents,
		[Description( "Checks if a component is in the scene, if it is, the first one will be destroyed, and the prefab will be spawned" )]
		DestroyFirst,
		[Description( "Checks if a component is in the scene, if it is, all of them will be destroyed and the prefab will be spawned" )]
		DestroyAll,

	}


	[GameResource( "SceneLoadingResource", "loading", "A resource that allows spawning of prefabs on scene start", Icon = "public" )]
	public class SceneLoadingResource : GameResource
	{
		public List<SceneLoadingClass> SceneLoadingClasses { get; set; } = new();
	}
	public class SceneLoadingClass
	{
		public GameObject Prefab { get; set; }
		public LoadingFlags Flags { get; set; } = LoadingFlags.None;
		public List<Type> ComponentTypes { get; set; }
		public bool NetworkSpawn { get; set; } = false;


		public SceneLoadingClass()
		{
			Prefab = null;
			Flags = LoadingFlags.None;
			ComponentTypes = null;
		}

		public SceneLoadingClass( GameObject prefab, LoadingFlags flags, List<Type> componentType )
		{
			Prefab = prefab;
			Flags = flags;
			ComponentTypes = componentType;
		}
	}
	[Description( "A custom scene that allows for manipulation of the scene file before loading" )]
	public class CustomScene
	{
		[Description( "The scenefile you are manipulating, override to change it" )] public virtual SceneFile sceneFileDupe { get; set; }
		public string RawScene { get; private set; }
		public Scene newScene { get; private set; } = new();
		[Description( "Called when a scene object is created, return the object you want to spawn, or null to use the default object" )]
		public Action<JsonObject> OnSceneObjectCreated { get; set; }
		public Action<JsonObject[]> BeforeSceneLoaded { get; set; }
		public CustomScene( SceneFile sceneFile )
		{
			sceneFileDupe = new SceneFile();
			sceneFileDupe.GameObjects = sceneFile.GameObjects
				.Select( obj => JsonSerializer.Deserialize<JsonObject>( JsonSerializer.Serialize( obj ) ) )
				.ToArray();
		}

		internal void LoadSceneInternal()
		{
			var finalScene = new SceneFile();
			var finalList = new List<JsonObject>();
			foreach ( var obj in sceneFileDupe.GameObjects )
			{
				OnSceneObjectCreated?.Invoke( obj );
				finalList.Add( obj );
			}

			finalScene.GameObjects = finalList.ToArray();
			BeforeSceneLoaded?.Invoke( finalScene.GameObjects );
			Game.ActiveScene.Load( finalScene );
		}
		[Description( "Load the custom scene" )]
		public void LoadScene()
		{
			LoadSceneInternal();
		}

		[Description( "Create a GameObject within the custom scene" )]
		public void CreateObject( GameObject gameObject )
		{
			var clone = gameObject.Clone();
			var objects = sceneFileDupe.GameObjects.ToList();
			objects.Add( clone.Serialize() );
			Log.Info( clone.Serialize().ToString() );
			sceneFileDupe.GameObjects = objects.ToArray();
		}

		[Description( "Remove a GameObject from the custom scene" )]
		public void RemoveObject( JsonNode obj )
		{
			List<JsonObject> gameObjects = sceneFileDupe.GameObjects.ToList();
			var selectedObject = gameObjects.Find( x => x == obj );
			gameObjects.Remove( selectedObject );
			sceneFileDupe.GameObjects = gameObjects.ToArray();

		}



		public void RemoveComponentByType( JsonObject obj, Type type )
		{
			var preObj = JsonSerializer.Deserialize<JsonObject>( JsonSerializer.Serialize( obj ) );
			if ( obj.TryGetPropertyValue( "Components", out var jsonnode ) && jsonnode is not null )
			{
				var jsonString = jsonnode.ToString();
				if ( !string.IsNullOrWhiteSpace( jsonString ) )
				{
					var components = JsonSerializer.Deserialize<List<JsonNode>>( jsonString );
					var component = components.Find( x => x["__type"]?.ToString() == type.ToString() );
					if ( component is not null )
					{
						components.Remove( component );
						obj["Components"] = JsonSerializer.Deserialize<JsonNode>( JsonSerializer.Serialize( components ) );
					}
				}

				var gbList = sceneFileDupe.GameObjects.ToList();
				if ( gbList.Find( x => x == obj ) is null )
				{
					var parentNode = FindParent( preObj );
					var parent = gbList.Find( x => x == parentNode );
					if ( parent != null && parent.TryGetPropertyValue( "Children", out var childrenJsonNode ) && childrenJsonNode != null )
					{
						if ( childrenJsonNode is JsonArray childrenArray )
						{
							UpdateChildComponentsRecursively( childrenArray, obj );
						}
						else
						{
							Log.Warning( "The 'Children' node is not of type 'JsonArray'." );
						}
					}
				}
				else
				{
					gbList[gbList.FindIndex( x => x == obj )] = obj;
				}
				sceneFileDupe.GameObjects = gbList.ToArray();
			}
		}

		private void UpdateChildComponentsRecursively( JsonArray childrenArray, JsonObject obj )
		{
			foreach ( var child in childrenArray )
			{
				if ( child is JsonObject childObject )
				{
					if ( childObject["__guid"].ToString() == obj["__guid"].ToString() )
					{
						childObject["Components"] = JsonSerializer.Deserialize<JsonNode>( JsonSerializer.Serialize( obj["Components"] ) );
					}
					else if ( childObject.TryGetPropertyValue( "Children", out var nestedChildrenJsonNode ) && nestedChildrenJsonNode is JsonArray nestedChildrenArray )
					{
						UpdateChildComponentsRecursively( nestedChildrenArray, obj );
					}
				}
			}
		}

		public void AddComponent( JsonObject obj, JsonObject newComponent )
		{
			var preObj = JsonSerializer.Deserialize<JsonObject>( JsonSerializer.Serialize( obj ) );
			if ( obj.TryGetPropertyValue( "Components", out var jsonnode ) && jsonnode is not null )
			{
				var jsonString = jsonnode.ToString();
				if ( !string.IsNullOrWhiteSpace( jsonString ) )
				{
					var components = JsonSerializer.Deserialize<List<JsonNode>>( jsonString );
					components.Add( newComponent );
					obj["Components"] = JsonSerializer.Deserialize<JsonNode>( JsonSerializer.Serialize( components ) );
				}
				else
				{
					var components = new List<JsonNode> { newComponent };
					obj["Components"] = JsonSerializer.Deserialize<JsonNode>( JsonSerializer.Serialize( components ) );
				}

				var gbList = sceneFileDupe.GameObjects.ToList();
				if ( gbList.Find( x => x == obj ) is null )
				{
					var parentNode = FindParent( preObj );
					var parent = gbList.Find( x => x == parentNode );
					if ( parent != null && parent.TryGetPropertyValue( "Children", out var childrenJsonNode ) && childrenJsonNode != null )
					{
						if ( childrenJsonNode is JsonArray childrenArray )
						{
							UpdateChildComponentsRecursively( childrenArray, obj );
						}
						else
						{
							Log.Warning( "The 'Children' node is not of type 'JsonArray'." );
						}
					}
				}
				else
				{
					gbList[gbList.FindIndex( x => x == obj )] = obj;
				}
				sceneFileDupe.GameObjects = gbList.ToArray();
			}
		}

		public void AddComponentByType( JsonObject obj, Type componentType )
		{
			var preObj = JsonSerializer.Deserialize<JsonObject>( JsonSerializer.Serialize( obj ) );
			if ( obj.TryGetPropertyValue( "Components", out var jsonnode ) && jsonnode is not null )
			{
				var jsonString = jsonnode.ToString();
				var components = !string.IsNullOrWhiteSpace( jsonString )
					? JsonSerializer.Deserialize<List<JsonNode>>( jsonString )
					: new List<JsonNode>();

				var newComponent = new JsonObject
				{
					["__type"] = componentType.ToString()
				};

				components.Add( newComponent );
				obj["Components"] = JsonSerializer.Deserialize<JsonNode>( JsonSerializer.Serialize( components ) );

				var gbList = sceneFileDupe.GameObjects.ToList();
				if ( gbList.Find( x => x == obj ) is null )
				{
					var parentNode = FindParent( preObj );
					var parent = gbList.Find( x => x == parentNode );
					if ( parent != null && parent.TryGetPropertyValue( "Children", out var childrenJsonNode ) && childrenJsonNode != null )
					{
						if ( childrenJsonNode is JsonArray childrenArray )
						{
							UpdateChildComponentsRecursively( childrenArray, obj );
						}
						else
						{
							Log.Warning( "The 'Children' node is not of type 'JsonArray'." );
						}
					}
				}
				else
				{
					gbList[gbList.FindIndex( x => x == obj )] = obj;
				}
				sceneFileDupe.GameObjects = gbList.ToArray();
			}
		}

		public JsonObject FindParent( JsonNode children )
		{
			foreach ( var obj in sceneFileDupe.GameObjects )
			{
				var parent = FindParentRecursive( obj, children );
				if ( parent != null )
				{
					return parent;
				}
			}
			return null;
		}

		private JsonObject FindParentRecursive( JsonObject parent, JsonNode children )
		{
			if ( parent.TryGetPropertyValue( "Children", out var childrenJsonNode ) )
			{
				var childrenList = JsonSerializer.Deserialize<List<JsonObject>>( childrenJsonNode.ToString() );
				if ( childrenList != null )
				{
					foreach ( var child in childrenList )
					{
						if ( child != null && child["__guid"].ToString() == children["__guid"].ToString() )
						{
							return parent;
						}

						var foundParent = FindParentRecursive( child, children );
						if ( foundParent != null )
						{
							return parent;
						}
					}
				}
			}
			return null;
		}

		public IEnumerable<JsonObject> GetAllObjectsByType( Type type )
		{
			return GetAllObjectsByTypeRecursive( sceneFileDupe.GameObjects, type );
		}

		public IEnumerable<JsonObject> GetAllObjectsByGuid( string guid )
		{
			return GetAllObjectsByGuidRecursive( sceneFileDupe.GameObjects, guid );
		}

		private IEnumerable<JsonObject> GetAllObjectsByTypeRecursive( IEnumerable<JsonObject> gameObjects, Type type )
		{
			foreach ( var obj in gameObjects )
			{
				if ( obj.TryGetPropertyValue( "Components", out var jsonnode ) && jsonnode is not null )
				{
					var jsonString = jsonnode.ToString();
					if ( !string.IsNullOrWhiteSpace( jsonString ) )
					{
						var components = JsonSerializer.Deserialize<List<JsonNode>>( jsonString );
						if ( components.Any( component => component["__type"]?.ToString() == type.ToString() ) )
						{
							yield return obj;
						}
					}
				}

				if ( obj.TryGetPropertyValue( "Children", out var childrenJsonNode ) && childrenJsonNode is not null )
				{
					var childrenString = childrenJsonNode.ToString();
					if ( !string.IsNullOrWhiteSpace( childrenString ) )
					{
						var children = JsonSerializer.Deserialize<List<JsonObject>>( childrenString );
						foreach ( var child in GetAllObjectsByTypeRecursive( children, type ) )
						{
							yield return child;
						}
					}
				}
			}
		}

		private IEnumerable<JsonObject> GetAllObjectsByGuidRecursive( IEnumerable<JsonObject> gameObjects, string guid )
		{
			foreach ( var obj in gameObjects )
			{
				if ( obj.TryGetPropertyValue( "__guid", out var jsonNode ) && jsonNode is not null )
				{
					var objectGuid = jsonNode.ToString();
					if ( !string.IsNullOrWhiteSpace( objectGuid ) && objectGuid == guid )
					{
						yield return obj;
					}
				}

				if ( obj.TryGetPropertyValue( "Children", out var childrenJsonNode ) && childrenJsonNode is not null )
				{
					var childrenString = childrenJsonNode.ToString();
					if ( !string.IsNullOrWhiteSpace( childrenString ) )
					{
						var children = JsonSerializer.Deserialize<List<JsonObject>>( childrenString );
						foreach ( var child in GetAllObjectsByGuidRecursive( children, guid ) )
						{
							yield return child;
						}
					}
				}
			}
		}
		public IEnumerable<JsonNode> GetAllObjectsByName( string name )
		{
			var objects = sceneFileDupe.GameObjects;
			foreach ( var obj in objects )
			{
				obj.TryGetPropertyValue( "Name", out var objName );
				if ( objName is not null && objName.ToString() == name )
				{
					yield return obj;
				}
			}
		}
	}

}