Files
sbox-public/engine/Sandbox.Tools/MapEditor/MapDoc/Nodes/MapGameObject.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

227 lines
5.9 KiB
C#

using NativeMapDoc;
using System;
using System.ComponentModel.DataAnnotations;
namespace Editor.MapDoc;
[Display( Name = "Map Game Object" ), Icon( "view_in_ar" )]
public sealed class MapGameObject : MapNode
{
internal CMapGameObject mapGameObjectNative;
internal MapGameObject( HandleCreationData _ ) { }
public GameObject GameObject { get; private set; }
public MapGameObject( MapDocument mapDocument = null, GameObject gameObject = null )
{
ThreadSafe.AssertIsMainThread();
// Default to the active map document if none specificed
mapDocument ??= MapEditor.Hammer.ActiveMap;
Assert.IsValid( mapDocument );
GameObject = gameObject;
using ( var h = IHandle.MakeNextHandle( this ) )
{
mapDocument.native.CreateGameObject( true );
}
}
internal override void OnNativeInit( CMapNode ptr )
{
base.OnNativeInit( ptr );
mapGameObjectNative = (CMapGameObject)ptr;
}
internal override void OnNativeDestroy()
{
base.OnNativeDestroy();
mapGameObjectNative = default;
}
internal override void OnAddedToWorld( MapWorld world )
{
if ( _isInvalid )
{
Remove();
return;
}
// Probably made through new MapGameObject( ... )
if ( GameObject is not null )
{
// GameObject transform is authoritative in this case
TransformChanged();
mapGameObjectNative.SetGUID( GameObject.Id.ToString() );
GameObject.Transform.OnTransformChanged += TransformChanged;
return;
}
// Loaded, sync to existing gameobject
var guid = mapGameObjectNative.GetGUID();
if ( !string.IsNullOrEmpty( guid ) )
{
GameObject = world.Scene.Directory.FindByGuid( Guid.Parse( guid ) );
}
using var sceneScope = world.Scene.Push();
// Are we copying?
if ( _toCopyFrom is not null )
{
GameObject = _toCopyFrom.Clone();
_toCopyFrom = null;
}
GameObject ??= new( true );
GameObject.Transform.OnTransformChanged += TransformChanged;
mapGameObjectNative.SetGUID( GameObject.Id.ToString() );
// Native has transform authority, make sure it's where it wants it
GameObject.WorldTransform = new Transform( Position, Rotation.From( Angles ), Scale );
// If we have a map mesh, make sure we always have this component
if ( GeneratesEntityModelGeometry )
{
var component = GameObject.GetOrAddComponent<HammerMesh>();
GameObject.Components.Move( component, -10 ); // Push to top, even though this is very likely the only component
}
}
internal override void OnRemovedFromWorld( MapWorld world )
{
if ( GameObject is null )
return;
GameObject.Transform.OnTransformChanged -= TransformChanged;
GameObject.DestroyImmediate();
GameObject = null;
}
private bool _isInvalid;
internal override void PostLoadFromFile()
{
// some sanity
var guid = mapGameObjectNative.GetGUID();
if ( string.IsNullOrEmpty( guid ) )
{
Log.Warning( $"Loaded {this} that links to no GameObject from the map, something fucked it. Marking for deletion..." );
_isInvalid = true;
}
}
internal override void PreSaveToFile()
{
mapGameObjectNative.SetGUID( GameObject.Id.ToString() );
}
internal GameObject _toCopyFrom;
/// <summary>
/// Mapdoc creates a bunch of map nodes that live on the clipboard, undo system or just for transforming.
/// These will almost never be part of a World until much later, so anything here should be treated as serialization only.
/// And defer any actual logic until OnAddedToWorld.
/// It's confusing, but it's how the entire datamodel works foundationally for Hammer and mapdoclib.
/// </summary>
internal override void OnCopyFrom( MapNode copyFrom, int flags )
{
// There is a special flags = 1 for undo
// If we are on the undo stack, I think we should store the serialized gameobject
if ( copyFrom is not MapGameObject fromMgo )
return;
_toCopyFrom = fromMgo.GameObject;
// We might be a copy of a copy...
if ( fromMgo.GameObject is null )
_toCopyFrom = fromMgo._toCopyFrom;
// Did we copy from an undo node, update position
if ( GameObject is not null )
{
GameObject.WorldTransform = new Transform( Position, Rotation.From( Angles ), Scale );
}
}
internal override void GetMimeData( DragData data )
{
data.Object = new[] { GameObject };
}
internal string GetGameObjectName()
{
return GameObject?.Name ?? "";
}
internal override void OnParentChanged( MapNode parent )
{
if ( !GameObject.IsValid() ) return;
// Maybe jump up until we find a MapGameObject parent
if ( parent is not MapGameObject parentMgo )
{
GameObject.SetParent( null );
return;
}
var parentGo = parentMgo.GameObject;
GameObject.SetParent( parentGo );
}
private bool _suppressTransformChanged = false;
private bool _suppressNativeTransformChanged = false;
void TransformChanged()
{
if ( _suppressTransformChanged ) return;
_suppressNativeTransformChanged = true;
Position = GameObject.WorldPosition;
Angles = GameObject.WorldRotation.Angles();
Scale = GameObject.WorldScale;
_suppressNativeTransformChanged = false;
// dispatches: event is when the user has changed core attributes (origin/angles/scales) by typing directly in the property editor
native.CoreAttributeChanged();
}
internal override void OnNativeTransformChanged( Vector3 position, Angles angle, Vector3 scale )
{
if ( _suppressNativeTransformChanged ) return;
if ( GameObject is null )
return;
_suppressTransformChanged = true;
GameObject.WorldPosition = position;
GameObject.WorldRotation = angle.ToRotation();
GameObject.WorldScale = scale;
_suppressTransformChanged = false;
}
internal override void OnSetEnabled( bool enabled )
{
if ( GameObject is null )
return;
GameObject.Enabled = enabled;
}
/// <summary>
/// Called when meshes are tied to this GameObject
/// </summary>
internal void OnMeshesTied()
{
if ( GameObject is null )
return;
var component = GameObject.GetOrAddComponent<HammerMesh>();
GameObject.Components.Move( component, -10 ); // Push to top, even though this is very likely the only component
}
}