using Facepunch.ActionGraphs; using Sandbox.ActionGraphs; using Sandbox.Internal; using System; using System.Collections.Generic; using System.Text.Json.Nodes; namespace GameObjects; [TestClass] public class SerializeTest { TypeLibrary TypeLibrary; NodeLibrary NodeLibrary; private TypeLibrary _oldTypeLibrary; private NodeLibrary _oldNodeLibrary; [TestInitialize] public void TestInitialize() { // Replace TypeLibrary / NodeLibrary with mocked ones, store the originals _oldTypeLibrary = Game.TypeLibrary; _oldNodeLibrary = Game.NodeLibrary; TypeLibrary = new Sandbox.Internal.TypeLibrary(); TypeLibrary.AddAssembly( typeof( ModelRenderer ).Assembly, false ); TypeLibrary.AddAssembly( typeof( OldVersioningComponent ).Assembly, false ); TypeLibrary.AddAssembly( typeof( InheritedPropertyComponent ).Assembly, false ); JsonUpgrader.UpdateUpgraders( TypeLibrary ); NodeLibrary = new NodeLibrary( new TypeLoader( () => TypeLibrary ) ); NodeLibrary.AddAssembly( typeof( LogNodes ).Assembly ); NodeLibrary.AddAssembly( typeof( Scene ).Assembly ); // engine Game.TypeLibrary = TypeLibrary; Game.NodeLibrary = NodeLibrary; } [TestCleanup] public void Cleanup() { // Make sure our mocked TypeLibrary / NodeLibrary doesn't leak out, restore old ones Game.TypeLibrary = _oldTypeLibrary; Game.NodeLibrary = _oldNodeLibrary; } /// /// Fails the test if type library isn't initialized, or doesn't contain the given types. /// private void AssertTypeLibraryReady( params Type[] expectedTypes ) { Assert.IsNotNull( TypeLibrary, "TypeLibrary hasn't been mocked" ); foreach ( var type in expectedTypes ) { Assert.IsNotNull( TypeLibrary.GetType( type ), "TypeLibrary hasn't been given the game assembly" ); } } [TestMethod] public void SerializeSingle() { AssertTypeLibraryReady( typeof( ModelRenderer ), typeof( ComponentIdTest ) ); using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "My Game Object"; go1.LocalTransform = new Transform( Vector3.Up, Rotation.Identity, 10 ); var go1comp1 = go1.Components.Create(); var go1comp2 = go1.Components.Create(); go1comp1.Other = go1comp2; go1comp2.Other = go1comp1; var model = go1.Components.Create(); model.Model = Model.Load( "models/dev/box.vmdl" ); model.Tint = Color.Red; var node = go1.Serialize(); System.Console.WriteLine( node ); SceneUtility.MakeIdGuidsUnique( node ); var go2 = new GameObject(); go2.Deserialize( node ); var go2comps = go2.Components.GetAll().ToArray(); var go2comp1 = go2comps[0]; var go2comp2 = go2comps[1]; Assert.AreNotEqual( go1comp1.Id, go2comp1.Id ); Assert.AreNotEqual( go1comp2.Id, go2comp2.Id ); Assert.AreEqual( go2comp1, go2comp2.Other ); Assert.AreEqual( go2comp2, go2comp1.Other ); Assert.AreNotEqual( go1.Id, go2.Id ); Assert.AreEqual( go1.Name, go2.Name ); Assert.AreEqual( go1.Enabled, go2.Enabled ); Assert.AreEqual( go1.LocalTransform, go2.LocalTransform ); Assert.AreEqual( go1.Components.Count, go2.Components.Count ); Assert.AreEqual( go1.Components.Get().Model, go2.Components.Get().Model ); Assert.AreEqual( go1.Components.Get().Tint, go2.Components.Get().Tint ); Assert.AreEqual( go1.Components.Get().MaterialOverride, go2.Components.Get().MaterialOverride ); } #region Cloning [TestMethod] public void CloneWithReferences() { AssertTypeLibraryReady( typeof( ModelRenderer ), typeof( ComponentIdTest ) ); using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "My Game Object"; var go1comp1 = go1.Components.Create(); var go1comp2 = go1.Components.Create(); var go1comp3 = go1.Components.Create(); go1comp1.Other = go1comp3; go1comp2.Other = go1comp1; go1comp3.Other = go1comp2; var go2 = go1.Clone(); var go2comps = go2.Components.GetAll().ToArray(); Assert.AreEqual( 3, go2comps.Length ); var go2comp1 = go2comps[0]; var go2comp2 = go2comps[1]; var go2comp3 = go2comps[2]; Assert.IsNotNull( go2comp1 ); Assert.IsNotNull( go2comp2 ); Assert.IsNotNull( go2comp3 ); Assert.AreEqual( go2comp1.Id, go2comp1.Id ); Assert.AreEqual( go2comp2.Id, go2comp2.Id ); Assert.AreEqual( go2comp3.Id, go2comp3.Id ); Assert.IsNotNull( go2comp1.Other ); Assert.IsNotNull( go2comp2.Other ); Assert.IsNotNull( go2comp3.Other ); Assert.AreEqual( go2comp3.Id, go2comp1.Other.Id ); Assert.AreEqual( go2comp1.Id, go2comp2.Other.Id ); Assert.AreEqual( go2comp2.Id, go2comp3.Other.Id ); } /// /// Cloned components need a deep copy of reference type properties. Here, the source /// object has a , and its clone shouldn't reference the same /// list. Otherwise, after cloning, when the clone modifies its list, the source /// object would see those changes. /// [TestMethod] public void CloneReferenceTypeProperty() { AssertTypeLibraryReady( typeof( ComponentWithListProperty ) ); using var scope = new Scene().Push(); var source = new GameObject( true, "Source" ); var sourceComp = source.AddComponent(); sourceComp.List = [1, 2, 3]; var clone = source.Clone(); var cloneComp = clone.GetComponent(); Assert.IsNotNull( cloneComp?.List ); // Critical test: lists can't be the same reference Assert.AreNotSame( sourceComp.List, cloneComp!.List ); Assert.AreEqual( sourceComp.List.Count, cloneComp.List.Count ); Assert.AreEqual( sourceComp.List[1], cloneComp.List[1] ); } /// /// Cloned components can contain user type properties, which themselves can contain /// references. /// /// /// If true, the cloned object contains a reference to itself. Otherwise, it references /// an external object in the scene. /// [TestMethod] [DataRow( true ), DataRow( false )] public void CloneUserTypeWithReference( bool selfReference ) { AssertTypeLibraryReady( typeof( ComponentWithNestedReference ) ); using var scope = new Scene().Push(); var source = new GameObject( true, "Source" ); var sourceComp = source.AddComponent(); var referenced = selfReference ? source : new GameObject( true, "Referenced" ); sourceComp.UserType = new UserTypeWithReference( referenced ); var clone = source.Clone(); var cloneComp = clone.GetComponent(); var expectedReference = selfReference ? clone : referenced; Assert.AreSame( expectedReference, cloneComp?.UserType?.Reference ); } /// /// LineRenders use a list of references. When cloning a LineRender, /// we need to ensure that the cloned object references the correct instances. /// /// /// If true, the cloned object contains a reference to itself. Otherwise, it references /// an external object in the scene. /// [TestMethod] [DataRow( true ), DataRow( false )] public void CloneLineRendererWithReferenceInPoints( bool selfReference ) { AssertTypeLibraryReady( typeof( ComponentWithNestedReference ) ); using var scope = new Scene().Push(); var source = new GameObject( true, "Source" ); var sourceComp = source.AddComponent(); sourceComp.Points = new(); var referenced = selfReference ? source : new GameObject( true, "Referenced" ); sourceComp.Points.Add( referenced ); var clone = source.Clone(); var cloneComp = clone.GetComponent(); var expectedReference = selfReference ? clone : referenced; Assert.AreSame( expectedReference, cloneComp?.Points?.FirstOrDefault() ); } /// /// If we clone a Component with a property using [RequireComponent]. /// The cloned Go should also have a reference to the cloned counterpart of the required component. /// Even if the property is not marked with [Property] or [JsonInclude]. /// [TestMethod] public void CloneRquiredComponentProperty() { AssertTypeLibraryReady( typeof( ModelRenderer ), typeof( ComponentIdTest ) ); using var scope = new Scene().Push(); var original = new GameObject(); original.Name = "My Game Object"; var originalComp = original.Components.Create(); var clone = original.Clone(); var cloneComp = clone.GetComponent(); Assert.IsNotNull( cloneComp ); Assert.AreNotEqual( originalComp.Id, cloneComp.Id ); } /// /// There was a regression when cloning a list of user defined objects, if the object had a property that was named "Prefab". /// This could lead to a deserilziation issue in UpdateClonedIdsInJson when trying to rewire the GameObjectReferences. /// https://github.com/Facepunch/sbox-issues/issues/7638 /// [TestMethod] public void CloneComponentWithPrefabList() { var scene = new Scene(); using var sceneScope = scene.Push(); var prefabBasic = Prefabs.BasicPrefab; var go = scene.CreateObject(); go.Components.Create(); go.Components.Get().Decorations.Add( new ComponentWithPrefabList.PrafbEntry() { Prefab = prefabBasic } ); var clone = go.Clone(); Assert.AreEqual( 1, clone.Components.Get().Decorations.Count ); var clonedPrefabRef = clone.Components.Get().Decorations[0].Prefab; Assert.AreEqual( prefabBasic, clonedPrefabRef ); } /// /// Cloned components can contain delegates implemented as action graphs. These delegates contain a /// reference to the that contains them, equivalent to a "this" parameter. /// Clones of these delegates need to be retargeted to the cloned object. /// [TestMethod] public void CloneActionGraphProperty() { AssertTypeLibraryReady( typeof( ComponentWithActionGraph ) ); using var sceneScope = new Scene().Push(); var source = new GameObject( true, "Source" ); var sourceComp = source.AddComponent(); var action = CreateActionGraphDelegateWithTarget>( source ); var graph = action.Graph; sourceComp.Func = action.Delegate; RewireGraphToReturnNameOfObject( graph.TargetOutput! ); var clone = source.Clone( Transform.Zero, name: "Clone" ); var cloneComp = clone.GetComponent(); var cloneAction = cloneComp.Func.GetActionGraphInstance(); // Clone should be re-using the same graph instance... Assert.AreSame( graph, cloneAction!.Graph ); // ...but should return its own name Assert.AreEqual( "Source", sourceComp.Func() ); Assert.AreEqual( "Clone", cloneComp.Func() ); } /// /// Cloned components can contain delegates implemented as action graphs. These graphs can contain /// GameObject / Component references (scene.ref nodes), which should be accounted for when cloning the GameObject /// containing the graphs. We should handle them the same as GameObject references in Component properties. /// /// /// If true, the cloned object contains a reference to itself. Otherwise, it references an external object in the scene. /// [TestMethod] [DataRow( true ), DataRow( false )] public void CloneActionGraphSceneReference( bool selfReference ) { AssertTypeLibraryReady( typeof( ComponentWithActionGraph ) ); using var sceneScope = new Scene().Push(); var source = new GameObject( true, "Source" ); var sourceComp = source.AddComponent(); var referenced = selfReference ? source : new GameObject( true, "Referenced" ); var action = CreateActionGraphDelegateWithTarget>( source ); var graph = action.Graph; sourceComp.Func = action.Delegate; // Create a scene.ref node that references a GameObject var sceneRef = graph.AddNode( "scene.ref" ); sceneRef.Properties["gameobject"].Value = GameObjectReference.FromInstance( referenced ); RewireGraphToReturnNameOfObject( sceneRef.Outputs.Result ); var clone = source.Clone( Transform.Zero, name: "Clone" ); var cloneComp = clone.GetComponent(); var expectedReference = selfReference ? clone : referenced; var cloneAction = cloneComp.Func.GetActionGraphInstance(); // Clone should be re-using the same graph instance... Assert.AreSame( graph, cloneAction!.Graph ); // ...but should return the correct name of the referenced object Assert.AreEqual( referenced.Name, sourceComp.Func() ); Assert.AreEqual( expectedReference.Name, cloneComp.Func() ); } #endregion [TestMethod] public void SerializeWithComponentIds() { AssertTypeLibraryReady( typeof( ModelRenderer ), typeof( ComponentIdTest ) ); using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "My Game Object"; var go1comp1 = go1.Components.Create(); var go1comp2 = go1.Components.Create(); var go1comp3 = go1.Components.Create(); go1comp1.Other = go1comp3; go1comp2.Other = go1comp1; go1comp3.Other = go1comp2; var node = go1.Serialize(); System.Console.WriteLine( node ); SceneUtility.MakeIdGuidsUnique( node ); var go2 = new GameObject(); go2.Deserialize( node ); var go2comps = go2.Components.GetAll().ToArray(); Assert.AreEqual( 3, go2comps.Length ); var go2comp1 = go2comps[0]; var go2comp2 = go2comps[1]; var go2comp3 = go2comps[2]; Assert.IsNotNull( go2comp1 ); Assert.IsNotNull( go2comp2 ); Assert.IsNotNull( go2comp3 ); Assert.AreEqual( go2comp1.Id, go2comp1.Id ); Assert.AreEqual( go2comp2.Id, go2comp2.Id ); Assert.AreEqual( go2comp3.Id, go2comp3.Id ); Assert.IsNotNull( go2comp1.Other ); Assert.IsNotNull( go2comp2.Other ); Assert.IsNotNull( go2comp3.Other ); Assert.AreEqual( go2comp3.Id, go2comp1.Other.Id ); Assert.AreEqual( go2comp1.Id, go2comp2.Other.Id ); Assert.AreEqual( go2comp2.Id, go2comp3.Other.Id ); } [TestMethod] public void SerializeWithChildren() { using var scope = new Scene().Push(); var timer = new ScopeTimer( "Creation" ); var go1 = new GameObject(); go1.Name = "My Game Object"; go1.LocalTransform = new Transform( Vector3.Up, Rotation.Identity, 10 ); int childrenCount = 15000; for ( int i = 0; i < childrenCount; i++ ) { var child = new GameObject(); child.Name = $"Child {i}"; child.LocalTransform = new Transform( Vector3.Random * 1000 ); child.Parent = go1; child.Components.Create(); } timer.Dispose(); timer = new ScopeTimer( "Serialize" ); var node = go1.Serialize(); timer.Dispose(); using ( new ScopeTimer( "MakeGameObjectsUnique" ) ) { SceneUtility.MakeIdGuidsUnique( node ); } timer = new ScopeTimer( "Deserialize" ); //System.Console.WriteLine( node ); var go2 = new GameObject(); go2.Deserialize( node ); timer.Dispose(); Assert.AreNotEqual( go1.Id, go2.Id ); Assert.AreEqual( go1.Name, go2.Name ); Assert.AreEqual( go1.Enabled, go2.Enabled ); Assert.AreEqual( go1.LocalTransform, go2.LocalTransform ); Assert.AreEqual( go2.Children.Count, childrenCount ); } [TestMethod] public void VersionedComponent() { AssertTypeLibraryReady( typeof( OldVersioningComponent ), typeof( NewVersioningComponent ) ); var scene = new Scene(); using var scope = scene.Push(); var go1 = new GameObject(); go1.Name = "My Game Object"; go1.LocalTransform = new Transform( Vector3.Up, Rotation.Identity, 10 ); var versioning = go1.Components.Create( true ); versioning.OldProperty = "My Value"; var node = go1.Serialize(); System.Console.WriteLine( "Before adjusting JSON" ); System.Console.WriteLine( node ); // This json parsing bit is a bit of a nightmare to work with // But basically all I'm doing is changing the type of the first component (defined above) to NewVersioningComponent // Then we can test a proper upgrade cycle. node.Remove( "Components", out var componentArrayNode ); var componentNode = componentArrayNode as JsonArray; var versioningComponentNode = componentNode.First(); versioningComponentNode["__type"] = "NewVersioningComponent"; // Replace the first component array node with our new node. // I'm using RemoveAt and Add because you can't use the array indexer on JsonObjects otherwise // it'll cry about the node already having a parent. componentNode.RemoveAt( 0 ); componentNode.Add( versioningComponentNode ); // Replace the component array too. node["Components"] = componentNode; SceneUtility.MakeIdGuidsUnique( node ); var go2 = new GameObject(); go2.Deserialize( node ); var node2 = go2.Serialize(); System.Console.WriteLine( node2 ); var go2ComponentNode = node2["Components"] as JsonArray; var go2versioningComponentNode = go2ComponentNode.First(); Assert.IsTrue( go2versioningComponentNode["OldProperty"] is null ); Assert.IsTrue( go2versioningComponentNode["MyProperty"].ToString() == "My Value" ); } [TestMethod] public void DeserializeMissingFieldShouldRespectCodeInitializer() { AssertTypeLibraryReady( typeof( ComponentWithInitializer ) ); using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "My Game Object"; go1.LocalTransform = new Transform( Vector3.Up, Rotation.Identity, 10 ); var go1comp1 = go1.Components.Create(); // JSON with missing fields var node = go1.Serialize(); node["Components"].AsArray()[0].AsObject().Remove( "ReferenceTypeWithIntializer" ); node["Components"].AsArray()[0].AsObject().Remove( "ValueTypeWithIntializer" ); node["Components"].AsArray()[0].AsObject().Remove( "TypeWithCustomJsonPopulator" ); System.Console.WriteLine( node ); SceneUtility.MakeIdGuidsUnique( node ); var go2 = new GameObject(); go2.Deserialize( node ); var go2comp1 = go2.Components.Get(); // Both the comp created via code and the one created from json should have the // code defined initializer set Assert.AreEqual( ComponentWithInitializer.TestReferenceType, go1comp1.ReferenceTypeWithIntializer ); Assert.AreEqual( ComponentWithInitializer.TestReferenceType, go2comp1.ReferenceTypeWithIntializer ); Assert.AreEqual( ComponentWithInitializer.TestValueType, go1comp1.ValueTypeWithIntializer ); Assert.AreEqual( ComponentWithInitializer.TestValueType, go2comp1.ValueTypeWithIntializer ); Assert.AreEqual( ComponentWithInitializer.TestRotation, go1comp1.TypeWithCustomJsonPopulator ); Assert.AreEqual( ComponentWithInitializer.TestRotation, go2comp1.TypeWithCustomJsonPopulator ); } [TestMethod] public void DeserializeNullFields() { AssertTypeLibraryReady( typeof( ComponentWithInitializer ) ); using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "My Game Object"; go1.LocalTransform = new Transform( Vector3.Up, Rotation.Identity, 10 ); var go1comp1 = go1.Components.Create(); go1comp1.ReferenceTypeWithIntializer = null; var node = go1.Serialize(); System.Console.WriteLine( node ); SceneUtility.MakeIdGuidsUnique( node ); var go2 = new GameObject(); go2.Deserialize( node ); var go2comp1 = go2.Components.Get(); // Field should be set to null after deserialize Assert.AreEqual( null, go1comp1.ReferenceTypeWithIntializer ); } [TestMethod] public void DeserializeNullFieldsIntoExistingObject() { AssertTypeLibraryReady( typeof( ComponentWithInitializer ) ); using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "My Game Object"; go1.LocalTransform = new Transform( Vector3.Up, Rotation.Identity, 10 ); var go1comp1 = go1.Components.Create(); go1comp1.ReferenceTypeWithIntializer = null; var node = go1.Serialize(); System.Console.WriteLine( node ); SceneUtility.MakeIdGuidsUnique( node ); var go2 = new GameObject(); go2.Name = "My Game Object"; go2.LocalTransform = new Transform( Vector3.Up, Rotation.Identity, 10 ); var go2comp2 = go1.Components.Create(); // make sure field is not null go2comp2.ReferenceTypeWithIntializer = ComponentWithInitializer.TestReferenceType; // desserialize data with null field into existing object go2.Deserialize( node ); go2comp2 = go2.Components.Get(); // Should now be null Assert.AreEqual( null, go2comp2.ReferenceTypeWithIntializer ); } [TestMethod] public void DeserializeEventOrder() { AssertTypeLibraryReady( typeof( OrderTestComponent ) ); JsonObject SceneSerialized = null; { var scene = new Scene(); using var sceneScope = scene.Push(); var go = scene.CreateObject(); var o = go.Components.Create( false ); SceneSerialized = scene.Serialize(); } { var scene = new Scene(); using var sceneScope = scene.Push(); scene.Deserialize( SceneSerialized ); var o = scene.GetAllObjects( true ).Select( x => x.GetComponent( true ) ).Where( x => x.IsValid() ).First(); Assert.AreEqual( 1, o.AwakeCalls ); Assert.AreEqual( 0, o.EnabledCalls ); Assert.AreEqual( 0, o.DisabledCalls ); } } [TestMethod] public void SerializesInheritedProperties() { using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "GameObject With Inherited Property Component"; var baseClassComponent = go1.Components.Create(); baseClassComponent.SetPrivateFlag( true ); var derivedComponent = go1.Components.Create(); derivedComponent.SetPrivateFlag( true ); var node1 = go1.Serialize(); var componentsArray1 = node1["Components"].AsArray(); var baseComponentJson = componentsArray1[0].AsObject(); var derivedComponentJson = componentsArray1[1].AsObject(); Assert.IsTrue( baseComponentJson.GetPropertyValue( "Flag", false ) ); Assert.IsTrue( derivedComponentJson.GetPropertyValue( "Flag", false ) ); SceneUtility.MakeIdGuidsUnique( node1 ); // Hack: pretend we're from dynamic assembly so that setting private // properties will be allowed. var type1 = Game.TypeLibrary.GetType(); var type2 = Game.TypeLibrary.GetType(); type1.IsDynamicAssembly = true; type2.IsDynamicAssembly = true; var go2 = new GameObject(); go2.Deserialize( node1 ); var baseComp1 = go2.GetComponent(); var baseComp2 = go2.GetComponent(); Assert.IsTrue( baseComp1.Flag ); Assert.IsTrue( baseComp2.Flag ); } [TestMethod] public void SerializesComponentEnabledFlag() { AssertTypeLibraryReady( typeof( ModelRenderer ) ); using var scope = new Scene().Push(); var go1 = new GameObject(); go1.Name = "Game Object With Enabled and Disabled Component"; var enabledComponent = go1.Components.Create( true ); var disabledComponent = go1.Components.Create( false ); // Ensure the component is enabled Assert.IsTrue( enabledComponent.Enabled ); var node1 = go1.Serialize(); // Verify enabled flag is in JSON and set to true var componentsArray1 = node1["Components"].AsArray(); var componentJson1 = componentsArray1[0].AsObject(); Assert.IsTrue( componentJson1.ContainsKey( Component.JsonKeys.Enabled ), "Enabled flag missing for enabled component" ); Assert.AreEqual( true, componentJson1[Component.JsonKeys.Enabled].GetValue() ); // Ensure the component is disabled Assert.IsFalse( disabledComponent.Enabled ); // Verify enabled flag is in JSON and set to false var componentsArray2 = node1["Components"].AsArray(); var componentJson2 = componentsArray2[1].AsObject(); Assert.IsTrue( componentJson2.ContainsKey( Component.JsonKeys.Enabled ), "Enabled flag missing for disabled component" ); Assert.AreEqual( false, componentJson2[Component.JsonKeys.Enabled].GetValue() ); } #region Action Graph Helpers /// /// Creates an action graph that implements a delegate of type , which has /// a target parameter that references (like a "this" parameter in C#). /// private ActionGraphDelegate CreateActionGraphDelegateWithTarget( GameObject target ) where T : Delegate { var graph = ActionGraph.CreateEmpty( NodeLibrary ); // Copy parameters from T, but include a target parameter that defaults to the target game object graph.SetParameters( NodeBinding.FromDelegateType( typeof( T ), NodeLibrary ) .With( InputDefinition.Target( typeof( GameObject ), target ) ) ); // Add input (entry point) / output (return) nodes to the graph graph.AddRequiredNodes(); return graph.CreateDelegate(); } /// /// Given , which is a value output in an , /// rewire the graph so it returns the name of whatever that game object is. /// private void RewireGraphToReturnNameOfObject( Node.Output gameObjectOutput ) { var graph = gameObjectOutput.Node.ActionGraph; // Create node to get the name of whatever GameObject is in gameObjectOutput var getName = graph.AddNode( NodeLibrary.Property ); getName.Properties.Type.Value = typeof( GameObject ); getName.Properties.Name.Value = nameof( GameObject.Name ); getName.Inputs.Target.SetLink( gameObjectOutput ); // Return that name right after the graph entry point node (InputNode) graph.PrimaryOutputNode!.Inputs.Signal.SetLink( graph.InputNode!.Outputs.Signal ); graph.PrimaryOutputNode.Inputs.Result.SetLink( getName.Outputs.Result ); } #endregion } public class InheritedPropertyComponentBase : Component { [Sandbox.Property] public bool Flag { get; private set; } public void SetPrivateFlag( bool value ) { Flag = value; } } public sealed class InheritedPropertyComponent : InheritedPropertyComponentBase { } public class ComponentIdTest : Component { [Sandbox.Property] public ComponentIdTest Other { get; set; } } public class OldVersioningComponent : Component { [Sandbox.Property] public string OldProperty { get; set; } public override int ComponentVersion => 0; } public class NewVersioningComponent : Component { [Sandbox.Property] public string MyProperty { get; set; } public override int ComponentVersion => 1; [JsonUpgrader( typeof( NewVersioningComponent ), 1 )] public static void MyPropertyUpgrade( JsonObject json ) { System.Console.WriteLine( "Running MyPropertyUpgrade" ); if ( json.Remove( "OldProperty", out var newNode ) ) { json["MyProperty"] = newNode; } else { Assert.IsTrue( false, "Couldn't remove OldProperty from OldVersioningComponent data. Fix this!" ); } } } public class ComponentWithInitializer : Component { public static string TestReferenceType = "intialized"; public static int TestValueType = 42; public static Rotation TestRotation = Rotation.Identity; // 0,0,0,1 [Sandbox.Property] public string ReferenceTypeWithIntializer = TestReferenceType; [Sandbox.Property] public int ValueTypeWithIntializer = TestValueType; [Sandbox.Property] public Rotation TypeWithCustomJsonPopulator = TestRotation; } public class ComponentWithListProperty : Component { [Sandbox.Property] public List List { get; set; } } public record UserTypeWithReference( GameObject Reference ); public class ComponentWithNestedReference : Component { [Sandbox.Property] public UserTypeWithReference UserType { get; set; } } public class ComponentWithActionGraph : Component { [Sandbox.Property] public Func Func { get; set; } } public class ComponentWithRequiredComponent : Component { [RequireComponent] public ComponentIdTest RequiredComponent { get; set; } } // https://github.com/Facepunch/sbox-issues/issues/7638 public class ComponentWithPrefabList : Component { [Sandbox.Property] public readonly List Decorations = new(); public class PrafbEntry { // Variables called Prefab caused issues when remapping prefabs to gameobject ids during cloning [KeyProperty] public GameObject Prefab { get; set; } [Range( 0, 1 ), KeyProperty] public float Probability { get; set; } = 1; } }