mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-02 11:28:19 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
400 lines
14 KiB
C#
400 lines
14 KiB
C#
|
|
using Facepunch.ActionGraphs;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace Sandbox;
|
|
|
|
public partial class GameObject
|
|
{
|
|
// Set only during the cloning process
|
|
// We store this on the GameObject to avoid the need reverse lookup table during the clone process
|
|
private GameObject _cloneOriginal = null;
|
|
private bool _isCloningPrefab = false;
|
|
private GameObject _cloneOriginalRoot = null;
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the passed in GameObject
|
|
/// </summary>
|
|
public GameObject Clone( in CloneConfig cloneConfig )
|
|
{
|
|
Assert.NotNull( Game.ActiveScene, "No Active Scene" );
|
|
|
|
if ( !this.IsValid() )
|
|
{
|
|
throw new InvalidOperationException( "Attempting to clone invalid GameObject" );
|
|
}
|
|
|
|
using var cacheScope = ActionGraph.PushSerializationOptions( new(
|
|
Cache: new ActionGraphCache(),
|
|
WriteCacheReferences: true
|
|
) );
|
|
|
|
using var batchGroup = CallbackBatch.Isolated();
|
|
|
|
// We also need to clone the dependecies that exist within the original hierarchy
|
|
//
|
|
// For example:
|
|
//
|
|
// OriginalA { OriginalComponentA {}, OriginalComponentB { property pointing to OriginalComponentA } }
|
|
//
|
|
// should result in:
|
|
//
|
|
// CloneA { CloneComponentA {}, CloneComponentB { property pointing to CloneComponentA } }
|
|
//
|
|
// To accomplish that we need to keep track which original object is cloned to which clone object
|
|
// We use this information to rewire the refrences when we are deserializing Component or GameObject properties in Component.PostClone
|
|
Dictionary<object, object> originalToClonedObject = new( Children.Count * 4 + Components.Count ); // Rough estimate of hierachy size
|
|
|
|
// 2 Step process:
|
|
|
|
// First:
|
|
// Create all GameObejcts and Components.
|
|
// This ensures we can resolve potential references correctly.
|
|
// Create root Clone
|
|
var clone = new GameObject( false );
|
|
|
|
// TODO, this is here for legacy support yeet at some point
|
|
JsonObject prefabVariablesOverride = null;
|
|
#pragma warning disable CS0612
|
|
if ( cloneConfig.PrefabVariables is not null && cloneConfig.PrefabVariables.Count > 0 )
|
|
{
|
|
prefabVariablesOverride = Json.ToNode( cloneConfig.PrefabVariables ).AsObject();
|
|
}
|
|
#pragma warning restore CS0612
|
|
|
|
|
|
var cloneTransform = cloneConfig.Transform;
|
|
// if we are cloning a prefab preserve the root transform
|
|
if ( this is PrefabScene )
|
|
{
|
|
cloneTransform = cloneTransform.WithRotation( cloneConfig.Transform.Rotation * LocalRotation );
|
|
cloneTransform = cloneTransform.WithScale( cloneConfig.Transform.Scale * LocalScale );
|
|
cloneTransform = cloneTransform.WithPosition( cloneConfig.Transform.Position + LocalPosition );
|
|
}
|
|
else
|
|
{
|
|
// The reason why we only keep the scale of the original is historical.
|
|
cloneTransform = cloneConfig.Transform.WithScale( cloneConfig.Transform.Scale * LocalScale );
|
|
}
|
|
|
|
// Initialize root clone and hierachy
|
|
clone.InitClone( this, cloneTransform, enabled: false, originalToClonedObject, isCloningPrefab: this is PrefabScene, this );
|
|
|
|
Dictionary<Guid, Guid> originalIdToCloneId = new( originalToClonedObject.Count );
|
|
foreach ( var (original, cloned) in originalToClonedObject )
|
|
{
|
|
if ( original is GameObject go )
|
|
originalIdToCloneId[go.Id] = (cloned as GameObject).Id;
|
|
|
|
if ( original is Component comp )
|
|
originalIdToCloneId[comp.Id] = (cloned as Component).Id;
|
|
}
|
|
|
|
|
|
// Set config overrides
|
|
if ( cloneConfig.Parent is not null )
|
|
{
|
|
clone.Parent = cloneConfig.Parent;
|
|
}
|
|
|
|
if ( cloneConfig.Name is not null )
|
|
{
|
|
clone.Name = cloneConfig.Name;
|
|
}
|
|
else
|
|
{
|
|
clone.Name = Name;
|
|
clone.MakeNameUnique();
|
|
}
|
|
|
|
// Not sure if we should do this here, we need to do it because it matches the old behaviour, where the clone is enabled before deserialization is completed.
|
|
// See https://github.com/Facepunch/sbox/issues/1785
|
|
clone.Enabled = cloneConfig.StartEnabled;
|
|
|
|
// Second:
|
|
// Copy all component properties from the original to the clone.
|
|
clone.PostClone( originalToClonedObject, originalIdToCloneId );
|
|
|
|
// Legacy support for restoring prefab vars
|
|
if ( prefabVariablesOverride is not null && clone.IsPrefabInstanceRoot )
|
|
{
|
|
clone.DeserializePrefabVariables( prefabVariablesOverride );
|
|
}
|
|
|
|
return clone;
|
|
}
|
|
|
|
private void InitClone( GameObject original, Transform transform, bool enabled, Dictionary<object, object> originalToClonedObject, bool isCloningPrefab, GameObject cloneOriginalRoot )
|
|
{
|
|
originalToClonedObject[original] = this;
|
|
_cloneOriginal = original;
|
|
_isCloningPrefab = isCloningPrefab;
|
|
_cloneOriginalRoot = cloneOriginalRoot;
|
|
Flags = original.Flags;
|
|
Flags |= GameObjectFlags.Deserializing;
|
|
|
|
// If we're absolute we want to maintain the world transform relative to our parent, not just use the world transform directly
|
|
if ( Flags.Contains( GameObjectFlags.Absolute ) && Parent != null && original.Parent != null )
|
|
{
|
|
// get the local transform relative to their parent
|
|
var originalLocal = original.Parent.WorldTransform.ToLocal( transform );
|
|
|
|
// convert it back to world relative to our parent
|
|
WorldTransform = Parent.WorldTransform.ToWorld( originalLocal );
|
|
}
|
|
else
|
|
{
|
|
LocalTransform = transform;
|
|
}
|
|
|
|
Name = original.Name;
|
|
Enabled = enabled;
|
|
|
|
NetworkMode = original.NetworkMode;
|
|
NetworkInterpolation = original.NetworkInterpolation;
|
|
NetworkOrphaned = original.NetworkOrphaned;
|
|
AlwaysTransmit = original.AlwaysTransmit;
|
|
OwnerTransfer = original.OwnerTransfer;
|
|
Tags.SetFrom( original.Tags );
|
|
|
|
if ( original.IsPrefabInstanceRoot || original is PrefabScene )
|
|
{
|
|
var prefabSource = original.PrefabInstance?.PrefabSource;
|
|
if ( original is PrefabScene prefabScene && prefabScene.Source is PrefabFile prefabFile )
|
|
{
|
|
prefabSource = prefabFile.ResourcePath;
|
|
}
|
|
|
|
var isNested = original.IsNestedPrefabInstanceRoot || (original.IsOutermostPrefabInstanceRoot && _isCloningPrefab);
|
|
InitPrefabInstance( prefabSource, isNested );
|
|
}
|
|
|
|
if ( original.Components.Count > 0 )
|
|
{
|
|
foreach ( var originalComponent in original.Components.GetAll() )
|
|
{
|
|
if ( originalComponent is null ) continue;
|
|
|
|
if ( originalComponent.Flags.Contains( ComponentFlags.NotCloned ) ) continue;
|
|
|
|
if ( originalComponent is MissingComponent missing )
|
|
{
|
|
var clonedMissingComp = new MissingComponent( missing.GetJson() );
|
|
Components.AddMissing( clonedMissingComp );
|
|
clonedMissingComp.InitClone( originalComponent, originalToClonedObject );
|
|
|
|
continue;
|
|
}
|
|
|
|
var clonedComp = Components.Create( originalComponent.GetType(), originalComponent.Enabled );
|
|
clonedComp.InitClone( originalComponent, originalToClonedObject );
|
|
}
|
|
}
|
|
|
|
if ( original.Children.Any() )
|
|
{
|
|
foreach ( var originalChild in original.Children )
|
|
{
|
|
if ( originalChild is null )
|
|
continue;
|
|
|
|
if ( originalChild.Flags.Contains( GameObjectFlags.NotSaved ) )
|
|
continue;
|
|
|
|
// Child gameobjects that are being destroyed don't want to be serialized
|
|
if ( originalChild.IsDestroyed )
|
|
continue;
|
|
|
|
var clonedChild = new GameObject( this, false );
|
|
|
|
clonedChild.InitClone( originalChild, originalChild.LocalTransform, originalChild.Enabled, originalToClonedObject, isCloningPrefab, cloneOriginalRoot );
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs after this clone has been created by a cloned GameObject.
|
|
/// </summary>
|
|
/// <param name="originalToClonedObject">A mapping of original objects to their clones, used for all reference types.</param>
|
|
/// <param name="originalIdToCloneId">A mapping of original GUIDs to cloned GUIDs, used for GameObject and Component references in JSON.</param>
|
|
private void PostClone( Dictionary<object, object> originalToClonedObject, Dictionary<Guid, Guid> originalIdToCloneId )
|
|
{
|
|
// This can happen if setting a component property creates gameobjects.
|
|
// But it really shouldn't, so we print a warning.
|
|
// So far this only happened when we had a bug in CreateBoneObjects/CreateAttachements.
|
|
if ( !_cloneOriginal.IsValid() )
|
|
{
|
|
Log.Warning( "Object created during cloning, which is not linked to an original." );
|
|
return;
|
|
}
|
|
|
|
// When cloning prefabs or prefab instances, we need to ensure the prefab lookup references
|
|
// of the clone point to the correct prefab rather than the prefab instances.
|
|
// There are two main scenarios to handle:
|
|
|
|
// Case 1: Cloning a PrefabScene (direct prefab cloning)
|
|
if ( _isCloningPrefab && _cloneOriginal is PrefabScene )
|
|
{
|
|
// When cloning a prefab directly, use the original-to-clone mapping as is
|
|
Dictionary<Guid, Guid> newMapping = originalIdToCloneId;
|
|
|
|
// For a new prefab clone, initialize with an empty patch
|
|
var instancePatch = new Json.Patch();
|
|
PrefabInstance.InitLookups( newMapping );
|
|
PrefabInstance.InitPatch( instancePatch );
|
|
}
|
|
// Case 2: Cloning an instance that is a prefab root (but not part of a PrefabScene)
|
|
else if ( _cloneOriginal.IsPrefabInstanceRoot )
|
|
{
|
|
// Create a new mapping based on the original's prefab instance mapping
|
|
var originalMapping = _cloneOriginal.PrefabInstance.InstanceToPrefabLookup;
|
|
var newMapping = new Dictionary<Guid, Guid>( originalMapping.Count );
|
|
|
|
// Remap GUIDs to point to the newly cloned instances
|
|
foreach ( var (originalInstanceGuid, originalPrefabGuid) in originalMapping )
|
|
{
|
|
if ( originalIdToCloneId.TryGetValue( originalInstanceGuid, out var clonedInstanceGuid ) )
|
|
{
|
|
newMapping[originalPrefabGuid] = clonedInstanceGuid;
|
|
}
|
|
}
|
|
|
|
PrefabInstance.InitLookups( newMapping );
|
|
|
|
// Copy the existing patch from the original instance
|
|
if ( _cloneOriginal.IsOutermostPrefabInstanceRoot )
|
|
{
|
|
var instancePatch = _cloneOriginal.PrefabInstance.Patch;
|
|
PrefabInstance.InitPatch( instancePatch );
|
|
}
|
|
}
|
|
|
|
// when cloning part of an isntance we may need to convert some instances to a full prefab instance
|
|
var isCloningPartOfPrefabInstance = _cloneOriginal.IsPrefabInstance && !_cloneOriginal.IsOutermostPrefabInstanceRoot && !_isCloningPrefab;
|
|
if ( _cloneOriginalRoot == _cloneOriginal && isCloningPartOfPrefabInstance )
|
|
{
|
|
if ( IsNestedPrefabInstanceRoot )
|
|
{
|
|
PrefabInstance.ConvertNestedToFullPrefabInstance();
|
|
}
|
|
else
|
|
{
|
|
PrefabInstanceData.ConvertTopLevelNestedToFullPrefabInstances( this );
|
|
}
|
|
}
|
|
|
|
if ( Components.Count > 0 )
|
|
{
|
|
Components.ForEach( "PostClone", true, c => c.PostClone( originalToClonedObject, originalIdToCloneId ) );
|
|
}
|
|
|
|
if ( Children.Any() )
|
|
{
|
|
// Need to do numeric iteration because the collection can change (e.g. PropComponent adds a several new components)
|
|
ForEachChild( "PostClone", true, c =>
|
|
{
|
|
// should never happen
|
|
if ( c.IsDestroyed )
|
|
throw new InvalidOperationException( "Cloned GameObject was destroyed before cloning was completed" );
|
|
c.PostClone( originalToClonedObject, originalIdToCloneId );
|
|
} );
|
|
}
|
|
|
|
// Kill temp ref
|
|
_cloneOriginal = null;
|
|
// Reset flag
|
|
_isCloningPrefab = false;
|
|
Flags &= ~GameObjectFlags.Deserializing;
|
|
|
|
Components.ForEach( "OnLoadInternal", true, c => c.OnLoadInternal() );
|
|
Components.ForEach( "OnValidate", true, c => c.Validate() );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clone a prefab from path
|
|
/// </summary>
|
|
public static GameObject Clone( string prefabPath, CloneConfig? config = default )
|
|
{
|
|
var prefabFile = ResourceLibrary.Get<PrefabFile>( prefabPath );
|
|
return Clone( prefabFile, config );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clone a prefab from path
|
|
/// </summary>
|
|
public static GameObject Clone( string prefabPath, Transform transform, GameObject parent = null, bool startEnabled = true, string name = null )
|
|
=> Clone( prefabPath, new CloneConfig( transform, parent, startEnabled, name ) );
|
|
|
|
/// <summary>
|
|
/// Clone a prefab from path
|
|
/// </summary>
|
|
public static GameObject Clone( PrefabFile prefabFile, CloneConfig? config = default )
|
|
{
|
|
if ( prefabFile is null ) return null;
|
|
|
|
var prefabScene = SceneUtility.GetPrefabScene( prefabFile );
|
|
|
|
return prefabScene.Clone( config ?? new CloneConfig( global::Transform.Zero ) );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clone a prefab from path
|
|
/// </summary>
|
|
public static GameObject Clone( PrefabFile prefabFile, Transform transform, GameObject parent = null, bool startEnabled = true, string name = null )
|
|
=> Clone( prefabFile, new CloneConfig( transform, parent, startEnabled, name ) );
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the GameObject
|
|
/// </summary>
|
|
public GameObject Clone( Transform transform, GameObject parent = null, bool startEnabled = true, string name = null )
|
|
=> Clone( new CloneConfig( transform, parent, startEnabled, name ) );
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the GameObject
|
|
/// </summary>
|
|
public GameObject Clone() => Clone( global::Transform.Zero );
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the GameObject
|
|
/// </summary>
|
|
public GameObject Clone( Vector3 position ) => Clone( new Transform( position ) );
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the GameObject
|
|
/// </summary>
|
|
public GameObject Clone( Vector3 position, Rotation rotation ) => Clone( new Transform( position, rotation ) );
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the GameObject
|
|
/// </summary>
|
|
public GameObject Clone( Vector3 position, Rotation rotation, Vector3 scale ) => Clone( new Transform( position, rotation, scale ) );
|
|
|
|
|
|
/// <summary>
|
|
/// Create a unique copy of the GameObject
|
|
/// </summary>
|
|
public GameObject Clone( GameObject parent, Vector3 position, Rotation rotation, Vector3 scale ) => Clone( new Transform( position, rotation, scale ), parent );
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// The low level input of a GameObject.Clone
|
|
/// </summary>
|
|
public struct CloneConfig
|
|
{
|
|
public bool StartEnabled;
|
|
public Transform Transform;
|
|
public string Name;
|
|
public GameObject Parent;
|
|
[Obsolete]
|
|
public Dictionary<string, object> PrefabVariables;
|
|
|
|
public CloneConfig( Transform transform, GameObject parent = null, bool startEnabled = true, string name = null )
|
|
{
|
|
Transform = transform;
|
|
Parent = parent;
|
|
StartEnabled = startEnabled;
|
|
Name = name;
|
|
}
|
|
}
|