Spawner/EntitySpawner.cs
/// <summary>
/// Payload for spawning a scripted entity prefab.
/// </summary>
public sealed class EntitySpawner : ISpawner
{
public string DisplayName { get; private set; }
public string FullIdent => Path is not null && !Path.EndsWith( ".sent" ) ? Path : null;
public string Icon => Path;
public string Data => Path;
public BBox Bounds { get; private set; }
public bool IsReady => Entity is not null;
public Task<bool> Loading { get; }
public GameObject Prefab => Entity?.Prefab is not null ? GameObject.GetPrefab( Entity.Prefab.ResourcePath ) : null;
public ScriptedEntity Entity { get; private set; }
public string Path { get; }
public EntitySpawner( string path )
{
Path = path;
DisplayName = System.IO.Path.GetFileNameWithoutExtension( path );
Loading = LoadAsync();
}
private async Task<bool> LoadAsync()
{
// Try local/installed first, then fall back to cloud
Entity = await ResourceLibrary.LoadAsync<ScriptedEntity>( Path );
Entity ??= await Cloud.Load<ScriptedEntity>( Path, true );
if ( Entity is not null )
{
DisplayName = Entity.Title ?? DisplayName;
var prefabScene = SceneUtility.GetPrefabScene( Entity.Prefab );
Bounds = prefabScene.GetLocalBounds();
}
return IsReady;
}
/// <summary>
/// Get a yaw correction so the longest horizontal axis of the bounds aligns with forward.
/// </summary>
private float GetYawCorrection()
{
var size = Bounds.Size;
return size.x > size.y ? 90f : 0f;
}
public void DrawPreview( Transform transform, Material overrideMaterial )
{
if ( !IsReady ) return;
// Draw a bounding box cube as a placeholder preview
var size = Bounds.Size;
if ( size.IsNearlyZero() ) return;
transform.Rotation *= Rotation.FromYaw( GetYawCorrection() );
var center = transform.PointToWorld( Bounds.Center );
var previewTransform = new Transform( center, transform.Rotation, transform.Scale * (size / 50) );
Game.ActiveScene.DebugOverlay.Model( Model.Cube, transform: previewTransform, overlay: false, materialOveride: overrideMaterial );
}
public Task<List<GameObject>> Spawn( Transform transform, Player player )
{
var depth = -Bounds.Mins.z;
transform.Position += transform.Up * depth;
transform.Rotation *= Rotation.FromYaw( GetYawCorrection() );
var go = GameObject.Clone( Entity.Prefab, new CloneConfig { Transform = transform, StartEnabled = false } );
go.Tags.Add( "removable" );
Ownable.Set( go, player.Network.Owner );
go.NetworkSpawn( true, null );
return Task.FromResult( new List<GameObject> { go } );
}
public void PopulateContextMenu( MenuPanel menu, string ident, string metadata )
{
if ( Prefab?.GetComponent<BaseCarryable>( true ) is not null )
{
menu.AddOption( "public", "Spawn in World", () => GameManager.Spawn( ident, metadata, forceWorld: true ) );
menu.AddSpacer();
}
}
}