mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-05-24 23:07:02 -04:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
333 lines
9.2 KiB
C#
333 lines
9.2 KiB
C#
using System.Linq;
|
|
using System.Reflection;
|
|
using Sandbox.MovieMaker;
|
|
using Sandbox.MovieMaker.Properties;
|
|
|
|
namespace Editor.MovieMaker;
|
|
|
|
#nullable enable
|
|
|
|
partial class Session
|
|
{
|
|
public IProjectTrack? GetTrack( GameObject go )
|
|
{
|
|
return Project.Tracks
|
|
.OfType<ProjectReferenceTrack<GameObject>>()
|
|
.FirstOrDefault( x => Binder.Get( x ) is { IsBound: true } binder && binder.Value == go );
|
|
}
|
|
|
|
public IProjectTrack? GetTrack( Component cmp )
|
|
{
|
|
return Project.Tracks
|
|
.OfType<IProjectReferenceTrack>()
|
|
.FirstOrDefault( x => Binder.Get( x ) is { IsBound: true } binder && binder.Value == cmp );
|
|
}
|
|
|
|
public IProjectTrack? GetTrack( GameObject go, string propertyPath )
|
|
{
|
|
return GetTrack( GetTrack( go ), propertyPath );
|
|
}
|
|
|
|
public IProjectTrack? GetTrack( Component cmp, string propertyPath )
|
|
{
|
|
return GetTrack( GetTrack( cmp ), propertyPath );
|
|
}
|
|
|
|
private IProjectTrack? GetTrack( IProjectTrack? parentTrack, string propertyPath )
|
|
{
|
|
while ( parentTrack is not null && propertyPath.Length > 0 )
|
|
{
|
|
var propertyName = propertyPath;
|
|
|
|
// TODO: Hack for anim graph parameters including periods
|
|
|
|
if ( parentTrack.TargetType != typeof( SkinnedModelRenderer.ParameterAccessor ) && propertyPath.IndexOf( '.' ) is var index and > -1 )
|
|
{
|
|
propertyName = propertyPath[..index];
|
|
propertyPath = propertyPath[(index + 1)..];
|
|
}
|
|
else
|
|
{
|
|
propertyPath = string.Empty;
|
|
}
|
|
|
|
parentTrack = parentTrack.Children.FirstOrDefault( x => x.Name == propertyName );
|
|
}
|
|
|
|
return parentTrack;
|
|
}
|
|
|
|
public ProjectSequenceTrack? GetTrack( MovieResource resource )
|
|
{
|
|
return Project.Tracks
|
|
.OfType<ProjectSequenceTrack>()
|
|
.FirstOrDefault( x => x.Blocks.Any( y => y.Resource == resource ) );
|
|
}
|
|
|
|
public IProjectTrack GetOrCreateTrack( GameObject go )
|
|
{
|
|
if ( GetTrack( go ) is { } existing ) return existing;
|
|
|
|
IProjectTrack? parentTrack = null;
|
|
|
|
if ( go.Parent is { } parentGo and not Scene )
|
|
{
|
|
// Procedural bone objects need a parent track
|
|
|
|
if ( (go.Flags & GameObjectFlags.Bone) != 0 )
|
|
{
|
|
parentTrack = GetOrCreateTrack( parentGo );
|
|
}
|
|
|
|
// Otherwise, if parent has a track, use it
|
|
|
|
else
|
|
{
|
|
parentTrack = GetTrack( parentGo );
|
|
}
|
|
}
|
|
|
|
var track = Project.AddReferenceTrack( go.Name, typeof(GameObject), parentTrack );
|
|
|
|
track.ReferenceId = go.Id;
|
|
|
|
Binder.Get( track ).Bind( go );
|
|
|
|
// If we have root tracks for child objects, parent them to the new track
|
|
|
|
foreach ( var child in go.Children )
|
|
{
|
|
if ( GetTrack( child ) is IProjectTrackInternal { Parent: null } childTrack )
|
|
{
|
|
((IProjectTrackInternal)track).AddChild( childTrack );
|
|
}
|
|
}
|
|
|
|
return track;
|
|
}
|
|
|
|
public IProjectTrack GetOrCreateTrack( Component cmp )
|
|
{
|
|
if ( GetTrack( cmp ) is { } existing ) return existing;
|
|
|
|
// Nest component tracks inside the containing game object's track
|
|
var goTrack = GetOrCreateTrack( cmp.GameObject );
|
|
var track = Project.AddReferenceTrack( cmp.GetType().Name, cmp.GetType(), goTrack );
|
|
|
|
track.ReferenceId = cmp.Id;
|
|
|
|
Binder.Get( track ).Bind( cmp );
|
|
|
|
return track;
|
|
}
|
|
|
|
public IProjectTrack GetOrCreateTrack( GameObject go, string propertyPath )
|
|
{
|
|
if ( GetTrack( go, propertyPath ) is { } existing ) return existing;
|
|
|
|
// Nest property tracks inside the containing GameObject's track
|
|
|
|
return GetOrCreateTrack( GetOrCreateTrack( go ), propertyPath );
|
|
}
|
|
|
|
public IProjectTrack GetOrCreateTrack( Component cmp, string propertyPath )
|
|
{
|
|
if ( GetTrack( cmp, propertyPath ) is { } existing ) return existing;
|
|
|
|
// Nest property tracks inside the containing Component's track
|
|
|
|
return GetOrCreateTrack( GetOrCreateTrack( cmp ), propertyPath );
|
|
}
|
|
|
|
public ProjectSequenceTrack GetOrCreateTrack( MovieResource resource )
|
|
{
|
|
if ( GetTrack( resource ) is { } existing ) return existing;
|
|
|
|
return Project.AddSequenceTrack( $"{resource.ResourceName.ToTitleCase()} Sequence" );
|
|
}
|
|
|
|
public IProjectTrack GetOrCreateTrack( IProjectTrack parentTrack, string propertyPath )
|
|
{
|
|
while ( propertyPath.Length > 0 )
|
|
{
|
|
var propertyName = propertyPath;
|
|
|
|
// TODO: Hack for anim graph parameters including periods
|
|
|
|
if ( parentTrack.TargetType != typeof( SkinnedModelRenderer.ParameterAccessor ) && propertyPath.IndexOf( '.' ) is var index and > -1 )
|
|
{
|
|
propertyName = propertyPath[..index];
|
|
propertyPath = propertyPath[(index + 1)..];
|
|
}
|
|
else
|
|
{
|
|
propertyPath = string.Empty;
|
|
}
|
|
|
|
parentTrack = GetOrCreateTrackCore( parentTrack, propertyName );
|
|
}
|
|
|
|
return parentTrack;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a track hierarchy matching the given <paramref name="preset"/>, rooted on <paramref name="rootTrack"/>.
|
|
/// </summary>
|
|
public void LoadPreset( IProjectTrack rootTrack, TrackPresetNode preset )
|
|
{
|
|
var rootGo = (Binder.Get( rootTrack ) as ITrackReference<GameObject>)?.Value;
|
|
|
|
foreach ( var childPreset in preset.Children )
|
|
{
|
|
if ( GetOrCreatePresetTrackCore( rootTrack, childPreset, rootGo ) is { } childTrack )
|
|
{
|
|
LoadPreset( childTrack, childPreset );
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemovePreset( IProjectTrack rootTrack, TrackPresetNode preset )
|
|
{
|
|
foreach ( var childPreset in preset.Children )
|
|
{
|
|
if ( rootTrack.Children.FirstOrDefault( x => x.Name == childPreset.PropertyName ) is not { } childTrack ) continue;
|
|
if ( !childTrack.TargetType.IsAssignableTo( childPreset.PropertyType ) ) continue;
|
|
|
|
RemovePreset( childTrack, childPreset );
|
|
|
|
if ( childTrack.IsEmpty )
|
|
{
|
|
childTrack.Remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
private IProjectTrack? GetOrCreatePresetTrackCore( IProjectTrack rootTrack, TrackPresetNode childPreset, GameObject? rootGameObject )
|
|
{
|
|
if ( rootGameObject is null )
|
|
{
|
|
return GetOrCreateTrack( rootTrack, childPreset.PropertyName );
|
|
}
|
|
|
|
if ( childPreset.PropertyType == typeof( GameObject ) )
|
|
{
|
|
var child = rootGameObject.Children.FirstOrDefault( x => x.Name == childPreset.PropertyName );
|
|
|
|
return child is null ? null : GetOrCreateTrack( child );
|
|
}
|
|
|
|
if ( childPreset.PropertyType.IsAssignableTo( typeof( Component ) ) )
|
|
{
|
|
var component = rootGameObject.Components.FirstOrDefault( childPreset.PropertyType.IsInstanceOfType );
|
|
|
|
return component is null ? null : GetOrCreateTrack( component );
|
|
}
|
|
|
|
return GetOrCreateTrack( rootTrack, childPreset.PropertyName );
|
|
}
|
|
|
|
private IProjectTrack GetOrCreateTrackCore( IProjectTrack parentTrack, string propertyName )
|
|
{
|
|
if ( parentTrack.Children.FirstOrDefault( x => x.Name == propertyName ) is { } existingTrack )
|
|
{
|
|
return existingTrack;
|
|
}
|
|
|
|
if ( Binder.Get( parentTrack ) is not { } parentProperty )
|
|
{
|
|
throw new Exception( "Parent track not registered." );
|
|
}
|
|
|
|
var property = TrackProperty.Create( parentProperty, propertyName )
|
|
?? throw new Exception( $"Unknown property \"{propertyName}\" in type \"{parentProperty.TargetType}\"." );
|
|
|
|
return Project.AddPropertyTrack( property.Name, property.TargetType, parentTrack );
|
|
}
|
|
|
|
private readonly HashSet<SkinnedModelRenderer> _controlledSkinnedModelRenderers = new();
|
|
|
|
private IEnumerable<T> GetControlled<T>()
|
|
where T : Component
|
|
{
|
|
// When rendering, the whole scene is considered part of the movie.
|
|
// Otherwise, we're just previewing playback in the editor, so only consider
|
|
// stuff we've explicitly bound to this movie.
|
|
|
|
return Renderer.IsRendering
|
|
? Player.Scene.GetAll<T>()
|
|
: Binder.GetComponents<T>( Project );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Advance all bound <see cref="SkinnedModelRenderer"/>s by the given <paramref name="deltaTime"/>.
|
|
/// </summary>
|
|
public void AdvanceAnimations( MovieTime deltaTime )
|
|
{
|
|
// Negative deltas aren't supported :(
|
|
|
|
var dt = Math.Min( (float)deltaTime.Absolute.TotalSeconds, 1f );
|
|
|
|
Time.Delta = dt;
|
|
|
|
using var sceneScope = Player.Scene.Push();
|
|
|
|
_controlledSkinnedModelRenderers.Clear();
|
|
|
|
foreach ( var controller in GetControlled<PlayerController>() )
|
|
{
|
|
controller.MovieEditorFixedUpdate();
|
|
|
|
if ( controller.Renderer is not { } renderer ) continue;
|
|
|
|
_controlledSkinnedModelRenderers.Add( renderer );
|
|
|
|
controller.UpdateAnimation( renderer );
|
|
}
|
|
|
|
foreach ( var renderer in GetControlled<SkinnedModelRenderer>() )
|
|
{
|
|
_controlledSkinnedModelRenderers.Add( renderer );
|
|
}
|
|
|
|
foreach ( var renderer in _controlledSkinnedModelRenderers )
|
|
{
|
|
UpdateAnimationPlaybackRate( renderer, dt );
|
|
}
|
|
}
|
|
|
|
private void UpdateAnimationPlaybackRate( SkinnedModelRenderer renderer, float dt )
|
|
{
|
|
if ( renderer.SceneModel is not { } model ) return;
|
|
|
|
if ( dt > 0f && IsEditorScene )
|
|
{
|
|
model.PlaybackRate = renderer.PlaybackRate;
|
|
model.Update( dt );
|
|
}
|
|
|
|
model.PlaybackRate = IsEditorScene ? 0f : 1f;
|
|
}
|
|
}
|
|
|
|
file static class PlayerControllerExtensions
|
|
{
|
|
private static Action<PlayerController> UpdateHeadroom { get; } = typeof( PlayerController )
|
|
.GetMethod( nameof( UpdateHeadroom ), BindingFlags.Instance | BindingFlags.NonPublic )!
|
|
.CreateDelegate<Action<PlayerController>>();
|
|
|
|
private static Action<PlayerController> UpdateFalling { get; } = typeof( PlayerController )
|
|
.GetMethod( nameof( UpdateFalling ), BindingFlags.Instance | BindingFlags.NonPublic )!
|
|
.CreateDelegate<Action<PlayerController>>();
|
|
|
|
public static void MovieEditorFixedUpdate( this PlayerController controller )
|
|
{
|
|
IScenePhysicsEvents physicsEvents = controller;
|
|
|
|
physicsEvents.PrePhysicsStep();
|
|
physicsEvents.PostPhysicsStep();
|
|
|
|
UpdateHeadroom( controller );
|
|
UpdateFalling( controller );
|
|
}
|
|
}
|