6746 results

global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sandbox;

namespace SFXR;


[Title( "SFXR Component" )]
[Category( "SFXR" )]
[Icon( "volume_up" )]
public sealed class SFXRComponent : Component
{

	/// <summary>
	/// The base Waveform
	/// (Default: Square)
	/// </summary>
	[Property, Group( "Sound" )]
	public Waveform Waveform { get; set; } = Waveform.Square;

	/// <summary>
	/// The sample rate of the sound
	/// </summary>
	[Property, Group( "Sound" )]
	public SampleRate SampleRate { get; set; } = SampleRate.Hz44100;

	/// <summary>
	/// The bit depth of the sound
	/// </summary>
	// [Property, Group( "Sound" )]
	public BitDepth BitDepth { get; set; } = BitDepth.Bit16;

	/// <summary>
	/// The length of the sound in seconds
	/// </summary>
	[Property, Group( "Sound" ), Range( 0f, 20f, 0.01f )]
	public float Length { get; set; } = 0.5f;

	/// <summary>
	/// The volume of the sound
	/// (Default: 0.5)
	/// </summary>
	[Property, Group( "Sound" ), Range( 0f, 1f, 0.01f )]
	public float MasterVolume { get; set; } = 0.5f;

	[Property, Group( "Frequency" ), Range( 0, 3000f, 1f )]
	float StartFrequency
	{
		get => Frequency.Start;
		set => Frequency.Start = value;
	}

	[Property, Group( "Frequency" ), Range( -3000f, 3000f, 1f )]
	float Slide
	{
		get => Frequency.Slide;
		set => Frequency.Slide = value;
	}

	[Property, Group( "Frequency" ), Range( -3000f, 3000f, 1f )]
	float SlideDelta
	{
		get => Frequency.DeltaSlide;
		set => Frequency.DeltaSlide = value;
	}

	/// <summary>
	/// The random seed
	/// </summary>
	[Property, Group( "Controls" )]
	public long Seed { get; set; } = 0;

	[Property, Group( "Controls" )]
	public SFXRControls Controls { get; set; } = new SFXRControls();

	public SFXRFrequency Frequency { get; set; } = new SFXRFrequency();
	Random _random = new Random();

	List<SFXRNote> NotesPlaying = new();

	/// <summary>
	/// Plays the sound defined by the component
	/// </summary>
	/// <returns>The sound handle of the sound. This can be used to change position, pitch, ect</returns>
	public SoundHandle PlaySound()
	{
		var sfx = Generate( (int)(Length * (int)SampleRate) );
		var handle = sfx.Play();
		// DestroyStream(sfx, Length);
		return handle;
	}

	/// <summary>
	/// Plays the sound defined by the component (Via a frequency trigger. This will play indefinitely until released)
	/// </summary>
	/// <param name="frequency">The frequency of the sound</param>
	/// <param name="volume">The volume of the trigger </param>
	public void TriggerNotePress( float frequency, float volume = 1f )
	{
		foreach ( var note in NotesPlaying.Where( x => x.Frequency == frequency ) )
		{
			note.Release();
		}

		var newNote = new SFXRNote( this, frequency, volume );
		newNote.Trigger();
		NotesPlaying.Add( newNote );
	}

	/// <summary>
	/// Releases a note playing at the given frequency
	/// </summary>
	/// <param name="frequency">The frequency of the sound</param>
	public void TriggerNoteRelease( float frequency )
	{
		foreach ( var note in NotesPlaying.Where( x => x.Frequency == frequency ) )
		{
			note.Release();
		}
	}

	/// <summary>
	/// Releases all notes playing
	/// </summary>
	public void TriggerReleaseAll()
	{
		foreach ( var note in NotesPlaying )
		{
			note.Release();
		}
	}

	/// <summary>
	/// Generates a sound stream from the component
	/// </summary>
	/// <param name="sampleCount">How many samples the stream should be filled with</param>
	/// <returns></returns>
	public SoundStream Generate( int sampleCount )
	{
		List<SFXREffect> effects = new();

		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect || !effect.Enabled ) continue;
			effects.Add( effect );
		}

		return Generate( sampleCount, effects );
	}

	/// <summary>
	/// Generates a sound stream from the component with the given effects
	/// </summary>
	/// <param name="sampleCount">The number of samples</param>
	/// <param name="effects">A list of the effects to apply</param>
	/// <returns></returns>
	public SoundStream Generate( int sampleCount, List<SFXREffect> effects )
	{
		short[] samples = new short[sampleCount];

		float t = 0;
		for ( int i = 0; i < sampleCount; i++ )
		{
			t += 1f / (int)SampleRate;
			short sampleValue = SFXR.GetWaveformSample( Waveform, t, Frequency.GetFrequency( t ) );

			sampleValue = (short)((float)sampleValue * MasterVolume);

			samples[i] = sampleValue;
		}

		foreach ( var effect in effects )
		{
			if ( !effect.Enabled ) continue;
			samples = effect.Apply( samples, this );
		}

		var stream = new SoundStream( (int)SampleRate );
		stream.WriteData( samples );

		return stream;
	}

	/// <summary>
	/// Randomizes the component's parameters
	/// </summary>
	public void Randomize()
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );

		var waveform = Waveform;
		ResetParameters();
		Waveform = waveform;

		Frequency.Start = _random.Next( 10, 3000 );
		if ( _random.Next( 2 ) == 0 ) Frequency.Slide = _random.Next( -3000, 3000 );
		if ( Frequency.Start > 2000 && Frequency.Slide > 200 ) Frequency.Slide = -Frequency.Slide;
		else if ( Frequency.Start < 400 && Frequency.Slide < -50 ) Frequency.Slide = -Frequency.Slide;
		if ( _random.Next( 2 ) == 0 ) Frequency.DeltaSlide = _random.Next( -3000, 3000 );

		SanitizeParameters();
	}

	/// <summary>
	/// Mutates the component's parameters slightly
	/// </summary>
	public void Mutate( float mutation = 0.05f )
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );

		Frequency.Start += _random.Float( -mutation, mutation ) * 1000;
		if ( Frequency.Start > 2000 && Frequency.Slide > 200 ) Frequency.Slide = -Frequency.Slide;
		else if ( Frequency.Start < 400 && Frequency.Slide < -50 ) Frequency.Slide = -Frequency.Slide;
		Frequency.Slide += _random.Float( -mutation, mutation ) * 1000;
		Frequency.DeltaSlide += _random.Float( -mutation, mutation ) * 1000;
		if ( Frequency.Slide < -3000 ) Frequency.Slide = -3000;
		if ( Frequency.Slide > 3000 ) Frequency.Slide = 3000;

		SanitizeParameters();
	}


	public void RandomizePickup()
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );
		ResetParameters();
		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect ) continue;
			effect.Enabled = false;
		}
		var envelope = Components.GetOrCreate<SFXREnvelope>();

		Waveform = (Waveform)_random.Int( 0, 2 );

		Frequency.Start = _random.Float( 0.4f, 0.9f ) * 3000;

		envelope.Enabled = true;
		envelope.Attack = 0;
		envelope.Decay = _random.Float( 0.1f, 0.3f );
		envelope.Sustain = _random.Float( 0f, 0.1f );
		envelope.Release = _random.Float( 0.1f, 0.3f );

		Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release;

	}

	public void RandomizeLaser()
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );
		ResetParameters();
		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect ) continue;
			effect.Enabled = false;
		}
		var envelope = Components.GetOrCreate<SFXREnvelope>();
		var highpass = Components.GetOrCreate<SFXRHighPass>();

		Waveform = (Waveform)_random.Int( 0, 2 );
		if ( Waveform == Waveform.Sine && _random.Next( 2 ) == 0 ) Waveform = (Waveform)_random.Int( 0, 1 );

		Frequency.Start = _random.Float( 0.6f, 0.75f ) * 3000;
		Frequency.Slide = _random.Float( -0.25f, -0.15f ) * 3000;
		envelope.Enabled = true;
		envelope.Attack = 0;
		envelope.Decay = _random.Float( 0f, 0.4f );
		envelope.Sustain = _random.Float( 0.1f, 0.3f );
		envelope.Release = _random.Float( 0.25f, 0.3f );
		Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release;

		if ( _random.Next( 2 ) == 0 )
		{
			highpass.Enabled = true;
			highpass.Cutoff = _random.Float( 0f, 0.3f );
		}
	}

	public void RandomizeExplosion()
	{

		if ( Seed != 0 ) _random = new Random( (int)Seed );
		ResetParameters();
		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect ) continue;
			effect.Enabled = false;
		}
		var envelope = Components.GetOrCreate<SFXREnvelope>();
		var vibrato = Components.GetOrCreate<SFXRVibrato>();

		Waveform = Waveform.Noise;

		if ( _random.Next( 2 ) == 0 )
		{
			Frequency.Start = _random.Float( 0.025f, 0.15f ) * 3000;
			Frequency.Slide = _random.Float( -0.1f, -0.01f ) * 3000;
		}
		else
		{
			Frequency.Start = _random.Float( 0.1f, 0.2f ) * 3000;
			Frequency.Slide = _random.Float( -0.6f, 0.6f ) * 3000;
		}

		if ( _random.Next( 4 ) == 0 ) Frequency.Slide = 0;

		envelope.Enabled = true;
		envelope.Attack = 0;
		envelope.Sustain = _random.Float( 0.1f, 0.4f );
		envelope.Release = _random.Float( 0.1f, 0.3f );
		Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release;


		if ( _random.Next( 2 ) == 0 )
		{
			vibrato.Enabled = true;
			vibrato.Depth = _random.Float( 0f, 0.7f );
			vibrato.Speed = _random.Float( 0f, 60f );
		}
		else
		{
			vibrato.Enabled = false;
		}

		if ( -Frequency.Slide > Frequency.Start )
		{
			Frequency.Slide = -Frequency.Start;
		}
	}

	public void RandomizePowerup()
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );
		ResetParameters();
		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect ) continue;
			effect.Enabled = false;
		}
		var envelope = Components.GetOrCreate<SFXREnvelope>();
		var vibrato = Components.GetOrCreate<SFXRVibrato>();

		if ( _random.Next( 2 ) == 0 )
		{
			Waveform = Waveform.Sawtooth;
		}

		if ( _random.Next( 2 ) == 0 )
		{
			Frequency.Start = _random.Float( 0.2f, 0.5f ) * 3000;
			Frequency.Slide = _random.Float( 0.1f, 0.5f ) * 3000;
		}
		else
		{
			Frequency.Start = _random.Float( 0.25f, 0.5f ) * 3000;
			Frequency.Slide = _random.Float( 0.05f, 0.25f ) * 3000;
			if ( _random.Next( 2 ) == 0 )
			{
				vibrato.Enabled = true;
				vibrato.Depth = _random.Float( 0, 0.7f );
				vibrato.Speed = _random.Float( 0, 60f );
			}
			else
			{
				vibrato.Enabled = false;
			}
		}

		if ( -Frequency.Slide > Frequency.Start )
		{
			Frequency.Slide = -Frequency.Start;
		}

		envelope.Enabled = true;
		envelope.Attack = 0;
		envelope.Sustain = _random.Float( 0f, 0.4f );
		envelope.Release = _random.Float( 0.1f, 0.5f );
		Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release;
	}

	public void RandomizeHit()
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );
		ResetParameters();
		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect ) continue;
			effect.Enabled = false;
		}
		var envelope = Components.GetOrCreate<SFXREnvelope>();
		var highpass = Components.GetOrCreate<SFXRHighPass>();

		Waveform = (Waveform)_random.Int( 0, 3 );
		if ( Waveform == Waveform.Sine )
		{
			Waveform = Waveform.Noise;
		}

		Frequency.Start = _random.Float( 0.1f, 0.5f ) * 3000;
		Frequency.Slide = _random.Float( -0.7f, -0.3f ) * 3000;

		if ( -Frequency.Slide > Frequency.Start )
		{
			Frequency.Slide = -Frequency.Start;
		}

		envelope.Enabled = true;
		envelope.Attack = 0;
		envelope.Decay = 0;
		envelope.Sustain = _random.Float( 0.025f, 0.1f );
		envelope.Release = _random.Float( 0.1f, 0.3f );
		Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release;

		if ( _random.Next( 2 ) == 0 )
		{
			highpass.Enabled = true;
			highpass.Cutoff = _random.Float( 0f, 0.3f );
		}
		else
		{
			highpass.Enabled = false;
		}
	}

	public void RandomizeJump()
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );
		ResetParameters();
		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect ) continue;
			effect.Enabled = false;
		}
		var envelope = Components.GetOrCreate<SFXREnvelope>();

		Waveform = Waveform.Square;

		Frequency.Start = _random.Float( 0.3f, 0.6f ) * 3000;
		Frequency.Slide = _random.Float( 0.1f, 0.3f ) * 3000;

		if ( -Frequency.Slide > Frequency.Start )
		{
			Frequency.Slide = -Frequency.Start;
		}

		envelope.Enabled = true;
		envelope.Attack = 0;
		envelope.Sustain = _random.Float( 0.1f, 0.4f );
		envelope.Release = _random.Float( 0.1f, 0.3f );
		Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release;
	}

	public void RandomizeBlip()
	{
		if ( Seed != 0 ) _random = new Random( (int)Seed );
		ResetParameters();
		foreach ( var component in GameObject.Components.GetAll() )
		{
			if ( component is not SFXREffect effect ) continue;
			effect.Enabled = false;
		}
		var envelope = Components.GetOrCreate<SFXREnvelope>();

		Waveform = Waveform.Square;

		Frequency.Start = _random.Float( 0.2f, 0.6f ) * 3000;

		envelope.Enabled = true;
		envelope.Attack = 0;
		envelope.Decay = _random.Float( 0.1f, 0.2f );
		envelope.Sustain = _random.Float( 0.025f, 0.1f );
		envelope.Release = _random.Float( 0.1f, 0.3f );
		Length = envelope.Attack + envelope.Sustain + envelope.Decay + envelope.Release;
	}


	public void ResetParameters()
	{
		Waveform = Waveform.Square;
		SampleRate = SampleRate.Hz44100;
		BitDepth = BitDepth.Bit16;
		Length = 0.5f;
		MasterVolume = 0.5f;
		Frequency = new SFXRFrequency();
		Controls = new SFXRControls();
	}

	void SanitizeParameters()
	{

	}

	protected override void OnUpdate()
	{
		foreach ( var note in NotesPlaying )
		{
			note.Update();

			// if (!note.IsPlaying)
			// {
			// 	note.DestroyStreams();
			// }
		}

		NotesPlaying.RemoveAll( x => !x.IsPlaying );
	}
}
using Editor;
using Sandbox;
using Sandbox.Helpers;
using System.Collections.Generic;
using System.Linq;

namespace SFXR.Editor;

[CustomEditor( typeof( List<SFXRSequencer.Note> ) )]
public class SFXRNotesListControlWidget : ControlWidget
{
    private SerializedCollection Collection;

    private Layout Content;

    private Button addButton;

    public override bool SupportsMultiEdit => false;

    SFXRSequencer Sequencer;

    public SFXRNotesListControlWidget( SerializedProperty property )
        : base( property )
    {
        SetSizeMode( SizeMode.Ignore, SizeMode.Ignore );

        base.Layout = Layout.Column();
        base.Layout.Spacing = 2f;
        if ( property.TryGetAsObject( out var obj ) && obj is SerializedCollection collection )
        {
            if ( property.Parent.Targets.First() is SFXRSequencer sequencer )
            {
                Sequencer = sequencer;
            }

            Collection = collection;
            Collection.OnEntryAdded = Rebuild;
            Collection.OnEntryRemoved = Rebuild;
            Content = Layout.Column();
            base.Layout.Add( Content );
            Layout layout = base.Layout.AddRow();
            layout.Margin = 8;
            layout.AddStretchCell();
            addButton = layout.Add( new Button( "Add Note" )
            {
                ToolTip = "Add new note",
            } );
            addButton.MinimumWidth = 200;
            addButton.Clicked = () => AddEntry();
            layout.AddStretchCell();
            Rebuild();
        }
    }

    public void Rebuild()
    {
        Content.Clear( deleteWidgets: true );
        Content.Margin = 0f;
        Layout layout = Layout.Column();
        layout.Spacing = 2f;
        int num = 0;
        int count = Collection.Count();
        for ( int i = 0; i < count; i++ )
        {
            var item = Collection.ElementAt( i );
            int index = num;
            var itemLayout = Layout.Row();
            itemLayout.Spacing = 4f;
            // try to get object
            if ( item.TryGetAsObject( out var obj ) )
            {
                var thing = new SFXRNoteSheet( obj );
                itemLayout.Add( thing );
            }
            else
            {
                var thing = ControlWidget.Create( item );
                thing.MinimumHeight = 100;
                itemLayout.Add( thing );
            }

            var buttonLayout = Layout.Column();
            if ( i > 0 )
            {
                buttonLayout.Add( new IconButton( "arrow_upward", delegate
                {
                    MoveUp( index );
                } )
                {
                    Background = Color.Transparent,
                    FixedWidth = ControlWidget.ControlRowHeight,
                    FixedHeight = ControlWidget.ControlRowHeight,
                    ToolTip = "Move note up"
                } );
            }
            else
            {
                buttonLayout.AddSpacingCell( 25 );
            }

            buttonLayout.Add( new IconButton( "delete", delegate
            {
                RemoveEntry( index );
            } )
            {
                Background = Color.Red,
                FixedWidth = ControlWidget.ControlRowHeight,
                FixedHeight = ControlWidget.ControlRowHeight,
                ToolTip = "Delete note"
            } );

            if ( i < count - 1 )
            {
                buttonLayout.Add( new IconButton( "arrow_downward", delegate
                {
                    MoveDown( index );
                } )
                {
                    Background = Color.Transparent,
                    FixedWidth = ControlWidget.ControlRowHeight,
                    FixedHeight = ControlWidget.ControlRowHeight,
                    ToolTip = "Move note down"
                } );
            }
            else
            {
                buttonLayout.AddSpacingCell( 25 );
            }

            itemLayout.Add( buttonLayout );
            layout.Add( itemLayout );
            num++;
        }

        MinimumHeight = 50 + (num * 105);

        Content.Add( layout );
        Content.Margin = ((num > 0) ? 3 : 0);
    }

    private void AddEntry()
    {
        Collection.Add( new SFXRSequencer.Note() );
    }

    private void RemoveEntry( int index )
    {
        Collection.RemoveAt( index );
    }

    private void MoveUp( int index )
    {
        // Move the index up in Sequencer.Notes list
        if ( index > 0 )
        {
            var note = Sequencer.Notes[index];
            Sequencer.Notes.RemoveAt( index );
            Sequencer.Notes.Insert( index - 1, note );
        }

        Rebuild();
    }

    private void MoveDown( int index )
    {
        // Move the index down in Sequencer.Notes list
        if ( index < Sequencer.Notes.Count - 1 )
        {
            var note = Sequencer.Notes[index];
            Sequencer.Notes.RemoveAt( index );
            Sequencer.Notes.Insert( index + 1, note );
        }

        Rebuild();
    }

    protected override void OnPaint()
    {

    }

    public void AddEffectDialog( Button source )
    {
        var s = new SFXREffectTypeSelector( this );
        s.OnSelect += ( t ) => AddEffect( t );
        s.OpenAt( source.ScreenRect.BottomLeft, animateOffset: new Vector2( 0, -4 ) );
        s.FixedWidth = source.Width;
    }

    void AddEffect( TypeDescription type )
    {
        if ( !type.TargetType.IsAssignableTo( typeof( SFXREffect ) ) )
        {
            Log.Error( $"Type {type.TargetType} is not assignable to {typeof( SFXREffect )}" );
            return;
        }

        SFXREffect effect = type.Create<SFXREffect>();
        Collection.Add( effect );

        Log.Info( effect );
    }


}
using System;
using System.Collections.Generic;
using Sandbox;

namespace SFXR;

[Title( "ADSR Envelope" )]
[Category( "SFXR Effects" )]
[Icon( "mail_outline" )]
public class SFXREnvelope : SFXREffect
{
    /// <summary>
    /// Time the sound takes to reach its peak amplitude
    /// (Default: 0)
    /// </summary>
    [Property, Range( 0, 10 )]
    public float Attack { get; set; } = 0;

    /// <summary>
    /// The time taken for the sound to fade to the sustain level
    /// </summary>
    [Property, Range( 0, 10 )]
    public float Decay { get; set; } = 0;

    /// <summary>
    /// The level maintained until release is triggered
    /// (Default: 1)
    /// </summary>
    [Property, Range( 0, 1 )]
    public float Sustain { get; set; } = 1f;

    /// <summary>
    /// The time taken for the sound to fade to zero after the sustain
    /// (Default: 0.3)
    /// </summary>
    [Property, Range( 0, 10 )]
    public float SustainTime { get; set; } = 0.3f;

    /// <summary>
    /// The time taken for the sound to fade to zero after the release
    /// (Default: 0.4)
    /// </summary>
    [Property, Range( 0, 10 )]
    public float Release { get; set; } = 0.4f;

    /// <summary>
    /// Returns the amplitude of the envelope at a given time
    /// </summary>
    /// <param name="time">Time in seconds</param>
    /// <returns>Amplitude of the envelope at the given time</returns>
    public float GetAmplitude( float time )
    {
        return GetCurve().Evaluate( time / GetLength() );
    }

    public override short[] Apply( short[] samples, SFXRComponent sound )
    {
        // Calculate the envelope amplitude for each sample
        for ( int i = 0; i < samples.Length; i++ )
        {
            float t = i / (float)sound.SampleRate;
            float amplitude = GetAmplitude( t );
            samples[i] = (short)(samples[i] * amplitude);
        }

        return samples;
    }

    public float GetLength()
    {
        return Attack + Decay + SustainTime + Release;
    }

    public Curve GetCurve()
    {
        Curve curve = new();

        List<Vector2> points = new();

        // Add the attack curve
        points.Add( new Vector2( 0, 0 ) );
        points.Add( new Vector2( Attack, 1 ) );

        // Add the decay curve
        points.Add( new Vector2( Attack + Decay, Sustain ) );

        // Add the sustain curve
        points.Add( new Vector2( Attack + Decay + SustainTime, Sustain ) );

        // Add the release curve
        points.Add( new Vector2( Attack + Decay + SustainTime + Release, 0 ) );

        // Normalize the curve to 0-1 in the x
        for ( int i = 0; i < points.Count; i++ )
        {
            points[i] = new Vector2( points[i].x / (Attack + Decay + SustainTime + Release), points[i].y );
        }

        // Add the points to the curve
        foreach ( var point in points )
        {
            curve.AddPoint( point.x, point.y );
        }

        return curve;
    }
}
using Sandbox;

public sealed class SceneTrigger : Component, Component.ITriggerListener
{
	[Property] public SceneFile SceneFile { get; set; }
	protected override void OnUpdate()
	{

	}

	void ITriggerListener.OnTriggerEnter(Sandbox.Collider other)
	{
		if (other.GameObject.Parent.Tags.Has("player") || other.GameObject.Tags.Has("boat"))
		{
			Game.ActiveScene.Load(SceneFile);
		}
	}

	void ITriggerListener.OnTriggerExit(Sandbox.Collider other)
	{

	}
}
using System.Collections.Generic;

namespace Sandbox;

/// <summary>
/// How to use the system: 
/// <code>
/// public sealed class ExampleComponent : Component
/// {
///		// Reference to the system.
///		private FixedUpdateInputSystem _fixedInput;
///		
///		protected override void Start()
///		{
///			// Get the reference like this:
///			_fixedInput = Scene.GetSystem&lt;FixedUpdateInputSystem&gt;();
///			
///			base.OnStart();
///		}
///		
///		protected override void OnFixedUpdate()
///		{
///			// Query for input like usual.
///			if( _fixedInput.Pressed("jump") )
///			{
///				Log.Info("Jumped");
///			}
///			
///			base.OnFixedUpdate();
///		}
/// }
/// </code>
/// </summary>
public sealed class FixedUpdateInputSystem : GameObjectSystem
{
	private struct FixedUpdateInputBuffer
	{
		private class State
		{
			public bool Held;
			public bool Pressed;
			public bool Released;
		}

		private Dictionary<string, State> _actionStates;

		public FixedUpdateInputBuffer()
		{
			_actionStates = new Dictionary<string, State>();

			foreach ( var b in Input.GetActions() )
			{
				_actionStates[b.Name.ToLowerInvariant()] = new State();
			}
		}

		/// <summary>
		/// Call from a <see cref="Component.OnUpdate"/> method
		/// to update the states of the actions.
		/// </summary>
		public void OnUpdate()
		{
			foreach ( var (name, state) in _actionStates )
			{
				if ( Input.Down( name ) )
					_actionStates[name].Held = true;

				if ( Input.Pressed( name ) )
					_actionStates[name].Pressed = true;

				if ( Input.Released( name ) )
					_actionStates[name].Released = true;
			}
		}

		/// <summary>
		/// Call from a <see cref="Component.OnFixedUpdate"/>
		/// method to get the <see cref="State.Held"/> state of this action.
		/// </summary>
		/// <param name="action">The action name (case insensitive).</param>
		/// <returns></returns>
		/// 
		public bool Held( string action )
		{
			return _actionStates[action.ToLowerInvariant()].Held;
		}

		/// <summary>
		/// Call from a <see cref="Component.OnFixedUpdate"/>
		/// method to get the <see cref="State.Pressed"/> state of this action.
		/// </summary>
		/// <param name="action">The action name (case insensitive).</param>
		/// <returns></returns>
		public bool Pressed( string action )
		{
			return _actionStates[action.ToLowerInvariant()].Pressed;
		}

		/// <summary>
		/// Call from a <see cref="Component.OnFixedUpdate"/>
		/// method to get the <see cref="State.Pressed"/> state of this action.
		/// </summary>
		/// <param name="action">The action name (case insensitive).</param>
		/// <returns></returns>
		public bool Released( string action )
		{
			return _actionStates[action.ToLowerInvariant()].Released;
		}

		/// <summary>
		/// Call at the end of your <see cref="Component.OnFixedUpdate"/> method
		/// to clear the state of the struct and reset.
		/// </summary>
		public void Clear()
		{
			foreach ( var actionName in _actionStates.Keys )
			{
				_actionStates[actionName].Held = false;
				_actionStates[actionName].Pressed = false;
			}
		}
	}

	private FixedUpdateInputBuffer _buffer;

	public FixedUpdateInputSystem( Scene scene ) : base( scene )
	{
		_buffer = new();
		Listen( Stage.StartUpdate, int.MinValue, OnStartUpdate, "FUIB.OnStartUpdate" );
		Listen( Stage.FinishFixedUpdate, int.MaxValue, OnFinishFixedUpdate, "FUIB.OnFinishFixedUpdate" );
	}

	private void OnStartUpdate()
	{
		_buffer.OnUpdate();
	}

	private void OnFinishFixedUpdate()
	{
		_buffer.Clear();
	}

	/// <summary>
	/// Is the action currently held down?
	/// </summary>
	/// <param name="action">The action name (case insensitive).</param>
	/// <returns></returns>
	/// 
	public bool Held( string action ) => _buffer.Held( action );

	/// <summary>
	/// Was the action pressed?
	/// </summary>
	/// <param name="action">The action name (case insensitive).</param>
	/// <returns></returns>
	public bool Pressed( string action ) => _buffer.Pressed( action );

	/// <summary>
	/// Was the action released?
	/// </summary>
	/// <param name="action">The action name (case insensitive).</param>
	/// <returns></returns>
	public bool Released( string action ) => _buffer.Released( action );
}
using Editor;

public static class MyEditorMenu
{
	[Menu( "Editor", "CrosshairBuilder/My Menu Option" )]
	public static void OpenMyMenu()
	{
		EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
	}
}

public sealed class PlayerPusher : Component
{
	[Property] public float Radius { get; set; } = 100;

	protected override void DrawGizmos()
	{
		base.DrawGizmos();

		Gizmo.Draw.LineSphere( Vector3.Zero, Radius );
	}

	public static Vector3 GetPushVector( in Vector3 position, Scene scene, GameObject ignore )
	{
		Vector3 vec = default;

		foreach ( var pusher in scene.GetAllComponents<PlayerPusher>() )
		{
			if ( pusher.GameObject.IsAncestor( ignore ) )
				continue;

			pusher.Collect( position, ref vec );
		}

		return vec;
	}

	private void Collect( Vector3 position, ref Vector3 output )
	{
		var delta = (position - Transform.Position);
		if ( delta.Length > Radius ) return;

		delta.z = 0; // ignore z

		var distanceDelta = (delta.Length / Radius);

		output += delta.Normal * (1.0f - distanceDelta);
	}
}
using Sandbox;
using System.Collections.Generic;

namespace EZCameraShake
{
    public class CameraShaker : Component
    {
        /// <summary>
        /// The single instance of the CameraShaker in the current scene. Do not use if you have multiple instances.
        /// </summary>
        public static CameraShaker Instance;
        static Dictionary<string, CameraShaker> instanceList = new Dictionary<string, CameraShaker>();

        /// <summary>
        /// The default position influcence of all shakes created by this shaker.
        /// </summary>
        [Property] public Vector3 DefaultPosInfluence = new Vector3(0.15f, 0.15f, 0.15f);
		/// <summary>
		/// The default rotation influcence of all shakes created by this shaker.
		/// </summary>
		[Property]  public Vector3 DefaultRotInfluence = new Vector3(1, 1, 1);
		/// <summary>
		/// Offset that will be applied to the camera's default (0,0,0) rest position
		/// </summary>
		[Property] public Vector3 RestPositionOffset = new Vector3(0, 0, 0);
		/// <summary>
		/// Offset that will be applied to the camera's default (0,0,0) rest rotation
		/// </summary>
		[Property] public Vector3 RestRotationOffset = new Vector3(0, 0, 0);

        Vector3 posAddShake, rotAddShake;

        List<CameraShakeInstance> cameraShakeInstances = new List<CameraShakeInstance>();

        protected override void OnAwake()
        {
            Instance = this;
            instanceList.Add(GameObject.Name, this);
        }

		protected override void OnUpdate()
        {
            posAddShake = Vector3.Zero;
            rotAddShake = Vector3.Zero;

            for (int i = 0; i < cameraShakeInstances.Count; i++)
            {
                if (i >= cameraShakeInstances.Count)
                    break;

                CameraShakeInstance c = cameraShakeInstances[i];

                if (c.CurrentState == CameraShakeState.Inactive && c.DeleteOnInactive)
                {
                    cameraShakeInstances.RemoveAt(i);
                    i--;
                }
                else if (c.CurrentState != CameraShakeState.Inactive)
                {
                    posAddShake += CameraUtilities.MultiplyVectors(c.UpdateShake(), c.PositionInfluence);
                    rotAddShake += CameraUtilities.MultiplyVectors(c.UpdateShake(), c.RotationInfluence);
                }
            }

            Transform.LocalPosition = (posAddShake) + RestPositionOffset;
			Vector3 thing = (rotAddShake / 100) + RestRotationOffset;

			Transform.LocalRotation = new Angles(thing.x, thing.y, thing.z);
        }

        /// <summary>
        /// Gets the CameraShaker with the given name, if it exists.
        /// </summary>
        /// <param name="name">The name of the camera shaker instance.</param>
        /// <returns></returns>
        public static CameraShaker GetInstance(string name)
        {
            CameraShaker c;

            if (instanceList.TryGetValue(name, out c))
                return c;

            Log.Error("CameraShake " + name + " not found!");

            return null;
        }

        /// <summary>
        /// Starts a shake using the given preset.
        /// </summary>
        /// <param name="shake">The preset to use.</param>
        /// <returns>A CameraShakeInstance that can be used to alter the shake's properties.</returns>
        public CameraShakeInstance Shake(CameraShakeInstance shake)
        {
            cameraShakeInstances.Add(shake);
            return shake;
        }

        /// <summary>
        /// Shake the camera once, fading in and out  over a specified durations.
        /// </summary>
        /// <param name="magnitude">The intensity of the shake.</param>
        /// <param name="roughness">Roughness of the shake. Lower values are smoother, higher values are more jarring.</param>
        /// <param name="fadeInTime">How long to fade in the shake, in seconds.</param>
        /// <param name="fadeOutTime">How long to fade out the shake, in seconds.</param>
        /// <returns>A CameraShakeInstance that can be used to alter the shake's properties.</returns>
        public CameraShakeInstance ShakeOnce(float magnitude, float roughness, float fadeInTime, float fadeOutTime)
        {
            CameraShakeInstance shake = new CameraShakeInstance(magnitude, roughness, fadeInTime, fadeOutTime);
            shake.PositionInfluence = DefaultPosInfluence;
            shake.RotationInfluence = DefaultRotInfluence;
            cameraShakeInstances.Add(shake);

            return shake;
        }

        /// <summary>
        /// Shake the camera once, fading in and out over a specified durations.
        /// </summary>
        /// <param name="magnitude">The intensity of the shake.</param>
        /// <param name="roughness">Roughness of the shake. Lower values are smoother, higher values are more jarring.</param>
        /// <param name="fadeInTime">How long to fade in the shake, in seconds.</param>
        /// <param name="fadeOutTime">How long to fade out the shake, in seconds.</param>
        /// <param name="posInfluence">How much this shake influences position.</param>
        /// <param name="rotInfluence">How much this shake influences rotation.</param>
        /// <returns>A CameraShakeInstance that can be used to alter the shake's properties.</returns>
        public CameraShakeInstance ShakeOnce(float magnitude, float roughness, float fadeInTime, float fadeOutTime, Vector3 posInfluence, Vector3 rotInfluence)
        {
            CameraShakeInstance shake = new CameraShakeInstance(magnitude, roughness, fadeInTime, fadeOutTime);
            shake.PositionInfluence = posInfluence;
            shake.RotationInfluence = rotInfluence;
            cameraShakeInstances.Add(shake);

            return shake;
        }

        /// <summary>
        /// Start shaking the camera.
        /// </summary>
        /// <param name="magnitude">The intensity of the shake.</param>
        /// <param name="roughness">Roughness of the shake. Lower values are smoother, higher values are more jarring.</param>
        /// <param name="fadeInTime">How long to fade in the shake, in seconds.</param>
        /// <returns>A CameraShakeInstance that can be used to alter the shake's properties.</returns>
        public CameraShakeInstance StartShake(float magnitude, float roughness, float fadeInTime)
        {
            CameraShakeInstance shake = new CameraShakeInstance(magnitude, roughness);
            shake.PositionInfluence = DefaultPosInfluence;
            shake.RotationInfluence = DefaultRotInfluence;
            shake.StartFadeIn(fadeInTime);
            cameraShakeInstances.Add(shake);
            return shake;
        }

        /// <summary>
        /// Start shaking the camera.
        /// </summary>
        /// <param name="magnitude">The intensity of the shake.</param>
        /// <param name="roughness">Roughness of the shake. Lower values are smoother, higher values are more jarring.</param>
        /// <param name="fadeInTime">How long to fade in the shake, in seconds.</param>
        /// <param name="posInfluence">How much this shake influences position.</param>
        /// <param name="rotInfluence">How much this shake influences rotation.</param>
        /// <returns>A CameraShakeInstance that can be used to alter the shake's properties.</returns>
        public CameraShakeInstance StartShake(float magnitude, float roughness, float fadeInTime, Vector3 posInfluence, Vector3 rotInfluence)
        {
            CameraShakeInstance shake = new CameraShakeInstance(magnitude, roughness);
            shake.PositionInfluence = posInfluence;
            shake.RotationInfluence = rotInfluence;
            shake.StartFadeIn(fadeInTime);
            cameraShakeInstances.Add(shake);
            return shake;
        }

        /// <summary>
        /// Gets a copy of the list of current camera shake instances.
        /// </summary>
        public List<CameraShakeInstance> ShakeInstances
        { get { return new List<CameraShakeInstance>(cameraShakeInstances); } }

		protected override void OnDestroy()
        {
            instanceList.Remove(GameObject.Name);
        }
    }
}
using System.Collections.Generic;
using System.Linq;

namespace Sandbox.Events;

/// <summary>
/// Generate an ordering based on a set of first-most and last-most items, and
/// individual constraints between pairs of items. All first-most items will be
/// ordered before all last-most items, and any other items will be put in the
/// middle unless forced to be elsewhere by a constraint.
/// </summary>
internal class SortingHelper
{
	public record struct SortConstraint( int EarlierIndex, int LaterIndex )
	{
		public SortConstraint Complement => new ( LaterIndex, EarlierIndex );
	}

	private readonly int _itemCount;

	private readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();

	private readonly HashSet<int> _first = new HashSet<int>();
	private readonly HashSet<int> _last = new HashSet<int>();

	public SortingHelper( int itemCount )
	{
		_itemCount = itemCount;
	}

	public void AddConstraint( int earlierIndex, int laterIndex )
	{
		_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );
	}

	public void AddFirst( int earlierIndex )
	{
		_first.Add( earlierIndex );
	}

	public void AddLast( int laterIndex )
	{
		_last.Add( laterIndex );
	}

	public bool Sort( List<int> result, out SortConstraint invalidConstraint )
	{
		var middle = new HashSet<int>();

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !_first.Contains( index ) && !_last.Contains( index ) )
				middle.Add( index );
		}

		var allConstraints = new HashSet<SortConstraint>();
		var newConstraints = new Queue<SortConstraint>();
		var beforeDict = new Dictionary<int, HashSet<int>>();
		var afterDict = new Dictionary<int, HashSet<int>>();

		bool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )
		{
			constraint = new SortConstraint( earlierIndex, laterIndex );

			if ( allConstraints.Contains( constraint.Complement ) )
				return false;

			if ( !allConstraints.Add( constraint ) )
				return true;

			newConstraints.Enqueue( constraint );

			if ( !beforeDict.TryGetValue( earlierIndex, out var before ) )
				beforeDict.Add( earlierIndex, before = new HashSet<int>() );

			if ( !afterDict.TryGetValue( laterIndex, out var after ) )
				afterDict.Add( laterIndex, after = new HashSet<int>() );

			before.Add( laterIndex );
			after.Add( earlierIndex );

			return true;
		}

		// Add initial constraints

		foreach ( var initialConstraint in _initialConstraints )
		{
			if ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )
				return false;
		}

		// Everything in _first should be before everything in _last

		foreach ( var earlierIndex in _first )
		{
			foreach ( var laterIndex in _last )
			{
				if ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )
					return false;
			}
		}

		// Keep propagating constraints until nothing changes

		while ( newConstraints.TryDequeue( out var nextConstraint ) )
		{
			// if a < b, and b < c, then a < c etc

			if ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )
			{
				foreach ( var laterIndex in before )
				{
					if ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )
						return false;
				}
			}

			if ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )
			{
				foreach ( var earlierIndex in after )
				{
					if ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )
					{
						return false;
					}
				}
			}
		}

		// Now if we have any items that aren't using GroupOrder.First, and haven't
		// determined that they are ordered before another item with GroupOrder.First,
		// we can safely order them after all GroupOrder.First items. And vice versa.

		foreach ( var middleIndex in middle )
		{
			var isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )
				&& before.Any( x => _first.Contains( x ) );

			var isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )
				&& after.Any( x => _last.Contains( x ) );

			if ( !isBeforeAnyFirst )
			{
				foreach ( var earlierIndex in _first )
					AddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );
			}

			if ( !isAfterAnyLast )
			{
				foreach ( var laterIndex in _last )
					AddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );
			}
		}

		// Now lets add items to the final ordering if all items that should be sorted
		// before them are already added to that ordering. We'll implement this by choosing
		// items that have an empty list / don't appear in afterDict, and update that
		// dictionary as we go.

		var earliestRemaining = new Queue<int>();

		// First, seed the queue with everything that's already not ordered after anything

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !afterDict.ContainsKey( index ) )
			{
				earliestRemaining.Enqueue( index );
			}
		}

		result.Clear();

		while ( earliestRemaining.TryDequeue( out var nextIndex ) )
		{
			result.Add( nextIndex );

			foreach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )
				? laterIndices : Enumerable.Empty<int>() )
			{
				var beforeLater = afterDict[laterIndex];
				beforeLater.Remove( nextIndex );

				if ( beforeLater.Count == 0 )
					earliestRemaining.Enqueue( laterIndex );
			}
		}

		invalidConstraint = default;
		return result.Count == _itemCount;
	}
}
using Braxnet;
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			// var go = new GameObject();
			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
			Assert.IsTrue( scene.Directory.FindByName( "LibraryTestComponent" ) != null );
		}
	}

}

[Autoload]
public class LibraryTestComponent : Component
{
	
}
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Sandbox.Events;

/// <summary>
/// Interface for event payloads that can be listened for by <see cref="IGameEventHandler{T}"/>s.
/// </summary>
public interface IGameEvent { }

/// <summary>
/// Interface for components that handle game events with a payload of type <see cref="T"/>.
/// </summary>
/// <typeparam name="T">Event payload type.</typeparam>
public interface IGameEventHandler<in T>
	where T : IGameEvent
{
	/// <summary>
	/// Called when an event with payload of type <see cref="T"/> is dispatched on a <see cref="GameObject"/>
	/// that contains this component, including on a descendant.
	/// </summary>
	/// <param name="eventArgs">Event payload.</param>
	void OnGameEvent( T eventArgs );
}

/// <summary>
/// Helper for dispatching game events in a scene.
/// </summary>
public static class GameEvent
{
	private static Dictionary<Type, IReadOnlyDictionary<Type, int>> HandlerOrderingCache { get; } = new();

	/// <summary>
	/// Notifies all <see cref="IGameEventHandler{T}"/> components that are within <paramref name="root"/>,
	/// with a payload of type <typeparamref name="T"/>.
	/// </summary>
	public static void Dispatch<T>( this GameObject root, T eventArgs )
		where T : IGameEvent
	{
		var handlers = (root is Scene scene
			? scene.GetAllComponents<IGameEventHandler<T>>() // I think this is more efficient?
			: root.Components.GetAll<IGameEventHandler<T>>())
			.ToArray();

		if ( !HandlerOrderingCache.TryGetValue( typeof(T), out var ordering ) || handlers.Any( x => !ordering.ContainsKey( x.GetType() ) ) )
		{
			ordering = HandlerOrderingCache[typeof(T)] = GetHandlerOrdering<T>();
		}

		List<Exception>? exceptions = null;

		foreach ( var handler in handlers.OrderBy( x => ordering[x.GetType()] ) )
		{
			try
			{
				handler.OnGameEvent( eventArgs );
			}
			catch ( Exception e )
			{
				exceptions ??= new();
				exceptions.Add( e );
			}
		}

		switch ( exceptions?.Count )
		{
			case 1:
				Log.Error( exceptions[0] );
				break;

			case > 1:
				Log.Error( new AggregateException( exceptions ) );
				break;
		}
	}

	private static bool IsImplementingMethodName( string methodName )
	{
		if ( methodName == nameof(IGameEventHandler<IGameEvent>.OnGameEvent) )
		{
			return true;
		}

		return methodName.StartsWith( "Sandbox.Events.IGameEventHandler<" ) && methodName.EndsWith( ">.OnGameEvent" );
	}

	private static MethodDescription? GetImplementation<T>( TypeDescription type )
	{
		foreach ( var method in type.Methods )
		{
			if ( method.IsStatic ) continue;
			if ( method.Parameters.Length != 1 ) continue;
			if ( method.Parameters[0].ParameterType != typeof( T ) ) continue;

			if ( !IsImplementingMethodName( method.Name ) ) continue;

			return method;
		}

		return null;
	}

	private static IReadOnlyDictionary<Type, int> GetHandlerOrdering<T>()
		where T : IGameEvent
	{
		var types = TypeLibrary.GetTypes<IGameEventHandler<T>>().ToArray();
		var helper = new SortingHelper( types.Length );

		for ( var i = 0; i < types.Length; ++i )
		{
			var type = types[i];
			var method = GetImplementation<T>( type );

			if ( method is null )
			{
				Log.Warning( $"Can't find {nameof( IGameEventHandler<T> )}<{typeof( T ).Name}> implementation in {type.Name}!" );
				continue;
			}

			foreach ( var attrib in method.Attributes )
			{
				switch ( attrib )
				{
					case EarlyAttribute:
						helper.AddFirst( i );
						break;

					case LateAttribute:
						helper.AddLast( i );
						break;

					case IBeforeAttribute before:
						for ( var j = 0; j < types.Length; ++j )
						{
							if ( i == j ) continue;

							var other = types[j];

							if ( before.Type.IsAssignableFrom( other.TargetType ) )
							{
								helper.AddConstraint( i, j );
							}
						}

						break;

					case IAfterAttribute after:
						for ( var j = 0; j < types.Length; ++j )
						{
							if ( i == j ) continue;

							var other = types[j];

							if ( after.Type.IsAssignableFrom( other.TargetType ) )
							{
								helper.AddConstraint( j, i );
							}
						}

						break;
				}
			}
		}

		var ordering = new List<int>();

		if ( !helper.Sort( ordering, out var invalid ) )
		{
			Log.Error( $"Invalid event ordering constraint between {types[invalid.EarlierIndex].Name} and {types[invalid.LaterIndex].Name}!" );
			return ImmutableDictionary<Type, int>.Empty;
		}

		return Enumerable.Range( 0, ordering.Count )
			.ToImmutableDictionary( i => types[ordering[i]].TargetType, i => i );
	}
}

public delegate void GameEventAction<in T>( T eventArgs )
	where T : IGameEvent;

/// <summary>
/// Base class for components that expose game events to Action Graph.
/// </summary>
public abstract class GameEventComponent<T> : Component, IGameEventHandler<T>
	where T : IGameEvent
{
	/// <summary>
	/// Action invoked when the <typeparamref name="T"/> event is dispatched.
	/// </summary>
	[Property]
	public GameEventAction<T>? OnEvent { get; set; }

	/// <summary>
	/// If this component is within a state machine, optional state to transition
	/// to when this event is dispatched.
	/// </summary>
	[Property]
	public StateComponent? NextState { get; set; }

	void IGameEventHandler<T>.OnGameEvent( T eventArgs )
	{
		OnEvent?.Invoke( eventArgs );

		if ( NextState is not null )
		{
			Components.GetInAncestorsOrSelf<StateMachineComponent>()?.Transition( NextState );
		}
	}
}

using System;

namespace Sandbox.Events;

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers not marked as early, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class EarlyAttribute : Attribute
{

}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers not marked as late, except if more specific
/// constraints are given (i.e., <see cref="BeforeAttribute{T}"/>, <see cref="AfterAttribute{T}"/>).
/// </summary>
[AttributeUsage( AttributeTargets.Method )]
public sealed class LateAttribute : Attribute
{

}

internal interface IBeforeAttribute
{
	Type Type { get; }
}

internal interface IAfterAttribute
{
	Type Type { get; }
}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked before any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class BeforeAttribute<T> : Attribute, IBeforeAttribute
{
	Type IBeforeAttribute.Type => typeof(T);
}

/// <summary>
/// Only valid on <see cref="IGameEventHandler{T}.OnGameEvent"/> implementations. Forces this
/// event handler to be invoked after any handlers in the specified type.
/// </summary>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = true )]
public sealed class AfterAttribute<T> : Attribute, IAfterAttribute
{
	Type IAfterAttribute.Type => typeof( T );
}
using System.Collections.Generic;
using System.Linq;

namespace Sandbox.Events;

/// <summary>
/// Generate an ordering based on a set of first-most and last-most items, and
/// individual constraints between pairs of items. All first-most items will be
/// ordered before all last-most items, and any other items will be put in the
/// middle unless forced to be elsewhere by a constraint.
/// </summary>
internal class SortingHelper
{
	public record struct SortConstraint( int EarlierIndex, int LaterIndex )
	{
		public SortConstraint Complement => new ( LaterIndex, EarlierIndex );
	}

	private readonly int _itemCount;

	private readonly HashSet<SortConstraint> _initialConstraints = new HashSet<SortConstraint>();

	private readonly HashSet<int> _first = new HashSet<int>();
	private readonly HashSet<int> _last = new HashSet<int>();

	public SortingHelper( int itemCount )
	{
		_itemCount = itemCount;
	}

	public void AddConstraint( int earlierIndex, int laterIndex )
	{
		_initialConstraints.Add( new SortConstraint( earlierIndex, laterIndex ) );
	}

	public void AddFirst( int earlierIndex )
	{
		_first.Add( earlierIndex );
	}

	public void AddLast( int laterIndex )
	{
		_last.Add( laterIndex );
	}

	public bool Sort( List<int> result, out SortConstraint invalidConstraint )
	{
		var middle = new HashSet<int>();

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !_first.Contains( index ) && !_last.Contains( index ) )
				middle.Add( index );
		}

		var allConstraints = new HashSet<SortConstraint>();
		var newConstraints = new Queue<SortConstraint>();
		var beforeDict = new Dictionary<int, HashSet<int>>();
		var afterDict = new Dictionary<int, HashSet<int>>();

		bool AddWorkingConstraint( int earlierIndex, int laterIndex, out SortConstraint constraint )
		{
			constraint = new SortConstraint( earlierIndex, laterIndex );

			if ( allConstraints.Contains( constraint.Complement ) )
				return false;

			if ( !allConstraints.Add( constraint ) )
				return true;

			newConstraints.Enqueue( constraint );

			if ( !beforeDict.TryGetValue( earlierIndex, out var before ) )
				beforeDict.Add( earlierIndex, before = new HashSet<int>() );

			if ( !afterDict.TryGetValue( laterIndex, out var after ) )
				afterDict.Add( laterIndex, after = new HashSet<int>() );

			before.Add( laterIndex );
			after.Add( earlierIndex );

			return true;
		}

		// Add initial constraints

		foreach ( var initialConstraint in _initialConstraints )
		{
			if ( !AddWorkingConstraint( initialConstraint.EarlierIndex, initialConstraint.LaterIndex, out invalidConstraint ) )
				return false;
		}

		// Everything in _first should be before everything in _last

		foreach ( var earlierIndex in _first )
		{
			foreach ( var laterIndex in _last )
			{
				if ( !AddWorkingConstraint( earlierIndex, laterIndex, out invalidConstraint ) )
					return false;
			}
		}

		// Keep propagating constraints until nothing changes

		while ( newConstraints.TryDequeue( out var nextConstraint ) )
		{
			// if a < b, and b < c, then a < c etc

			if ( beforeDict.TryGetValue( nextConstraint.LaterIndex, out var before ) )
			{
				foreach ( var laterIndex in before )
				{
					if ( !AddWorkingConstraint( nextConstraint.EarlierIndex, laterIndex, out invalidConstraint ) )
						return false;
				}
			}

			if ( afterDict.TryGetValue( nextConstraint.EarlierIndex, out var after ) )
			{
				foreach ( var earlierIndex in after )
				{
					if ( !AddWorkingConstraint( earlierIndex, nextConstraint.LaterIndex, out invalidConstraint ) )
					{
						return false;
					}
				}
			}
		}

		// Now if we have any items that aren't using GroupOrder.First, and haven't
		// determined that they are ordered before another item with GroupOrder.First,
		// we can safely order them after all GroupOrder.First items. And vice versa.

		foreach ( var middleIndex in middle )
		{
			var isBeforeAnyFirst = beforeDict.TryGetValue( middleIndex, out var before )
				&& before.Any( x => _first.Contains( x ) );

			var isAfterAnyLast = afterDict.TryGetValue( middleIndex, out var after )
				&& after.Any( x => _last.Contains( x ) );

			if ( !isBeforeAnyFirst )
			{
				foreach ( var earlierIndex in _first )
					AddWorkingConstraint( earlierIndex, middleIndex, out invalidConstraint );
			}

			if ( !isAfterAnyLast )
			{
				foreach ( var laterIndex in _last )
					AddWorkingConstraint( middleIndex, laterIndex, out invalidConstraint );
			}
		}

		// Now lets add items to the final ordering if all items that should be sorted
		// before them are already added to that ordering. We'll implement this by choosing
		// items that have an empty list / don't appear in afterDict, and update that
		// dictionary as we go.

		var earliestRemaining = new Queue<int>();

		// First, seed the queue with everything that's already not ordered after anything

		for ( var index = 0; index < _itemCount; ++index )
		{
			if ( !afterDict.ContainsKey( index ) )
			{
				earliestRemaining.Enqueue( index );
			}
		}

		result.Clear();

		while ( earliestRemaining.TryDequeue( out var nextIndex ) )
		{
			result.Add( nextIndex );

			foreach ( var laterIndex in beforeDict.TryGetValue( nextIndex, out var laterIndices )
				? laterIndices : Enumerable.Empty<int>() )
			{
				var beforeLater = afterDict[laterIndex];
				beforeLater.Remove( nextIndex );

				if ( beforeLater.Count == 0 )
					earliestRemaining.Enqueue( laterIndex );
			}
		}

		invalidConstraint = default;
		return result.Count == _itemCount;
	}
}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using Sandbox;
using System.Threading.Tasks;

public sealed class WebSocketUtility : Component
{
	[Property] public List<WebsocketTools> websocketToolsList { get; set; }
	protected override void OnAwake()
	{
		foreach ( var websocketTools in websocketToolsList )
		{
			if ( websocketTools.url is null )
			{
				Log.Error( "WebsocketTools URL is null" );
				return;
			}
			websocketTools.webSocket = new WebSocket();
			ConnectToSocket( websocketTools.webSocket, websocketTools.url );
			websocketTools.isConnected = true;
			websocketTools.webSocket.OnMessageReceived += websocketTools.OnMessageReceivedMethod;
			websocketTools.isSubscribed = true;
		}
	}

	protected override void OnUpdate()
	{
		SendMessageFromList( WebsocketTools.Fetch.OnUpdate );
	}
	protected override void OnFixedUpdate()
	{
		SendMessageFromList( WebsocketTools.Fetch.OnFixedUpdate );
	}
	protected override void OnStart()
	{
		SendMessageFromList( WebsocketTools.Fetch.OnStart );
	}

	private async void SendMessageFromList( WebsocketTools.Fetch fetch )
	{
		foreach ( var websocketTools in websocketToolsList )
		{
			if ( websocketTools.fetch == fetch )
			{
				if ( websocketTools.message.UseJsonTags )
				{
					var jsonStrings = websocketTools.message.jsonTags.Select( tag => Json.Serialize( tag.ToString() ) );

					var bigString = string.Join( "", jsonStrings );

					var finalJsonString = Json.Serialize( bigString );

					await websocketTools.webSocket.Send( finalJsonString );
				}
				else
				{
					var messageBytes = Encoding.UTF8.GetBytes( websocketTools.message.message );
					await websocketTools.webSocket.Send( messageBytes );
				}
			}
		}
	}

	[Description( "Sends a message over a websocket connection" )]
	public static async Task SendAsync( WebsocketTools websocketTools )
	{
		if ( websocketTools.webSocket is null )
		{
			websocketTools.webSocket = new WebSocket();
		}
		if ( !websocketTools.isConnected )
		{
			await websocketTools.webSocket.Connect( websocketTools.url );
			websocketTools.isConnected = true;
		}

        if ( websocketTools.message.UseJsonTags )
            await websocketTools.webSocket.Send( Json.Serialize( websocketTools.message.jsonTags ) );
        else
		    await websocketTools.webSocket.Send( websocketTools.message.message );

		if ( !websocketTools.isSubscribed )
		{
			websocketTools.webSocket.OnMessageReceived += websocketTools.OnMessageReceivedMethod;
			websocketTools.isSubscribed = true;
		}
	}

	public static async Task SendStringAsync( string url, string message )
	{
		var webSocket = new WebSocket();
		await webSocket.Connect( url );
		await webSocket.Send( message );
	}

	public static void ChangeJsonTagValue( WebsocketMessage message, string tag, string value )
	{
         if ( message is null )
            message = new WebsocketMessage();

		if ( message.jsonTags is null )
			message.jsonTags = new List<JsonTags>();

		var jsonTag = message.jsonTags.Find( x => x.tag == tag );
		if ( jsonTag is null )
		{
			Log.Warning( $"Tag {tag} not found in message" );
		}
		else
		{
			jsonTag.value = value;
		}
	}

	public static void AddJsonTag( WebsocketMessage message, string tag, string value )
	{
        if ( message is null )
            message = new WebsocketMessage();
		
		if ( message.jsonTags is null )
			message.jsonTags = new List<JsonTags>();

		var jsonTag = new JsonTags
		{
			tag = tag,
			value = value
		};
		message.jsonTags.Add( jsonTag );
	}

	private async void ConnectToSocket( WebSocket webSocket, string url )
	{
		await webSocket.Connect( url );
	}

	[ActionGraphNode( "new websocket tools" ), Pure]
	public static WebsocketTools NewWebsocketTools()
	{
		return new WebsocketTools();
	}
}

public class WebsocketTools
{
	public delegate void OnMessageReceived( string message );
	public OnMessageReceived onMessageReceived { get; set; }
	public WebSocket webSocket { get; set; }
	public string url { get; set; }
	public WebsocketMessage message { get; set; } = new();
	public bool isConnected { get; set; }
	public bool isSubscribed { get; set; }
	public string returnMessage { get; set; }
	public enum Fetch
	{
		OnUpdate,
		OnFixedUpdate,
		OnStart,
	}
	public Fetch fetch { get; set; }

	public void OnMessageReceivedMethod( string message )
	{
		onMessageReceived?.Invoke( message );
		returnMessage = message;
	}

	public WebsocketTools()
	{
		url = "ws://localhost:8080";
		fetch = Fetch.OnUpdate;
		onMessageReceived = null;
		message = null;
	}

	public WebsocketTools( string url, OnMessageReceived onMessageReceived, WebsocketMessage message, Fetch fetch = Fetch.OnUpdate )
	{
		this.url = url;
		this.fetch = fetch;
		this.onMessageReceived = onMessageReceived;
		this.message = message;
	}

}
[GameResource( "Message", "message", "A message to be sent over a websocket connection", Icon = "chat_bubble" )]
public class WebsocketMessage : GameResource
{
	public bool UseJsonTags { get; set; }
	[ShowIf( "UseJsonTags", false )] public string message { get; set; } = "";
	[ShowIf( "UseJsonTags", true )] public List<JsonTags> jsonTags { get; set; } = new();
}

public class JsonTags
{
	public string tag { get; set; }
	public string value { get; set; }

}
global using Microsoft.AspNetCore.Components; 
global using Microsoft.AspNetCore.Components.Rendering;
public sealed class JiggleBone : TransformProxyComponent
{
	JiggleBoneState state = new JiggleBoneState();

	[Property]
	public Vector3 StartPoint = new Vector3( 0, 0, 0 );

	[Property]
	public Vector3 EndPoint = new Vector3( 32, 0, 0 );

	[Property, Range( 0, 2 )]
	public float Speed { get; set; } = 1.0f;

	[Property, Range( 0, 2 )]
	public float Stiffness { get; set; } = 1.0f;

	[Property, Range( 0, 2 )]
	public float Damping { get; set; } = 1.0f;

	[Property, Range( 0, 100 )]
	public float Radius { get; set; } = 40.0f;

	[Property, Range( 0, 100 )]
	public float Mass { get; set; } = 1.0f;

	Transform LocalJigglePosition;

	protected override void OnEnabled()
	{
		LocalJigglePosition = Transform.Local;

		base.OnEnabled();

		state = new JiggleBoneState();
	}

	protected override void OnUpdate()
	{
		var oldPos = LocalJigglePosition;



		using ( Transform.DisableProxy() )
		{
			var worldTx = Transform.World;

			var startPoint = worldTx.PointToWorld( StartPoint );
			var endPoint = worldTx.PointToWorld( EndPoint );

			//Gizmo.Draw.LineSphere( startPoint, 1 );
			//Gizmo.Draw.LineSphere( endPoint, 1 );

			state.Extent = (endPoint - startPoint);
			state.Stiffness = Stiffness;
			state.Damping = Damping;
			state.Radius = Radius;
			state.Mass = Mass;

			state.Update( startPoint, Time.Delta * Speed * 16.0f );

			var tx = worldTx.RotateAround( startPoint, state.Rotation );
			LocalJigglePosition = GameObject.Parent.Transform.World.ToLocal( tx );
		}

		if ( oldPos != LocalJigglePosition )
		{
			MarkTransformChanged();
		}
	}

	protected override void DrawGizmos()
	{
		base.DrawGizmos();

		if ( !Gizmo.IsSelected )
			return;

		using ( Transform.DisableProxy() )
		{
			Gizmo.Transform = Transform.World;
			Gizmo.Draw.IgnoreDepth = false;
			Gizmo.Draw.Color = Gizmo.Colors.Yaw.WithAlpha( 0.5f );
			Gizmo.Draw.Line( StartPoint, EndPoint );
			Gizmo.Draw.LineBBox( BBox.FromPositionAndSize( StartPoint, 5 ) );
			Gizmo.Draw.LineBBox( BBox.FromPositionAndSize( EndPoint, 5 ) );
			Gizmo.Draw.LineSphere( EndPoint, Radius * 2.0f, 4 );
		}
	}

	public override Transform GetLocalTransform()
	{
		return LocalJigglePosition;
	}
}

class JiggleBoneState
{
	public Vector3 Extent = new Vector3( 32, 0, 0 );

	public Vector3 Position { get; set; }
	public Rotation Rotation { get; set; }
	public float Stiffness { get; set; } = 1.0f;
	public float Damping { get; set; } = 1.0f;
	public float Radius { get; set; } = 10.0f;
	public float Gravity { get; set; } = 1.0f;
	public float Mass { get; set; } = 1.0f;


	Vector3 basePosition;
	Vector3 velocity;

	public JiggleBoneState()
	{

	}

	internal void Update( Vector3 position, float timeDelta )
	{
		basePosition = position + Extent;

		// initialization
		if ( Position == default )
		{
			Position = basePosition;
		}

		// Calculate spring force based on displacement from the cube
		Vector3 displacement = Position - basePosition;
		Vector3 springForce = -Stiffness * displacement;

		// Calculate acceleration (Newton's second law)
		Vector3 acceleration = springForce / Mass;

		// Update velocity (integrate acceleration)
		velocity += acceleration * timeDelta;

		// Apply exponential damping
		velocity *= (float)Math.Exp( -Damping * timeDelta );

		// Update position (integrate velocity)
		Position += velocity * timeDelta;

		{
			var diff = Position - basePosition;
			var diffLen = diff.Length;
			if ( diffLen > Radius )
			{
				Position = basePosition + diff.Normal * Radius;
				//velocity = velocity.AddClamped( -diff * 2.0f, diff.Length );
			}
		}

		// Store the rotation offset result
		Rotation = Rotation.FromToRotation( basePosition - position, Position - position );

		//Gizmo.Draw.IgnoreDepth = true;
		//Gizmo.Draw.Line( position, Position );
		//Gizmo.Draw.Line( basePosition, Position );
	}
}
using Sandbox.UI;
using Sandbox.UI.Construct;

public class ToastPanel : Panel
{
	public ToastPanel( Toast toast )
	{
		AddClass( toast.Status.ToString() );
		AddClass( toast.Position.ToString() );

		Add.Icon( toast.Status switch
		{
			ToastStatus.Info => "info",
			ToastStatus.Warning => "warning_amber",
			ToastStatus.Success => "check_circle_outline",
			ToastStatus.Error => "error_outline",
			_ => "",
		} );

		Add.Label( toast.Text, "text" );

		AddEventListener( "onclick", ( PanelEvent _ ) => Delete() );

		Invoke( toast.Duration, () => Delete() );
	}

	protected override int BuildHash() => HashCode.Combine( 1 );
}
using System;
using static TestClasses;

namespace SandbankDatabase;

internal static class TestData
{
	public static ReadmeExample TestData1 = new ReadmeExample()
	{
		UID = "",
		Health = 100,
		Name = "TestPlayer1",
		Level = 10,
		LastPlayTime = DateTime.UtcNow,
		Items = new() { "gun", "frog", "banana" }
	};

	public static ReadmeExample TestData2 = new ReadmeExample()
	{
		UID = "",
		Health = 90,
		Name = "TestPlayer2",
		Level = 15,
		LastPlayTime = DateTime.UtcNow,
		Items = new() { "apple", "box" }
	};
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Sandbox;
using Sandbox.Diagnostics;

namespace DataTables;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class JsonTypeAnnotateAttribute : Attribute
{
}

internal static class Json
{
	public static JsonSerializerOptions Options()
	{
		return new JsonSerializerOptions() { WriteIndented = true };
	}

	public static JsonNode Serialize( object target, bool typeAnnotate, Type typeOverride = null )
	{
		var type = target.GetType();
		var typeDesc = TypeLibrary.GetType( type );

		if ( typeDesc.IsValueType || type.IsAssignableTo( typeof(Resource) ) ||
		     type.IsAssignableTo( typeof(string) ) )
			return Sandbox.Json.ToNode( target );

		if ( type.IsAssignableTo( typeof(IList) ) )
			return SerializeList( (IList)target, typeAnnotate );

		if ( type.IsAssignableTo( typeof(IDictionary) ) )
			return SerializeDictionary( (IDictionary)target, typeAnnotate );

		var node = SerializeObject( target, true, typeOverride );
		if ( typeAnnotate )
			node["__type"] = typeDesc.FullName;
		return node;
	}

	public static JsonNode SerializeDictionary( IDictionary target, bool typeAnnotate )
	{
		JsonObject jdict = new();

		Type keyArg = TypeLibrary.GetGenericArguments( target.GetType() )[0];

		bool isInteger = keyArg == typeof(int);
		bool isString = keyArg == typeof(string);
		bool isReal = keyArg == typeof(float) || keyArg == typeof(double);

		if ( !(isInteger || isString || isReal) )
		{
			Log.Error(
				$"The type '{keyArg.FullName}' is not a supported dictionary key! If you really need this to be supported, please submit an issue @ https://github.com/tzainten/DataTables" );
			return jdict;
		}

		foreach ( var key in target.Keys )
		{
			if ( key is null )
				continue;

			var value = target[key];
			if ( value is null )
				continue;

			jdict.Add( key.ToString(), Serialize( value, typeAnnotate ) );
		}

		return jdict;
	}

	public static JsonArray SerializeList( IList target, bool typeAnnotate )
	{
		JsonArray jarray = new();

		foreach ( var elem in target )
		{
			if ( elem is null )
				continue;

			jarray.Add( Serialize( elem, typeAnnotate ) );
		}

		return jarray;
	}

	public static JsonObject SerializeObject( object target, bool typeAnnotate, Type typeOverride = null )
	{
		JsonObject jobj = new();

		var type = typeOverride ?? target.GetType();
		var typeDesc = TypeLibrary.GetType( type );

		if ( typeDesc.IsValueType || type.IsAssignableTo( typeof(Resource) ) ||
		     type.IsAssignableTo( typeof(string) ) )
			return Sandbox.Json.ToNode( target ).AsObject();

		var members = TypeLibrary.GetFieldsAndProperties( typeDesc );
		foreach ( var member in members )
		{
			object value = null;
			bool shouldAnnotate = false;

			if ( member.IsField )
			{
				FieldDescription field = (FieldDescription)member;
				value = field.GetValue( target );
				if ( value is null )
					continue;

				shouldAnnotate = field.HasAttribute( typeof(JsonTypeAnnotateAttribute) );
				jobj[field.Name] = Serialize( value, shouldAnnotate, !shouldAnnotate ? field.FieldType : null );

				continue;
			}

			PropertyDescription property = (PropertyDescription)member;
			value = property.GetValue( target );
			if ( value is null )
				continue;

			shouldAnnotate = property.HasAttribute( typeof(JsonTypeAnnotateAttribute) );
			jobj[property.Name] = Serialize( value, shouldAnnotate, !shouldAnnotate ? property.PropertyType : null );
		}

		return jobj;
	}

	public static T Deserialize<T>( string json )
	{
		JsonNode node = JsonNode.Parse( json );
		if ( node is null )
			return default;

		return (T)DeserializeInternal( node, typeof(T) );
	}

	public static object DeserializeInternal( JsonNode node, Type type )
	{
		TypeDescription typeDesc = TypeLibrary.GetType( type );
		if ( typeDesc is not null && (typeDesc.IsValueType || type.IsAssignableTo( typeof(Resource) ) ||
		                              type.IsAssignableTo( typeof(string) )) )
		{
			try
			{
				return Sandbox.Json.FromNode( node, type );
			}
			catch ( Exception e )
			{
				return null;
			}
		}

		if ( type.IsAssignableTo( typeof(IDictionary) ) )
			return DeserializeDictionary( node.AsObject(), type );

		switch ( node.GetValueKind() )
		{
			case JsonValueKind.Object:
				return DeserializeObject( node.AsObject(), type );
			case JsonValueKind.Array:
				return DeserializeList( node.AsArray(), type );
			default:
				try
				{
					return Sandbox.Json.FromNode( node, type );
				}
				catch ( Exception e )
				{
					return null;
				}
		}
	}

	public static IList DeserializeList( JsonArray jarray, Type type )
	{
		IList list = TypeLibrary.Create<IList>( type );

		using var enumerator = jarray.GetEnumerator();
		while ( enumerator.MoveNext() )
		{
			var node = enumerator.Current;

			Type genericArg = TypeLibrary.GetGenericArguments( type ).First();

			var elem = DeserializeInternal( node, genericArg );
			if ( elem is null )
				continue;

			if ( elem.GetType().IsAssignableTo( genericArg ) )
				list.Add( elem );
		}

		return list;
	}

	public static IDictionary DeserializeDictionary( JsonObject jobj, Type type )
	{
		IDictionary dict = TypeLibrary.Create<IDictionary>( type );

		using var enumerator = jobj.GetEnumerator();
		while ( enumerator.MoveNext() )
		{
			var pair = enumerator.Current;

			Type[] genericArgs = TypeLibrary.GetGenericArguments( type );
			var keyType = genericArgs[0];

			var key = pair.Key;
			object parsedKey = key;
			if ( keyType == typeof(int) )
			{
				if ( int.TryParse( key, out int num ) )
					parsedKey = num;
			}
			else if ( keyType == typeof(double) )
			{
				if ( double.TryParse( key, out double num ) )
					parsedKey = num;
			}
			else if ( keyType == typeof(float) )
			{
				if ( float.TryParse( key, out float num ) )
					parsedKey = num;
			}

			var node = pair.Value;

			var elem = DeserializeInternal( node, genericArgs[1] );
			if ( elem is null )
				continue;

			Type keyArg = TypeLibrary.GetGenericArguments( type )[0];
			Type valueArg = TypeLibrary.GetGenericArguments( type )[1];

			bool isCorrectKeyType = parsedKey.GetType().IsAssignableTo( keyArg );
			bool isCorrectValueType = elem.GetType().IsAssignableTo( valueArg );

			if ( !isCorrectKeyType || !isCorrectValueType )
				continue;

			if ( elem.GetType().IsAssignableTo( genericArgs[1] ) )
				dict.Add( parsedKey, elem );
		}

		return dict;
	}

	public static object DeserializeObject( JsonObject jobj, Type type )
	{
		jobj.TryGetPropertyValue( "__type", out JsonNode __type );

		TypeDescription typeDesc = null;
		if ( __type is not null )
		{
			typeDesc = TypeLibrary.GetType( __type.GetValue<string>() );
		}
		else
		{
			typeDesc = TypeLibrary.GetType( type );
		}

		if ( typeDesc is null )
			return null;

		object instance = TypeLibrary.Create<object>( typeDesc.TargetType );
		using var enumerator = jobj.GetEnumerator();
		while ( enumerator.MoveNext() )
		{
			var node = enumerator.Current;

			var property = typeDesc.Properties.FirstOrDefault( x =>
				x.IsPublic && !x.IsStatic && x.IsNamed( node.Key ) && x.CanWrite && x.CanRead );
			bool isValidProperty = property is not null;

			var field = typeDesc.Fields.FirstOrDefault( x => x.IsPublic && !x.IsStatic && x.IsNamed( node.Key ) );
			bool isValidField = field is not null;

			if ( !isValidProperty && !isValidField )
				continue;

			var deserializeType = isValidProperty ? property.PropertyType : field.FieldType;
			var value = DeserializeInternal( node.Value, deserializeType );
			if ( value is null )
				continue;

			if ( value.GetType().IsAssignableTo( deserializeType ) )
			{
				if ( isValidProperty )
					property.SetValue( instance, value );
				else
					field.SetValue( instance, value );
			}
		}

		return instance;
	}
}
global using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class TestInit
{
	[AssemblyInitialize]
	public static void ClassInitialize( TestContext context )
	{
		Sandbox.Application.InitUnitTest();
	}
}
using Duccsoft.ImGui.Rendering;
using System;

namespace Duccsoft.ImGui.Elements;

public class Window : Element
{
	public Window( string name, ref bool open, Vector2 screenPos, Vector2 pivot, Vector2 size, ImGuiWindowFlags flags )
		: base( null )
	{
		Name = name;
		DrawList = new ImDrawList( Name );
		Id = ImGui.GetID( Name );
		WindowFlags = flags;
		Position = screenPos;
		if ( System.CustomWindowPositions.TryGetValue( Id, out var customPos ) )
		{
			// Window positions are stored unscaled in case screen size changes,
			// so we need to scale them back up here.
			Position = customPos * ImGuiStyle.UIScale;
		}
		Pivot = pivot;
		Padding = ImGui.GetStyle().WindowPadding;
		CustomSize = size;
		ImGuiSystem.Current.IdStack.Push( Id );
		ImGuiSystem.Current.WindowStack.Push( this );
		CursorPosition = ImGui.GetStyle().WindowPadding;
		CursorStartPosition = CursorPosition;

		OnBegin();

		open = true;
	}

	public string Name { get; init; }
	public ImDrawList DrawList { get; set; }
	public ImGuiWindowFlags WindowFlags { get; init; }

	public Action OnClose { get; set; }

	internal WindowTitleBar TitleBar { get; set; }

	public Vector2 CursorStartPosition { get; set; }
	public Vector2 CursorPosition { get; set; }

	public static Color32 BackgroundColor => ImGui.GetColorU32( ImGuiCol.WindowBg );
	public static Color32 BorderColor => ImGui.GetColorU32( ImGuiCol.Border );

	public override void OnEnd()
	{
		base.OnEnd();

		TitleBar?.OnEnd();
		if ( System.TryGetDrawList( Id, out var drawList ) )
		{
			DrawList = drawList;
			DrawList.CommandList.Reset();
		}
		else
		{
			DrawList = new ImDrawList( $"ImGui DrawList {Name}" );
			System.AddDrawList( Id, DrawList );
		}
	}

	protected override void OnDrawSelf( ImDrawList drawList )
	{
		DrawList.AddRect( ScreenRect.TopLeft, ScreenRect.BottomRight, BorderColor, rounding: 0, flags: ImDrawFlags.None, thickness: 1 );
		DrawList.AddRectFilled( ScreenRect.TopLeft, ScreenRect.BottomRight, BackgroundColor );
	}
}
namespace Duccsoft.ImGui;

public static partial class ImGui
{
	public static ImGuiIO GetIO() => System.InputState;
	public static Vector2 GetMousePos() => MouseState.Position;
	public static Vector2 GetMouseDragDelta( ImGuiMouseButton button, float lockThreshold = -1.0f )
	{
		if ( lockThreshold < 0f )
		{
			// TODO: Use io.MouseDraggingThreshold
			lockThreshold = 1.0f;
		}
		var mouseDelta = button switch
		{
			ImGuiMouseButton.Left	=> MouseState.LeftClickDragTotalDelta,
			ImGuiMouseButton.Right	=> MouseState.RightClickDragTotalDelta,
			ImGuiMouseButton.Middle	=> MouseState.MiddleClickDragTotalDelta,
			_						=> Vector2.Zero
		};
		if ( mouseDelta.Length < lockThreshold )
			return Vector2.Zero;

		return mouseDelta;
	}
}
using System;

namespace Duccsoft.ImGui;

public static partial class ImGui
{
	public static float GetFontSize() => (int)(18 * ImGuiStyle.UIScale);

	public static ImGuiStyle GetStyle()
	{
		return ImGuiSystem.Current.Style;
	}

	public static Color32 GetColorU32( ImGuiCol color, float alphaMul = 1.0f )
	{
		var colors = ImGuiSystem.Current.Style.Colors;

		if ( colors is null || !colors.TryGetValue( color, out Color32 styleColor ) )
			return new Color32( 0xFF, 0x00, 0xFF, (byte)(0xFF * alphaMul) );

		return styleColor with { a = (byte)(styleColor.a * alphaMul) };
	}

	#region Style Colors
	public static void StyleColorsDark( ImGuiStyle style )
	{
		if ( style is null )
			return;

		style.Colors ??= new();
		style.Colors[ImGuiCol.WindowBg]						= new( 0x0F, 0x0F, 0x0F, 240 );
		style.Colors[ImGuiCol.Border]						= new( 0x42, 0x42, 0x4C, 128 );
		style.Colors[ImGuiCol.Text]							= new( 0xFF, 0xFF, 0xFF );
		style.Colors[ImGuiCol.TitleBg]						= new( 0x0A, 0x0A, 0x0A );
		style.Colors[ImGuiCol.TitleBgActive]				= new( 0x29, 0x4A, 0x7A );
		style.Colors[ImGuiCol.Button]						= new( 66, 150, 250, 102 );
		style.Colors[ImGuiCol.ImGuiColButtonHovered]		= new( 66, 150, 250 );
		style.Colors[ImGuiCol.ButtonActive]					= new( 15, 135, 250 );
		style.Colors[ImGuiCol.FrameBg]						= new( 41, 74, 122, 138 );
		style.Colors[ImGuiCol.FrameBgHovered]				= new( 66, 150, 250, 102 );
		style.Colors[ImGuiCol.FrameBgActive]				= new( 66, 150, 250, 171 );
		style.Colors[ImGuiCol.SliderGrab]					= new( 61, 133, 244 );
		style.Colors[ImGuiCol.SliderGrabActive]				= new( 66, 150, 250, 255 );
		style.Colors[ImGuiCol.CheckMark]					= new( 66, 150, 250, 255 );
	}
	#endregion
}
using Duccsoft.ImGui.Elements;

namespace Duccsoft.ImGui;

public static partial class ImGui
{
	public static bool IsItemClicked( ImGuiMouseButton button = ImGuiMouseButton.Left )
	{
		return System.ClickedElementId == CurrentItemRecursive?.Id;
	}

	public static void Text( string formatString, params object[] args )
	{
		var text = string.Format( formatString, args );
		_ = new TextWidget( CurrentWindow, text );
	}

	public static bool Button( string label, Vector2 size = default )
	{
		var button = new ButtonWidget( CurrentWindow, label );
		return button.IsReleased;
	}

	public static bool Checkbox( string label, ref bool value )
	{
		var checkbox = new Checkbox( CurrentWindow, label, ref value );
		return checkbox.IsReleased;
	}

	public static bool DragInt( string label, ref int value, float speed = 1.0f, int min = 0, int max = 0, string format = null, ImGuiSliderFlags flags = 0 )
	{
		_ = new DragInt( CurrentWindow, label, ref value, speed, min, max, format, flags );
		// TODO: Is returning true correct?
		return true;
	}

	public static bool SliderFloat( string label, ref float value, float min, float max, string format = "F3", ImGuiSliderFlags flags = 0 )
	{
		var components = new float[1] { value };
		_ = new Slider<float>( CurrentWindow, label, ref components, min, max, format );
		value = components[0];
		return true;
	}

	public static bool SliderFloat2( string label, ref Vector2 value, float min, float max, string format = "F3", ImGuiSliderFlags flags = 0 )
	{
		var components = new float[2] { value.x, value.y };
		_ = new Slider<float>( CurrentWindow, label, ref components, min, max, format );
		value.x = components[0];
		value.y = components[1];
		return true;
	}

	public static bool SliderFloat3( string label, ref Vector3 value, float min, float max, string format = "F3", ImGuiSliderFlags flags = 0 )
	{
		var components = new float[3] { value.x, value.y, value.z };
		_ = new Slider<float>( CurrentWindow, label, ref components, min, max, format );
		value.x = components[0];
		value.y = components[1];
		value.z = components[2];
		return true;
	}

	public static bool SliderFloat4( string label, ref Vector4 value, float min, float max, string format = "F3", ImGuiSliderFlags flags = 0 )
	{
		var components = new float[4] { value.x, value.y, value.z, value.w };
		_ = new Slider<float>( CurrentWindow, label, ref components, min, max, format );
		value.x = components[0];
		value.y = components[1];
		value.z = components[2];
		value.w = components[3];
		return true;
	}

	public static bool SliderInt( string label, ref int value, int min, int max, string format = null, ImGuiSliderFlags flags = 0 )
	{
		var components = new int[1] { value };
		_ = new Slider<int>( CurrentWindow, label, ref components, min, max, format );
		value = components[0];
		return true;
	}

	public static void Image( Texture texture, Vector2 size, Vector2 uv0, Vector2 uv1, Color tintColor, Color borderColor )
	{
		_ = new ImageWidget( CurrentWindow, texture, size, uv0, uv1, tintColor, borderColor );
	}

	public static void Image( Texture texture, Vector2 size, Color tintColor, Color borderColor )
	{
		Image( texture, size, Vector2.Zero, Vector2.One, tintColor, borderColor );
	}
}
using System;
using System.Collections.Generic;

namespace Duccsoft.ImGui;

public class IdStack
{
	private struct HashData
	{
		public HashData( string id )
		{
			StringSource = id;
		}

		public HashData( int id )
		{
			IntSource = id;
		}

		public string StringSource { get; set; }
		public int IntSource { get; set; }
		
		public override int GetHashCode()
		{
			return HashCode.Combine( StringSource, IntSource );
		}
	}

	private Stack<HashData> _data = new();
	private Stack<int> _hashes = new();
	private int GetSeed()
	{
		if ( _hashes.Count == 0 )
		{
			return 0;
		}
		else
		{
			return _hashes.Peek();
		}
	}

	public void Clear()
	{
		_data.Clear();
		_hashes.Clear();
	}

	public int GetHash( string id ) => HashCode.Combine( GetSeed(), id );
	public int GetHash( int id ) => HashCode.Combine( GetSeed(), id );
	private int GetHash( HashData id ) => HashCode.Combine( GetSeed(), id.GetHashCode() );

	public void Push( string id ) => Push( new HashData( id ) );
	public void Push( int id ) => Push( new HashData( id ) );
	private void Push( HashData data )
	{
		_data.Push( data );
		var hash = GetHash( data );
		_hashes.Push( hash );
	}

	public void Pop()
	{
		_data.Pop();
		_hashes.Pop();
	}

}
using System;
using System.Collections.Generic;
using System.Linq;

namespace Duccsoft.ImGui;

internal class ReflectionCache : IHotloadManaged
{
	private Dictionary<Type, TypeDescription> _typeCache { get; set; } = new();
	private Dictionary<Type, List<PropertyDescription>> _propertyCache { get; set; } = new();
	public TypeDescription GetTypeDescription( Type type )
	{
		ArgumentNullException.ThrowIfNull( type );
		if ( !_typeCache.TryGetValue( type, out var typeDesc ) )
		{
			typeDesc = TypeLibrary.GetType( type );
			if ( typeDesc is null )
				throw new Exception( $"Type {type?.FullName} not found in {nameof( TypeLibrary )}" );
			_typeCache[type] = typeDesc;
		}
		return _typeCache[type];
	}

	public List<PropertyDescription> GetProperties( Type type )
	{
		ArgumentNullException.ThrowIfNull( type );
		if ( !_propertyCache.TryGetValue( type, out var properties ) )
		{
			var typeDesc = GetTypeDescription( type );
			properties = typeDesc.Properties
				.Where( p => p.HasAttribute<PropertyAttribute>() )
				.ToList();
			_propertyCache[type] = properties;
		}
		return _propertyCache[type];
	}

	private void Clear()
	{
		_typeCache?.Clear();
		_propertyCache?.Clear();
		_typeCache ??= new();
		_propertyCache ??= new();
	}

	public void Created( IReadOnlyDictionary<string, object> state ) => Clear();
	public void Persisted() => Clear();
}
using Sandbox.Rendering;
using System;

namespace Duccsoft.ImGui.Rendering;

public class ImDrawList
{
	public ImDrawList( string name )
	{
		CommandList = new CommandList( $"ImGui DrawList {name}" )
		{
			Flags = CommandList.Flag.Hud
		};
	}

	public CommandList CommandList { get; private set; }

	#region Rect
	public void AddRect( Vector2 upperLeft, Vector2 lowerRight, Color32 color, float rounding = 0f, ImDrawFlags flags = ImDrawFlags.None, float thickness = 1.0f )
	{
		DrawRect( upperLeft, lowerRight, Color.Transparent, color, rounding, flags, thickness );
	}
	public void AddRectFilled( Vector2 upperLeft, Vector2 lowerRight, Color32 color, float rounding = 0f, ImDrawFlags flags = ImDrawFlags.None )
		=> DrawRect( upperLeft, lowerRight, color, Color.Transparent, rounding, flags, borderThickness: 0f );

	private void DrawRect( Vector2 upperLeft, Vector2 lowerRight, Color fillColor, Color borderColor, float rounding, ImDrawFlags flags, float borderThickness )
	{
		// Transform
		CommandList.Set( "BoxPosition", upperLeft );
		CommandList.Set( "BoxSize", lowerRight - upperLeft );

		// Background
		CommandList.SetCombo( "D_BACKGROUND_IMAGE", 0 );
		if ( borderThickness >= 1f )
		{
			// Border
			CommandList.Set( "HasBorder", 1 );
			// TODO: Use ImDrawFlags to determine which borders are rounded.
			CommandList.Set( "BorderSize", borderThickness );
			CommandList.Set( "BorderRadius", rounding );
			CommandList.Set( "BorderColorL", borderColor );
			CommandList.Set( "BorderColorT", borderColor );
			CommandList.Set( "BorderColorR", borderColor );
			CommandList.Set( "BorderColorB", borderColor );
			CommandList.SetCombo( "D_BORDER_IMAGE", 0 );
		}

		CommandList.DrawQuad( new Rect( upperLeft, lowerRight - upperLeft ), Material.UI.Box, fillColor );
	}
	#endregion

	#region Triangle
	//public void AddTriangleFilled( Vector2 p1, Vector2 p2, Vector3 p3, Color32 color )
	//{
	//	throw new NotImplementedException();
	//}
	#endregion

	#region Text
	private static TextRendering.Scope TextScope( string text, Color color ) 
		=> new( text, color, ImGui.GetTextLineHeight(), "Consolas" );

	public void AddText( Vector2 pos, Color32 color, string text, TextFlag flags = TextFlag.LeftTop )
		=> DrawText( new Rect( pos, 1f ), TextScope( text, color ), flags );

	private void DrawText( Rect rect, TextRendering.Scope scope, TextFlag flags )
	{
		var textTexture = TextRendering.GetOrCreateTexture( in scope, clip: default, flags );
		if ( !textTexture.IsValid() )
			return;

		CommandList.Set( "TextureIndex", textTexture.Index );
		var size = textTexture.Size;
		rect = rect.Align( size, flags );
		CommandList.DrawQuad( rect, Material.FromShader( "shaders/ui_text.shader" ), Color.White );
	}
	#endregion

	#region Image
	public void AddImage( Texture texture, Vector2 upperLeft, Vector2 lowerRight, Vector2 uv0, Vector2 uv1, Color32 tintColor )
		=> DrawImage( texture, upperLeft, lowerRight, uv0, uv1, tintColor );

	public void AddImage( Texture texture, Vector2 upperLeft, Vector2 lowerRight )
		=> AddImage( texture, upperLeft, lowerRight, uv0: new Vector2( 0, 0 ), uv1: new Vector2( 1, 1 ), tintColor: Color.White );

	private void DrawImage( Texture texture, Vector2 upperLeft, Vector2 lowerRight, Vector2 uv0, Vector2 uv1, Color32 tintColor )
	{
		if ( !texture.IsValid() )
			return;

		// Transform
		CommandList.Set( "BoxPosition", upperLeft );
		CommandList.Set( "BoxSize", lowerRight - upperLeft );

		// Background
		CommandList.SetCombo( "D_BACKGROUND_IMAGE", 1 );
		CommandList.Set( "BgRepeat", -1 );
		CommandList.Set( "TextureIndex", texture.Index );
		var texToRectScale = 1f / (texture.Size / (lowerRight - upperLeft));
		var offset = uv0 * texture.Size * texToRectScale;
		var size = uv1 * texture.Size * texToRectScale - offset;
		var bgPos = new Vector4( offset.x, offset.y, size.x, size.y );
		CommandList.Set( "BgPos", bgPos );

		// Border
		CommandList.Set( "HasBorder", 0 );

		CommandList.DrawQuad( new Rect( upperLeft, lowerRight - upperLeft ), Material.FromShader( "shaders/imgui_rect.shader"), tintColor );
	}
	#endregion Image
}
global using static Sandbox.Internal.GlobalGameNamespace;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Second Order Dynamics" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "secondorderdynamics" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "andicraft" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "andicraft.secondorderdynamics" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "1/17/2025 7:48:34 PM" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "17" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.140.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.140.0")]
using Andicraft.SecondOrderDynamics;
using Sandbox;

namespace Andicraft.SecondOrderDynamics.Components;

[Title( "Second Order Dynamics - Follow Target" ), Category("Second Order Dynamics")]
public class FollowTargetComponent : Component
{
	[Property] public GameObject FollowTarget { get; set; }

	/// <summary>
	/// Reset the dynamics if the object moves further than this distance in one frame
	/// </summary>
	[Property] public float TeleportDistanceThreshold { get; set; } = 32 * 10;

	[Property] public DynamicsParameters PositionParameters { get; set; } = new();
	[Property] public DynamicsParameters RotationParameters { get; set; } = new();

	private Vector3Dynamics _positionDynamics;
	private RotationDynamics _rotationDynamics;

	private Vector3 _previousFollowPos = default;
	
	protected override void OnStart()
	{
		_positionDynamics = new Vector3Dynamics( PositionParameters, FollowTarget.WorldPosition );
		_rotationDynamics = new RotationDynamics( RotationParameters, FollowTarget.WorldRotation );

		_previousFollowPos = FollowTarget.WorldPosition;
	}

	protected override void OnUpdate()
	{
		if ( FollowTarget == null ) return;

		if ( FollowTarget.WorldPosition.DistanceSquared( _previousFollowPos ) >
		     TeleportDistanceThreshold * TeleportDistanceThreshold )
		{
			Warp();
			_previousFollowPos = FollowTarget.WorldPosition;
			return;
		}
		
		WorldPosition = _positionDynamics.Update( Time.Delta, FollowTarget.WorldPosition );
		WorldRotation = _rotationDynamics.Update( Time.Delta, FollowTarget.WorldRotation );

		_previousFollowPos = FollowTarget.WorldPosition;
	}

	/// <summary>
	/// Resets the following target, useful for when you teleport or jump somewhere
	/// </summary>
	public void Warp()
	{
		_positionDynamics.Reset( FollowTarget.WorldPosition );
		_rotationDynamics.Reset( FollowTarget.WorldRotation );

		WorldPosition = FollowTarget.WorldPosition;
		WorldRotation = FollowTarget.WorldRotation;
	}

	public void UpdatePositionParameters( DynamicsParameters p )
	{
		PositionParameters = p;
		_positionDynamics.UpdateParameters( p );
	}

	public void UpdateRotationParameters( DynamicsParameters p )
	{
		RotationParameters = p;
		_rotationDynamics.UpdateParameters( p );
	}
}
using Sandbox;

[Title("[DEMO] Follow Mouse On Plane"), Category("Second Order Dynamics (Demo)")]
public sealed class DemoFollowMouse : Component
{
	[Property] private CameraComponent Camera { get; set; }
	[Property] private float PlaneDistance { get; set; } = 100;
	[Property] private GameObject TargetObject { get; set; }
	
	protected override void OnUpdate()
	{
		var ray = Camera.ScreenPixelToRay( Mouse.Position );
		var p = new Plane( Camera.WorldPosition + Camera.WorldTransform.Forward * PlaneDistance,
			Camera.WorldTransform.Backward );
		var pt = p.Trace( ray );
		
		if (pt.HasValue)
			TargetObject.WorldPosition = pt.Value;
	}
}
using System;
using Sandbox;

namespace Andicraft.SecondOrderDynamics;

/// <summary>
/// Helper class that lets you run dynamics on a Color.
/// </summary>
public class ColorDynamics
{
	private FloatDynamics _h;
	private Vector2Dynamics _sv;
	private FloatDynamics _alpha;

	private Vector3 _currentHsv;
	private Color _currentColor;

	public Color CurrentValue => _currentColor;
	public float CurrentAlpha => _currentColor.a;

	public ColorDynamics(float frequency, float damping, float response, Color startingValue)
	{
		_currentHsv = startingValue.ToOkHsv();

		_h = new(frequency, damping, response, _currentHsv.x)
		{
			MinWrapValue = 0f,
			MaxWrapValue = 1f
		};

		_sv = new Vector2Dynamics(frequency, damping, response, new Vector2(_currentHsv.z, _currentHsv.y));

		_alpha = new FloatDynamics(frequency, damping, response, startingValue.a);
	}

	public void SetFrequency(float f)
	{
		_h.SetFrequency(f);
		_sv.SetFrequency(f);
		_alpha.SetFrequency(f);
	}

	public void SetDamping(float d)
	{
		_h.SetDamping(d);
		_sv.SetDamping(d);
		_alpha.SetDamping(d);
	}

	public void SetResponse(float r)
	{
		_h.SetResponse(r);
		_sv.SetResponse(r);
		_alpha.SetResponse(r);
	}

	public void Reset(Color value)
	{
		var targetColor = value.ToOkHsv();
		_currentColor = value;
		_currentHsv = targetColor;

		_h.Reset(_currentHsv.x);
		_sv.Reset(new Vector2(_currentHsv.y, _currentHsv.z));
		_alpha.Reset(value.a);
	}

	public Color Update(float deltaTime, Color target, bool setVelocity = false, Vector4 velocity = default)
	{
		var targetHsv = target.ToOkHsv();
		var h = _h.Update(deltaTime, targetHsv.x, setVelocity, velocity.x);
		var sv = _sv.Update(deltaTime, new Vector2(targetHsv.y, targetHsv.z), setVelocity,
			new Vector2(velocity.y, velocity.z));
		var a = _alpha.Update(deltaTime, target.a, setVelocity, velocity.w);
		_currentHsv = new Vector3(h, sv.x.Saturate(), sv.y.Saturate());

		var col = OkColor.OkHsvToColor(_currentHsv);
		col.a = a.Saturate();
		_currentColor = col;
		return _currentColor;
	}
}
// Copyright(c) 2021 Bjorn Ottosson
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files(the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

using System;

namespace Andicraft.SecondOrderDynamics;

// Straight up port of OKColor to allow ColorDynamics to work nicely
public static class OkColor
{

    // Finds the maximum saturation possible for a given hue that fits in sRGB
    // Saturation here is defined as S = C/L
    // a and b must be normalized so a^2 + b^2 == 1
    private static double ComputeMaxSaturation (in double a, in double b)
    {
        // Max saturation will be when one of r, g or b goes below zero.

        // Select different coefficients depending on which component goes below zero first
        double k0, k1, k2, k3, k4, wl, wm, ws;

        if (-1.88170328d * a - 0.80936493d * b > 1.0d)
        {
            // Re component
            k0 = 1.19086277d;
            k1 = 1.76576728d;
            k2 = 0.59662641d;
            k3 = 0.75515197d;
            k4 = 0.56771245d;
            wl = 4.0767416621d;
            wm = -3.3077115913d;
            ws = 0.2309699292d;
        }
        else if (1.81444104d * a - 1.19445276d * b > 1.0d)
        {
            // Green component
            k0 = 0.73956515d;
            k1 = -0.45954404d;
            k2 = 0.08285427d;
            k3 = 0.1254107d;
            k4 = 0.14503204d;
            wl = -1.2684380046d;
            wm = 2.6097574011d;
            ws = -0.3413193965d;
        }
        else
        {
            // Blue component
            k0 = 1.35733652d;
            k1 = -0.00915799d;
            k2 = -1.1513021d;
            k3 = -0.50559606d;
            k4 = +0.00692167d;
            wl = -0.0041960863d;
            wm = -0.7034186147d;
            ws = 1.707614701d;
        }

        // Approximate max saturation using a polynomial:
        double S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;

        // Do one step Halley's method to get closer
        // this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
        // this should be sufficient for most applications, otherwise do two/three steps

        double k_l = 0.3963377774d * a + 0.2158037573d * b;
        double k_m = -0.1055613458d * a - 0.0638541728d * b;
        double k_s = -0.0894841775d * a - 1.291485548d * b;

        {
            double l_ = 1.0d + S * k_l;
            double m_ = 1.0d + S * k_m;
            double s_ = 1.0d + S * k_s;

            double l = l_ * l_ * l_;
            double m = m_ * m_ * m_;
            double s = s_ * s_ * s_;

            double l_dS = 3.0d * k_l * l_ * l_;
            double m_dS = 3.0d * k_m * m_ * m_;
            double s_dS = 3.0d * k_s * s_ * s_;

            double l_dS2 = 6.0d * k_l * k_l * l_;
            double m_dS2 = 6.0d * k_m * k_m * m_;
            double s_dS2 = 6.0d * k_s * k_s * s_;

            double f = wl * l + wm * m + ws * s;
            double f1 = wl * l_dS + wm * m_dS + ws * s_dS;
            double f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;

            double sDenom = (f1 * f1 - 0.5d * f * f2);
            if (sDenom != 0.0d) { S = S - f * f1 / sDenom; }
        }

        return S;
    }

    private static (double L, double C) FindCusp (in double a, in double b)
    {
        // First, find the maximum saturation (saturation S = C/L)
        double S_cusp = OkColor.ComputeMaxSaturation (a, b);

        // Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
        var rgb_at_max = OkColor.OkLabToLinearSrgb ((L: 1.0d, a: S_cusp * a, b: S_cusp * b));
        double L_cusp = Math.Pow (1.0d / Math.Max (Math.Max (rgb_at_max.r, rgb_at_max.g), rgb_at_max.b), 1.0d / 3.0d);
        double C_cusp = L_cusp * S_cusp;

        return (L: L_cusp, C: C_cusp);
    }

    // Finds intersection of the line defined by
    // L = L0 * (1 - t) + t * L1;
    // C = t * C1;
    // a and b must be normalized so a^2 + b^2 == 1
    private static double FindGamutIntersection (in double a, in double b, in double L1, in double C1, in double L0, (double L, double C) cusp)
    {
        // Find the intersection for upper and lower half seprately
        double t = 0.0d;
        if (((L1 - L0) * cusp.C - (cusp.L - L0) * C1) <= 0.0d)
        {
            // Lower half
            double tDenom = (C1 * cusp.L + cusp.C * (L0 - L1));
            if (tDenom != 0.0d) { t = cusp.C * L0 / tDenom; }
        }
        else
        {
            // Upper half

            // First intersect with triangle
            double tDenom = C1 * (cusp.L - 1.0d) + cusp.C * (L0 - L1);
            if (tDenom != 0.0d) { t = cusp.C * (L0 - 1.0d) / tDenom; }

            // Then one step Halley's method
            {
                double dL = L1 - L0;
                double dC = C1;

                double k_l = 0.3963377774d * a + 0.2158037573d * b;
                double k_m = -0.1055613458d * a - 0.0638541728d * b;
                double k_s = -0.0894841775d * a - 1.2914855480d * b;

                double l_dt = dL + dC * k_l;
                double m_dt = dL + dC * k_m;
                double s_dt = dL + dC * k_s;

                // If higher accuracy is required, 2 or 3 iterations of the following block can be used:
                {
                    double L = L0 * (1.0d - t) + t * L1;
                    double C = t * C1;

                    double l_ = L + C * k_l;
                    double m_ = L + C * k_m;
                    double s_ = L + C * k_s;

                    double l = l_ * l_ * l_;
                    double m = m_ * m_ * m_;
                    double s = s_ * s_ * s_;

                    double ldt = 3.0d * l_dt * l_ * l_;
                    double mdt = 3.0d * m_dt * m_ * m_;
                    double sdt = 3.0d * s_dt * s_ * s_;

                    double ldt2 = 6.0d * l_dt * l_dt * l_;
                    double mdt2 = 6.0d * m_dt * m_dt * m_;
                    double sdt2 = 6.0d * s_dt * s_dt * s_;

                    double r0 = 4.0767416621d * l - 3.3077115913d * m + 0.2309699292d * s - 1.0d;
                    double r1 = 4.0767416621d * ldt - 3.3077115913d * mdt + 0.2309699292d * sdt;
                    double r2 = 4.0767416621d * ldt2 - 3.3077115913d * mdt2 + 0.2309699292d * sdt2;

                    double rDenom = r1 * r1 - 0.5d * r0 * r2;
                    double ur = (rDenom != 0.0d) ? r1 / rDenom : 0.0d;
                    double tr = -r0 * ur;

                    double g0 = -1.2684380046d * l + 2.6097574011d * m - 0.3413193965d * s - 1.0d;
                    double g1 = -1.2684380046d * ldt + 2.6097574011d * mdt - 0.3413193965d * sdt;
                    double g2 = -1.2684380046d * ldt2 + 2.6097574011d * mdt2 - 0.3413193965d * sdt2;

                    double gDenom = g1 * g1 - 0.5d * g0 * g2;
                    double ug = (gDenom != 0.0d) ? g1 / gDenom : 0.0d;
                    double tg = -g0 * ug;

                    double b0 = -0.0041960863d * l - 0.7034186147d * m + 1.7076147010d * s - 1.0d;
                    double b1 = -0.0041960863d * ldt - 0.7034186147d * mdt + 1.7076147010d * sdt;
                    double b2 = -0.0041960863d * ldt2 - 0.7034186147d * mdt2 + 1.7076147010d * sdt2;

                    double bDenom = b1 * b1 - 0.5d * b0 * b2;
                    double ub = (bDenom != 0.0d) ? b1 / bDenom : 0.0d;
                    double tb = -b0 * ub;

                    tr = ur >= 0.0d ? tr : Single.MaxValue;
                    tg = ug >= 0.0d ? tg : Single.MaxValue;
                    tb = ub >= 0.0d ? tb : Single.MaxValue;

                    t = t + Math.Min (tr, Math.Min (tg, tb));
                }
            }
        }

        return t;
    }

    private static (double C_0, double C_mid, double C_max) GetCs (in double L, in double a_, in double b_)
    {
        var cusp = OkColor.FindCusp (a_, b_);

        double C_max = OkColor.FindGamutIntersection (a_, b_, L, 1.0d, L, cusp);
        var ST_max = OkColor.ToSt (cusp);

        // Scale factor to compensate for the curved part of gamut shape:
        double k = C_max / Math.Min ((L * ST_max.S), (1.0d - L) * ST_max.T);

        double C_mid = 0.0d;
        {
            var ST_mid = OkColor.GetSTMid (a_, b_);

            // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
            double C_a = L * ST_mid.S;
            double C_b = (1.0d - L) * ST_mid.T;
            double cae4 = C_a * C_a * C_a * C_a;
            double cbe4 = C_b * C_b * C_b * C_b;
            C_mid = 0.9d * k * Math.Sqrt (Math.Sqrt (1.0d / (1.0d / cae4 + 1.0d / cbe4)));
        }

        double C_0 = 0.0d;
        {
            // for C_0, the shape is independent of hue, so ST are constant. Values picked to roughly be the average values of ST.
            double C_a = L * 0.4d;
            double C_b = (1.0d - L) * 0.8d;

            // Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
            C_0 = Math.Sqrt (1.0d / (1.0d / (C_a * C_a) + 1.0d / (C_b * C_b)));
        }

        return (C_0: C_0, C_mid: C_mid, C_max: C_max);
    }

    // Returns a smooth approximation of the location of the cusp
    // This polynomial was created by an optimization process
    // It has been designed so that S_mid < S_max and T_mid < T_max
    private static (double S, double T) GetSTMid (in double a_, in double b_)
    {
        double S = 0.11516993d;
        double sDenom = 7.4477897d + 4.1590124d * b_ +
            a_ * (-2.19557347d + 1.75198401d * b_ +
                a_ * (-2.13704948d - 10.02301043d * b_ +
                    a_ * (-4.24894561d + 5.38770819d * b_ +
                        4.69891013d * a_)));
        if (sDenom != 0.0d) { S += 1.0d / sDenom; }

        double T = 0.11239642d;
        double tDenom = 1.6132032d - 0.68124379d * b_ +
            a_ * (0.40370612d + 0.90148123d * b_ +
                a_ * (-0.27087943d + 0.6122399d * b_ +
                    a_ * (0.00299215d - 0.45399568d * b_ - 0.14661872d * a_)));
        if (tDenom != 0.0d) { T += 1.0d / tDenom; }

        return (S: S, T: T);
    }

    private static (double L, double a, double b) LinearSrgbToOkLab (in (double r, double g, double b) c)
    {
        double cr = c.r;
        double cg = c.g;
        double cb = c.b;

        double l = 0.4122214708d * cr + 0.5363325363d * cg + 0.0514459929d * cb;
        double m = 0.2119034982d * cr + 0.6806995451d * cg + 0.1073969566d * cb;
        double s = 0.0883024619d * cr + 0.2817188376d * cg + 0.6299787005d * cb;

        double lCbrt = Math.Pow (l, 1.0d / 3.0d);
        double mCbrt = Math.Pow (m, 1.0d / 3.0d);
        double sCbrt = Math.Pow (s, 1.0d / 3.0d);

        return (
            L: 0.2104542553d * lCbrt + 0.793617785d * mCbrt - 0.0040720468d * sCbrt,
            a: 1.9779984951d * lCbrt - 2.428592205d * mCbrt + 0.4505937099d * sCbrt,
            b: 0.0259040371d * lCbrt + 0.7827717662d * mCbrt - 0.808675766d * sCbrt);
    }

    public static (double L, double a, double b) OkHslToOkLab (in (double h, double s, double l) hsl)
    {
        // With single-precision numbers, this can generate invalid values, NaNs, infinities, etc.

        double l = hsl.l;
        if (l >= 1.0d) { return (L: 1.0d, a: 0.0d, b: 0.0d); }
        if (l <= 0.0d) { return (L: 0.0d, a: 0.0d, b: 0.0d); }

        double s = hsl.s;
        if (s < 0.0d) { s = 0.0d; }
        if (s > 1.0d) { s = 1.0d; }

        double hRad = hsl.h * 6.283185307179586d;
        double a_ = Math.Cos (hRad);
        double b_ = Math.Sin (hRad);
        double L = OkColor.ToeInv (l);

        var cs = OkColor.GetCs (L, a_, b_);
        double c0 = cs.C_0;
        double cMid = cs.C_mid;
        double cMax = cs.C_max;

        double mid = 0.8d;
        double midInv = 1.25d;

        double C = 0.0d;
        double t = 0.0d;
        double k0 = 0.0d;
        double k1 = 0.0d;
        double k2 = 1.0d;

        if (s < mid)
        {
            t = midInv * s;

            k1 = mid * c0;
            if (cMid != 0.0d) { k2 = (1.0d - k1 / cMid); }

            double kDenom = 1.0d - k2 * t;
            if (kDenom != 0.0d) { C = t * k1 / kDenom; }
        }
        else
        {
            double tDenom = 1.0d - mid;
            if (tDenom != 0.0d) { t = (s - mid) / tDenom; }

            k0 = cMid;
            if (c0 != 0.0d) { k1 = (1.0d - mid) * cMid * cMid * midInv * midInv / c0; }

            double cDenom = cMax - cMid;
            k2 = 1.0d;
            if (cDenom != 0.0d) { k2 = 1.0d - k1 / cDenom; }

            double kDenom = 1.0d - k2 * t;
            if (kDenom != 0.0d) { C = k0 + t * k1 / kDenom; }
        }

        return (L: L, a: C * a_, b: C * b_);
    }

    public static (double r, double g, double b) OkHslToSrgb (in (double h, double s, double l) hsl)
    {
        return OkColor.OkLabToSrgb (OkColor.OkHslToOkLab (hsl));
    }

    public static (double L, double a, double b) OkHsvToOkLab (in (double h, double s, double v) hsv)
    {
        double v = hsv.v;
        if (v <= 0.0d) { return (L: 0.0d, a: 0.0d, b: 0.0d); }
        if (v > 1.0d) { v = 1.0d; }

        double s = hsv.s;
        if (s < 0.0d) { s = 0.0d; }
        if (s > 1.0d) { s = 1.0d; }

        double hRad = hsv.h * 6.283185307179586d;
        double a_ = Math.Cos (hRad);
        double b_ = Math.Sin (hRad);

        var cusp = OkColor.FindCusp (a_, b_);
        var stMax = OkColor.ToSt (cusp);
        double sMax = stMax.S;
        double tMax = stMax.T;
        double s0 = 0.5d;
        double k = 1.0d;
        if (sMax != 0.0d) { k = 1.0d - s0 / sMax; }

        // first we compute L and V as if the gamut is a perfect triangle:

        // L, C when v==1:
        double vDenom = s0 + tMax - tMax * k * s;
        double lv = 1.0d;
        double cv = 0.0d;
        if (vDenom != 0.0d)
        {
            lv = 1.0d - s * s0 / vDenom;
            cv = s * tMax * s0 / vDenom;
        }

        double L = v * lv;
        double C = v * cv;

        // then we compensate for both toe and the curved top part of the triangle:
        double lvt = OkColor.ToeInv (lv);
        double cvt = 0.0d;
        if (lv != 0.0d) { cvt = cv * lvt / lv; }

        double lNew = OkColor.ToeInv (L);
        if (L != 0.0d) { C = C * lNew / L; }
        L = lNew;

        var rgbScale = OkColor.OkLabToLinearSrgb ((L: lvt, a: a_ * cvt, b: b_ * cvt));
        double maxComp = Math.Max (rgbScale.r, Math.Max (rgbScale.g, Math.Max (rgbScale.b, 0.0d)));
        double lScale = 0.0d;
        if (maxComp != 0.0d)
        {
            lScale = Math.Pow (1.0d / maxComp, 1.0d / 3.0d);
        }

        C = C * lScale;
        return (
            L: L * lScale,
            a: C * a_,
            b: C * b_);
    }

    public static (double r, double g, double b) OkHsvToSrgb (in (double h, double s, double v) hsv)
    {
        return OkColor.OkLabToSrgb (OkColor.OkHsvToOkLab (hsv));
    }

    static (double r, double g, double b) OkLabToLinearSrgb (in (double L, double a, double b) c)
    {
        double cl = c.L;
        double ca = c.a;
        double cb = c.b;

        double lCbrt = cl + 0.3963377774d * ca + 0.2158037573d * cb;
        double mCbrt = cl - 0.1055613458d * ca - 0.0638541728d * cb;
        double sCbrt = cl - 0.0894841775d * ca - 1.291485548d * cb;

        double l = lCbrt * lCbrt * lCbrt;
        double m = mCbrt * mCbrt * mCbrt;
        double s = sCbrt * sCbrt * sCbrt;

        return (
            r: 4.0767416621d * l - 3.3077115913d * m + 0.2309699292d * s,
            g: -1.2684380046d * l + 2.6097574011d * m - 0.3413193965d * s,
            b: -0.0041960863d * l - 0.7034186147d * m + 1.707614701d * s);
    }

    public static (double h, double s, double l) OkLabToOkHsl (in (double L, double a, double b) lab)
    {
        double L = lab.L;
        if (L > 1.0d - Single.Epsilon) { return (h: 0.0d, s: 0.0d, l: 1.0d); }
        if (L < Single.Epsilon) { return (h: 0.0d, s: 0.0d, l: 0.0d); }

        // This has to be gt epsilon, not gt zero to avoid glitches.
        double Csq = lab.a * lab.a + lab.b * lab.b;
        if (Csq > Single.Epsilon)
        {
            double C = Math.Sqrt (Csq);
            double a_ = lab.a / C;
            double b_ = lab.b / C;

            // 1.0 / math.pi = 0.3183098861837907
            double h = 0.5d + 0.5d * (Math.Atan2 (-lab.b, -lab.a) * 0.3183098861837907d);

            var cs = OkColor.GetCs (L, a_, b_);
            double c0 = cs.C_0;
            double cMid = cs.C_mid;
            double cMax = cs.C_max;

            // Inverse of the interpolation in okhsl_to_srgb:
            double mid = 0.8d;
            double midInv = 1.25d;

            double s = 0.0d;
            if (C < cMid)
            {
                double k1 = mid * c0;
                double k2 = 1.0d;
                if (cMid != 0.0d) { k2 = (1.0d - k1 / cMid); }

                double tDenom = k1 + k2 * C;
                double t = 0.0d;
                if (tDenom != 0.0d) { t = C / tDenom; }

                s = t * mid;
            }
            else
            {
                double k0 = cMid;
                double k1 = 0.0d;
                if (c0 != 0.0d)
                {
                    k1 = (1.0d - mid) * cMid * cMid * midInv * midInv / c0;
                }

                double cDenom = cMax - cMid;
                double k2 = 1.0d;
                if (cDenom != 0.0d) { k2 = 1.0d - k1 / cDenom; }

                double tDenom = k1 + k2 * (C - k0);
                double t = 0.0d;
                if (tDenom != 0.0d) { t = (C - k0) / tDenom; }

                s = mid + (1.0d - mid) * t;
            }

            return (h: h, s: s, l: OkColor.Toe (L));
        }
        else
        {
            return (h: 0.0d, s: 0.0d, l: L);
        }
    }

    public static (double h, double s, double v) OkLabToOkHsv (in (double L, double a, double b) lab)
    {
        double L = lab.L;
        if (L > 1.0d - Single.Epsilon) { return (h: 0.0d, s: 0.0d, v: 1.0d); }
        if (L < Single.Epsilon) { return (h: 0.0d, s: 0.0d, v: 0.0d); }

        // This has to be gt epsilon, not gt zero to avoid glitches.
        double csq = lab.a * lab.a + lab.b * lab.b;
        if (csq > Single.Epsilon)
        {
            double C = Math.Sqrt (csq);
            double a_ = lab.a / C;
            double b_ = lab.b / C;

            // 1.0 / math.pi = 0.3183098861837907
            double h = 0.5d + 0.5d * (Math.Atan2 (-lab.b, -lab.a) * 0.3183098861837907d);

            var cusp = OkColor.FindCusp (a_, b_);
            var stMax = OkColor.ToSt (cusp);
            double sMax = stMax.S;
            double tMax = stMax.T;
            double s0 = 0.5d;
            double k = 1.0d;
            if (sMax != 0.0d) { k = 1.0d - s0 / sMax; }

            // first we find L_v, C_v, L_vt and C_vt
            double tDenom = C + L * tMax;
            double t = 0.0d;
            if (tDenom != 0.0d) { t = tMax / tDenom; }
            double lv = t * L;
            double cv = t * C;

            double lvt = OkColor.ToeInv (lv);
            double cvt = 0.0d;
            if (lv != 0.0d) { cvt = cv * lvt / lv; }

            // we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:
            var rgbScale = OkColor.OkLabToLinearSrgb (
                (L: lvt,
                    a: a_ * cvt,
                    b: b_ * cvt));
            double scaleDenom = Math.Max (rgbScale.r, Math.Max (rgbScale.g, Math.Max (rgbScale.b, 0.0d)));
            double lScale = 0.0d;
            if (scaleDenom != 0.0d)
            {
                lScale = Math.Pow (1.0d / scaleDenom, 1.0d / 3.0d);
                L = L / lScale;
                C = C / lScale;
            }

            double toel = OkColor.Toe (L);
            C = C * toel / L;
            L = toel;

            // we can now compute v and s:
            double v = 0.0d;
            if (lv != 0.0d) { v = L / lv; }

            double s = 0.0d;
            double sDenom = ((tMax * s0) + tMax * k * cv);
            if (sDenom != 0.0d) { s = (s0 + tMax) * cv / sDenom; }

            return (h: h, s: s, v: v);
        }
        else
        {
            return (h: 0.0d, s: 0.0d, v: L);
        }
    }

    public static (double r, double g, double b) OkLabToSrgb (in (double L, double a, double b) lab)
    {
        var lrgb = OkColor.OkLabToLinearSrgb (lab);
        return (
            r: OkColor.SrgbTransferFunction (lrgb.r),
            g: OkColor.SrgbTransferFunction (lrgb.g),
            b: OkColor.SrgbTransferFunction (lrgb.b));
    }

    public static (double h, double s, double l) SrgbToOkHsl (in (double r, double g, double b) srgb)
    {
        return OkColor.OkLabToOkHsl (OkColor.SrgbToOkLab (srgb));
    }

    public static (double h, double s, double v) SrgbToOkHsv (in (double r, double g, double b) srgb)
    {
        return OkColor.OkLabToOkHsv (OkColor.SrgbToOkLab (srgb));
    }

    public static (double L, double a, double b) SrgbToOkLab (in (double r, double g, double b) srgb)
    {
        return OkColor.LinearSrgbToOkLab ((
            r: OkColor.SrgbTransferFunctionInv (srgb.r),
            g: OkColor.SrgbTransferFunctionInv (srgb.g),
            b: OkColor.SrgbTransferFunctionInv (srgb.b)));
    }

    static double SrgbTransferFunction (in double a)
    {
        return 0.0031308d >= a ? 12.92d * a : 1.055d * Math.Pow (a, 1.0d / 2.4d) - 0.055d;
    }

    static double SrgbTransferFunctionInv (in double a)
    {
        return 0.04045d < a ? Math.Pow ((a + 0.055d) / 1.055d, 2.4d) : a / 12.92d;
    }

    static (double S, double T) ToSt (in (double L, double C) cusp)
    {
        double L = cusp.L;
        double C = cusp.C;
        if (L != 0.0d && L != 1.0d)
        {
            return (S: C / L, T: C / (1.0d - L));
        }
        else if (L != 0.0d)
        {
            return (S: C / L, T: 0.0d);
        }
        else if (L != 1.0d)
        {
            return (S: 0.0d, T: C / (1.0d - L));
        }
        else
        {
            return (S: 0.0d, T: 0.0d);
        }
    }

    static double Toe (in double x)
    {
        double y = 1.170873786407767d * x - 0.206d;
        return 0.5d * (y + Math.Sqrt (y * y + 0.14050485436893204d * x));
    }

    static double ToeInv (in double x)
    {
        double denom = 1.170873786407767d * (x + 0.03d);
        return (denom != 0.0) ? (x * x + 0.206d * x) / denom : 0.0d;
    }

    public static Vector3 ToOkHsv(this Color c)
    {
        (double, double, double) col = (c.r, c.g, c.b);
        (double, double, double) hsv = SrgbToOkHsv(col);

        return new Vector3((float)hsv.Item1, (float)hsv.Item2, (float)hsv.Item3);
    }

    public static Color OkHsvToColor(Vector3 hsv)
    {
        (double, double, double) col = (hsv.x, hsv.y, hsv.z);
        (double, double, double) rgb = OkHsvToSrgb(col);

        return new Color((float)rgb.Item1, (float)rgb.Item2, (float)rgb.Item3);
    }
}
using System;

namespace Andicraft.SecondOrderDynamics;

public class Vector4Dynamics : SecondOrderDynamics<Vector4>
{
	/// <inheritdoc/>
	public Vector4Dynamics(Vector4 startingValue) : base(startingValue)
	{
	}

	/// <inheritdoc/>
	public Vector4Dynamics(Vector3 parameters, Vector4 startingValue) : base(parameters, startingValue)
	{
	}

	/// <inheritdoc/>
	public Vector4Dynamics(float frequency, float damping, float response, Vector4 startingValue) : base(frequency, damping, response, startingValue)
	{
	}

	/// <inheritdoc/>
	public override void Reset(Vector4 value)
	{
		_previousValue = value;
		_currentValue = value;
		_velocity = Vector4.Zero;
	}

	/// <inheritdoc/>
	public override Vector4 Update(float deltaTime, Vector4 target, bool setVelocity = false, Vector4 velocity = default)
	{
		if (setVelocity == false)
		{
			velocity = (target - _previousValue) / deltaTime;
			_previousValue = target;
		}

		var k2Stable = MathF.Max(k2, MathF.Max(deltaTime * deltaTime / 2 + deltaTime * k1 / 2, deltaTime * k1));
		_currentValue += deltaTime * _velocity;
		_velocity += deltaTime * (target + k3 * velocity - _currentValue - k1 * _velocity) / k2Stable;
		return _currentValue;
	}
}
global using Microsoft.AspNetCore.Components; 
global using Microsoft.AspNetCore.Components.Rendering;
namespace RbxlReader.DataTypes;

public class NumberSequenceKeypoint {
    public float Time;
    public float Value;
    public float Envelope;
}

public class NumberSequence {
    public NumberSequenceKeypoint[] Keypoints;

    public NumberSequence(NumberSequenceKeypoint[] points) {
        Keypoints = points;
    }
}
namespace RbxlReader.DataTypes;

public class UDim {
    public float Scale;
    public int Offset;

    public UDim() {}

    public UDim(float scale, int offset) {
        Scale = scale;
        Offset = offset;
    }
}
using System.Collections.Generic;
using Sandbox;

[Group("Noblox")]
public class RbxlRoot : Component {
    public InstanceComponent[] IdToInstance {get; set;}

    [Property, ReadOnly]
    public int InstanceCount {get; set;}
    [Property, ReadOnly]
    public int ClassCount {get; set;}

    [Property, ReadOnly]
    public int ImportedObjectCount {get; set;}

    private List<GameObject> debris = new();

    /// <summary>
    /// add to cleanup list, so it will be deleted. Do not use by itself
    /// </summary>
    public void AddToCleanup(GameObject obj) => debris.Add(obj);

    /// <summary>
    /// Cleans up a debris list.
    /// </summary>
    /// <returns>Instances removed total</returns>
    public int Cleanup() {
        int i = 0;
        foreach(var obj in debris) {
            obj.Destroy();
            i++;
        }

        debris = new List<GameObject>();
        return i;
    }
}
using Sandbox;

[Group("Noblox Instances")]
public class RopeComponent : ConstraintComponent {

    [Property, ReadOnly]
    public float Length {get; set;} 

    public override void ConstraintSetup(GameObject part0, GameObject part1) {
        if (part0.GetComponent<InstanceComponent>().ClassName == "Attachment") {
            part0 = part0.Parent;
        }
        if (part1.GetComponent<InstanceComponent>().ClassName == "Attachment") {
            part1 = part1.Parent;
        }

        var joint = part0.Components.GetOrCreate<SpringJoint>();
        joint.Frequency = 10;
        joint.Body = part1;
    }
}
using System.Threading.Tasks;
using Sandbox;
using System.IO;
using Sandbox.Utility;

public sealed class LocalFile : Component
{



	public void Save( string FileName, Curve HeightCurve, int Noise1Type, int Noise1Seed, Curve Noise1Weight, float Noise1Frequency, int Noise2Type, int Noise2Seed, Curve Noise2Weight, float Noise2Frequency, int Noise3Type, int Noise3Seed, Curve Noise3Weight, float Noise3Frequency, int FalloffValue, int Resolution, Vector2 TerrainOffset, Vector2 FalloffCenter, int SampleRes, int Noise1Octave, int Noise2Octave, int Noise3Octave, float Noise1gain, float Noise2gain, float Noise3gain, float Noise1Lac, float Noise2Lac, float Noise3Lac)
	{
		//Makes filename string
		string FloatsName = FileName + " Floats" + ".json";
		string CurvesName = FileName + " Curves" + ".json";

		//Writes data to json
		FileSystem.Data.WriteJson( FloatsName, new float[25] { Noise1Type, Noise1Seed, Noise1Frequency, Noise2Type, Noise2Seed, Noise2Frequency, Noise3Type, Noise3Seed, Noise3Frequency, FalloffValue, Resolution, TerrainOffset.x, TerrainOffset.y, FalloffCenter.x, FalloffCenter.y, SampleRes, Noise1Octave, Noise2Octave, Noise3Octave, Noise1gain, Noise2gain, Noise3gain, Noise1Lac, Noise2Lac, Noise3Lac } );
		FileSystem.Data.WriteJson( CurvesName, new Curve[4] { HeightCurve, Noise1Weight, Noise2Weight, Noise3Weight } );

		

		Log.Info( "Saved " + FileName.ToString() + " as " + FloatsName.ToString() + " and " + CurvesName.ToString());
		Log.Info( "Saved at: " + FileSystem.Data.GetFullPath( FloatsName ).ToString() );

	}

	public void Load( string FileName, out float[] LoadedFloatValues, out Curve[] LoadedCurveValues )
	{
		//Makes filename string
		string FloatsName = FileName + " Floats" + ".json";
		string CurvesName = FileName + " Curves" + ".json";

		LoadedCurveValues = new Curve[4];
		LoadedFloatValues = new float[25];


		if ( FileSystem.Data.ReadJson<float[]>( FloatsName ) != null && FileSystem.Data.ReadJson<Curve[]>( CurvesName ) != null ) //Checks for file
		{
			//Loads File
			LoadedFloatValues = FileSystem.Data.ReadJson<float[]>( FloatsName );
			LoadedCurveValues = FileSystem.Data.ReadJson<Curve[]>( CurvesName );

		}
		else
		{

			Log.Error( "FILE NOT FOUND" );

		}
		


	}



}
using Sandbox;
using System.Numerics;

public sealed class SpawnTrigger : Component, Component.ITriggerListener
{

	public async void OnTriggerEnter( Collider other )
	{
		var player = other.Components.Get<GudeMovement>();

		if ( player != null ) 
		{	
			var gravity = player.Gravity;

			var directionalInfluence = player.DirectionalInfluence;

			var spawn = player.Spawned;

			Log.Info( "Get Ready!!!" );

			if (  spawn == true )
			{
				player.Rigidbody.Velocity = Vector3.Zero;

				player.Gravity = 0;

				player.DirectionalInfluence = 0;

				await Task.DelaySeconds( 2 );

				player.DirectionalInfluence = directionalInfluence;

				player.Gravity = gravity;

			}
		}
	}

	public void OnTriggerExit( Collider other )
	{
		var player = other.Components.Get<GudeMovement>();

		if ( player != null )
		{
			var spawn = player.Spawned;

			Log.Info( "Left Trigger" );

			if ( spawn == true )
			{
				player.Spawned = false;
			}
		}
	}
}
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace Ink.Runtime
{
    public class CallStack
    {
        public class Element
        {
            public Pointer currentPointer;

            public bool inExpressionEvaluation;
            public Dictionary<string, Runtime.Object> temporaryVariables;
            public PushPopType type;

            // When this callstack element is actually a function evaluation called from the game,
            // we need to keep track of the size of the evaluation stack when it was called
            // so that we know whether there was any return value.
            public int evaluationStackHeightWhenPushed;

            // When functions are called, we trim whitespace from the start and end of what
            // they generate, so we make sure know where the function's start and end are.
            public int functionStartInOuputStream;

            public Element(PushPopType type, Pointer pointer, bool inExpressionEvaluation = false) {
                this.currentPointer = pointer;
                this.inExpressionEvaluation = inExpressionEvaluation;
                this.temporaryVariables = new Dictionary<string, Object>();
                this.type = type;
            }

            public Element Copy()
            {
                var copy = new Element (this.type, currentPointer, this.inExpressionEvaluation);
                copy.temporaryVariables = new Dictionary<string,Object>(this.temporaryVariables);
                copy.evaluationStackHeightWhenPushed = evaluationStackHeightWhenPushed;
                copy.functionStartInOuputStream = functionStartInOuputStream;
                return copy;
            }
        }

        public class Thread
        {
            public List<Element> callstack;
            public int threadIndex;
            public Pointer previousPointer;

            public Thread() {
                callstack = new List<Element>();
            }

			public Thread(Dictionary<string, object> jThreadObj, Story storyContext) : this() {
                threadIndex = (int) jThreadObj ["threadIndex"];

				List<object> jThreadCallstack = (List<object>) jThreadObj ["callstack"];
				foreach (object jElTok in jThreadCallstack) {

					var jElementObj = (Dictionary<string, object>)jElTok;

                    PushPopType pushPopType = (PushPopType)(int)jElementObj ["type"];

                    Pointer pointer = Pointer.Null;

					string currentContainerPathStr = null;
					object currentContainerPathStrToken;
					if (jElementObj.TryGetValue ("cPath", out currentContainerPathStrToken)) {
						currentContainerPathStr = currentContainerPathStrToken.ToString ();

                        var threadPointerResult = storyContext.ContentAtPath (new Path (currentContainerPathStr));
                        pointer.container = threadPointerResult.container;
                        pointer.index = (int)jElementObj ["idx"];

                        if (threadPointerResult.obj == null) {
                            throw new System.Exception ("When loading state, internal story location couldn't be found: " + currentContainerPathStr + ". Has the story changed since this save data was created?");
                        } else if (threadPointerResult.approximate) {
                            if (pointer.container != null) {
                                storyContext.Warning ("When loading state, exact internal story location couldn't be found: '" + currentContainerPathStr + "', so it was approximated to '" + pointer.container.path.ToString() + "' to recover. Has the story changed since this save data was created?");
                            } else {
                                storyContext.Warning ("When loading state, exact internal story location couldn't be found: '" + currentContainerPathStr + "' and it may not be recoverable. Has the story changed since this save data was created?");
                            }
                        }
					}

                    bool inExpressionEvaluation = (bool)jElementObj ["exp"];

					var el = new Element (pushPopType, pointer, inExpressionEvaluation);

                    object temps;
                    if ( jElementObj.TryGetValue("temp", out temps) ) {
                        el.temporaryVariables = Json.JObjectToDictionaryRuntimeObjs((Dictionary<string, object>)temps);
                    } else {
                        el.temporaryVariables.Clear();
                    }					

					callstack.Add (el);
				}

				object prevContentObjPath;
				if( jThreadObj.TryGetValue("previousContentObject", out prevContentObjPath) ) {
					var prevPath = new Path((string)prevContentObjPath);
                    previousPointer = storyContext.PointerAtPath(prevPath);
                }
			}

            public Thread Copy() {
                var copy = new Thread ();
                copy.threadIndex = threadIndex;
                foreach(var e in callstack) {
                    copy.callstack.Add(e.Copy());
                }
                copy.previousPointer = previousPointer;
                return copy;
            }

            public void WriteJson(SimpleJson.Writer writer)
            {
                writer.WriteObjectStart();

                // callstack
                writer.WritePropertyStart("callstack");
                writer.WriteArrayStart();
                foreach (CallStack.Element el in callstack)
                {
                    writer.WriteObjectStart();
                    if(!el.currentPointer.isNull) {
                        writer.WriteProperty("cPath", el.currentPointer.container.path.componentsString);
                        writer.WriteProperty("idx", el.currentPointer.index);
                    }

                    writer.WriteProperty("exp", el.inExpressionEvaluation);
                    writer.WriteProperty("type", (int)el.type);

                    if(el.temporaryVariables.Count > 0) {
                        writer.WritePropertyStart("temp");
                        Json.WriteDictionaryRuntimeObjs(writer, el.temporaryVariables);
                        writer.WritePropertyEnd();
                    }

                    writer.WriteObjectEnd();
                }
                writer.WriteArrayEnd();
                writer.WritePropertyEnd();

                // threadIndex
                writer.WriteProperty("threadIndex", threadIndex);

                if (!previousPointer.isNull)
                {
                    writer.WriteProperty("previousContentObject", previousPointer.Resolve().path.ToString());
                }

                writer.WriteObjectEnd();
            }
        }

        public List<Element> elements {
            get {
                return callStack;
            }
        }

		public int depth {
			get {
				return elements.Count;
			}
		}

        public Element currentElement { 
            get {
                var thread = _threads [_threads.Count - 1];
                var cs = thread.callstack;
                return cs [cs.Count - 1];
            } 
        }

        public int currentElementIndex {
            get {
                return callStack.Count - 1;
            }
        }

        public Thread currentThread
        {
            get {
                return _threads [_threads.Count - 1];
            }
            set {
                SboxDebug.Assert (_threads.Count == 1, "Shouldn't be directly setting the current thread when we have a stack of them");
                _threads.Clear ();
                _threads.Add (value);
            }
        }

        public bool canPop {
            get {
                return callStack.Count > 1;
            }
        }

        public CallStack (Story storyContext)
        {
            _startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
            Reset();
        }


        public CallStack(CallStack toCopy)
        {
            _threads = new List<Thread> ();
            foreach (var otherThread in toCopy._threads) {
                _threads.Add (otherThread.Copy ());
            }
            _threadCounter = toCopy._threadCounter;
            _startOfRoot = toCopy._startOfRoot;
        }

        public void Reset() 
        {
            _threads = new List<Thread>();
            _threads.Add(new Thread());

            _threads[0].callstack.Add(new Element(PushPopType.Tunnel, _startOfRoot));
        }


        // Unfortunately it's not possible to implement jsonToken since
        // the setter needs to take a Story as a context in order to
        // look up objects from paths for currentContainer within elements.
        public void SetJsonToken(Dictionary<string, object> jObject, Story storyContext)
        {
            _threads.Clear ();

            var jThreads = (List<object>) jObject ["threads"];

            foreach (object jThreadTok in jThreads) {
                var jThreadObj = (Dictionary<string, object>)jThreadTok;
                var thread = new Thread (jThreadObj, storyContext);
                _threads.Add (thread);
            }

            _threadCounter = (int)jObject ["threadCounter"];
            _startOfRoot = Pointer.StartOf(storyContext.rootContentContainer);
        }

        public void WriteJson(SimpleJson.Writer w)
        {
            w.WriteObject(writer =>
            {
                writer.WritePropertyStart("threads");
                {
                    writer.WriteArrayStart();

                    foreach (CallStack.Thread thread in _threads)
                    {
                        thread.WriteJson(writer);
                    }

                    writer.WriteArrayEnd();
                }
                writer.WritePropertyEnd();

                writer.WritePropertyStart("threadCounter");
                {
                    writer.Write(_threadCounter);
                }
                writer.WritePropertyEnd();
            });
        
        }

        public void PushThread()
        {
            var newThread = currentThread.Copy ();
            _threadCounter++;
            newThread.threadIndex = _threadCounter;
            _threads.Add (newThread);
        }

        public Thread ForkThread()
        {
            var forkedThread = currentThread.Copy();
            _threadCounter++;
            forkedThread.threadIndex = _threadCounter;
            return forkedThread;
        }

        public void PopThread()
        {
            if (canPopThread) {
                _threads.Remove (currentThread);
            } else {
				throw new System.Exception("Can't pop thread");
            }
        }

        public bool canPopThread
        {
            get {
				return _threads.Count > 1 && !elementIsEvaluateFromGame;
            }
        }

		public bool elementIsEvaluateFromGame
		{
			get {
				return currentElement.type == PushPopType.FunctionEvaluationFromGame;
			}
		}

        public void Push(PushPopType type, int externalEvaluationStackHeight = 0, int outputStreamLengthWithPushed = 0)
        {
            // When pushing to callstack, maintain the current content path, but jump out of expressions by default
            var element = new Element (
                type, 
                currentElement.currentPointer,
                inExpressionEvaluation: false
            );

            element.evaluationStackHeightWhenPushed = externalEvaluationStackHeight;
            element.functionStartInOuputStream = outputStreamLengthWithPushed;

            callStack.Add (element);
        }

        public bool CanPop(PushPopType? type = null) {

            if (!canPop)
                return false;
            
            if (type == null)
                return true;
            
            return currentElement.type == type;
        }
            
        public void Pop(PushPopType? type = null)
        {
            if (CanPop (type)) {
                callStack.RemoveAt (callStack.Count - 1);
                return;
            } else {
				throw new System.Exception("Mismatched push/pop in Callstack");
            }
        }

        // Get variable value, dereferencing a variable pointer if necessary
        public Runtime.Object GetTemporaryVariableWithName(string name, int contextIndex = -1)
        {
            // contextIndex 0 means global, so index is actually 1-based
            if (contextIndex == -1)
                contextIndex = currentElementIndex+1;
            
            Runtime.Object varValue = null;

            var contextElement = callStack [contextIndex-1];

            if (contextElement.temporaryVariables.TryGetValue (name, out varValue)) {
                return varValue;
            } else {
                return null;
            }
        }
            
        public void SetTemporaryVariable(string name, Runtime.Object value, bool declareNew, int contextIndex = -1)
        {
            if (contextIndex == -1)
                contextIndex = currentElementIndex+1;

            var contextElement = callStack [contextIndex-1];
            
            if (!declareNew && !contextElement.temporaryVariables.ContainsKey(name)) {
                throw new System.Exception ("Could not find temporary variable to set: " + name);
            }

            Runtime.Object oldValue;
            if( contextElement.temporaryVariables.TryGetValue(name, out oldValue) )
                ListValue.RetainListOriginsForAssignment (oldValue, value);

            contextElement.temporaryVariables [name] = value;
        }

        // Find the most appropriate context for this variable.
        // Are we referencing a temporary or global variable?
        // Note that the compiler will have warned us about possible conflicts,
        // so anything that happens here should be safe!
        public int ContextForVariableNamed(string name)
        {
            // Current temporary context?
            // (Shouldn't attempt to access contexts higher in the callstack.)
            if (currentElement.temporaryVariables.ContainsKey (name)) {
                return currentElementIndex+1;
            } 

            // Global
            else {
                return 0;
            }
        }
            
        public Thread ThreadWithIndex(int index)
        {
            return _threads.Find (t => t.threadIndex == index);
        }

        private List<Element> callStack
        {
            get {
                return currentThread.callstack;
            }
        }

		public string callStackTrace {
			get {
				var sb = new System.Text.StringBuilder();

				for(int t=0; t<_threads.Count; t++) {

					var thread = _threads[t];
					var isCurrent = (t == _threads.Count-1);
					sb.AppendFormat("=== THREAD {0}/{1} {2}===\n", (t+1), _threads.Count, (isCurrent ? "(current) ":""));

					for(int i=0; i<thread.callstack.Count; i++) {

						if( thread.callstack[i].type == PushPopType.Function )
							sb.Append("  [FUNCTION] ");
						else
							sb.Append("  [TUNNEL] ");

						var pointer = thread.callstack[i].currentPointer;
						if( !pointer.isNull ) {
							sb.Append("<SOMEWHERE IN ");
							sb.Append(pointer.container.path.ToString());
							sb.AppendLine(">");
						}
					}
				}


				return sb.ToString();
			}
		}

        List<Thread> _threads;
        int _threadCounter;
        Pointer _startOfRoot;
    }
}

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace Ink.Runtime
{
	/// <summary>
	/// Simple ink profiler that logs every instruction in the story and counts frequency and timing.
	/// To use:
	///  
	///   var profiler = story.StartProfiling(), 
	/// 
	///   (play your story for a bit)
	/// 
	///   var reportStr = profiler.Report();
	/// 
	///   story.EndProfiling();
	/// 
	/// </summary>
	public class Profiler
	{
        /// <summary>
        /// The root node in the hierarchical tree of recorded ink timings.
        /// </summary>
		public ProfileNode rootNode {
			get {
				return _rootNode;
			}
		}

		public Profiler() {
			_rootNode = new ProfileNode();
		}

        /// <summary>
        /// Generate a printable report based on the data recording during profiling.
        /// </summary>
		public string Report() {
			var sb = new StringBuilder();
			sb.AppendFormat("{0} CONTINUES / LINES:\n", _numContinues);
			sb.AppendFormat("TOTAL TIME: {0}\n", FormatMillisecs(_continueTotal));
			sb.AppendFormat("SNAPSHOTTING: {0}\n", FormatMillisecs(_snapTotal));
			sb.AppendFormat("OTHER: {0}\n", FormatMillisecs(_continueTotal - (_stepTotal + _snapTotal)));
			sb.Append(_rootNode.ToString());
			return sb.ToString();
		}

		public void PreContinue() {
			_continueWatch.Reset();
			_continueWatch.Start();
		}

		public void PostContinue() {
			_continueWatch.Stop();
			_continueTotal += Millisecs(_continueWatch);
			_numContinues++;
		}

		public void PreStep() {
			_currStepStack = null;
			_stepWatch.Reset();
			_stepWatch.Start();
		}

		public void Step(CallStack callstack) 
		{
			_stepWatch.Stop();

			var stack = new string[callstack.elements.Count];
			for(int i=0; i<stack.Length; i++) {
                string stackElementName = "";
                if(!callstack.elements[i].currentPointer.isNull) {
                    var objPath = callstack.elements[i].currentPointer.path;

                    for(int c=0; c<objPath.length; c++) {
                        var comp = objPath.GetComponent(c);
                        if( !comp.isIndex ) {
                            stackElementName = comp.name;
                            break;
                        }
                    }

                }
                stack[i] = stackElementName;
			}
				
			_currStepStack = stack;

			var currObj = callstack.currentElement.currentPointer.Resolve();

			string stepType = null;
			var controlCommandStep = currObj as ControlCommand;
			if( controlCommandStep )
				stepType = controlCommandStep.commandType.ToString() + " CC";
			else
				stepType = currObj.GetType().Name;

			_currStepDetails = new StepDetails {
				type = stepType,
				obj = currObj
			};

			_stepWatch.Start();
		}

		public void PostStep() {
			_stepWatch.Stop();

			var duration = Millisecs(_stepWatch);
			_stepTotal += duration;

			_rootNode.AddSample(_currStepStack, duration);

			_currStepDetails.time = duration;
			_stepDetails.Add(_currStepDetails);
		}

        /// <summary>
        /// Generate a printable report specifying the average and maximum times spent
        /// stepping over different internal ink instruction types.
        /// This report type is primarily used to profile the ink engine itself rather
        /// than your own specific ink.
        /// </summary>
		public string StepLengthReport()
		{
			var sb = new StringBuilder();

			sb.AppendLine("TOTAL: "+_rootNode.totalMillisecs+"ms");

			var averageStepTimes = _stepDetails
				.GroupBy(s => s.type)
				.Select(typeToDetails => new KeyValuePair<string, double>(typeToDetails.Key, typeToDetails.Average(d => d.time)))
				.OrderByDescending(stepTypeToAverage => stepTypeToAverage.Value)
				.Select(stepTypeToAverage => {
					var typeName = stepTypeToAverage.Key;
					var time = stepTypeToAverage.Value;
					return typeName + ": " + time + "ms";
				})
				.ToArray();

			sb.AppendLine("AVERAGE STEP TIMES: "+string.Join(", ", averageStepTimes));

			var accumStepTimes = _stepDetails
				.GroupBy(s => s.type)
				.Select(typeToDetails => new KeyValuePair<string, double>(typeToDetails.Key + " (x"+typeToDetails.Count()+")", typeToDetails.Sum(d => d.time)))
				.OrderByDescending(stepTypeToAccum => stepTypeToAccum.Value)
				.Select(stepTypeToAccum => {
					var typeName = stepTypeToAccum.Key;
					var time = stepTypeToAccum.Value;
					return typeName + ": " + time;
				})
				.ToArray();

			sb.AppendLine("ACCUMULATED STEP TIMES: "+string.Join(", ", accumStepTimes));

			return sb.ToString();
		}

        /// <summary>
        /// Create a large log of all the internal instructions that were evaluated while profiling was active.
        /// Log is in a tab-separated format, for easy loading into a spreadsheet application.
        /// </summary>
		public string Megalog()
		{
			var sb = new StringBuilder();

			sb.AppendLine("Step type\tDescription\tPath\tTime");

			foreach(var step in _stepDetails) {
				sb.Append(step.type);
				sb.Append("\t");
				sb.Append(step.obj.ToString());
				sb.Append("\t");
				sb.Append(step.obj.path);
				sb.Append("\t");
				sb.AppendLine(step.time.ToString("F8"));
			}

			return sb.ToString();
		}

		public void PreSnapshot() {
			_snapWatch.Reset();
			_snapWatch.Start();
		}

		public void PostSnapshot() {
			_snapWatch.Stop();
			_snapTotal += Millisecs(_snapWatch);
		}

		double Millisecs(Stopwatch watch)
		{
			var ticks = watch.ElapsedTicks;
			return ticks * _millisecsPerTick;
		}

		public static string FormatMillisecs(double num) {
			if( num > 5000 ) {
				return string.Format("{0:N1} secs", num / 1000.0);
			} if( num > 1000 ) {
				return string.Format("{0:N2} secs", num / 1000.0);
			} else if( num > 100 ) {
				return string.Format("{0:N0} ms", num);
			} else if( num > 1 ) {
				return string.Format("{0:N1} ms", num);
			} else if( num > 0.01 ) {
				return string.Format("{0:N3} ms", num);
			} else {
				return string.Format("{0:N} ms", num);
			}
		}

		Stopwatch _continueWatch = new Stopwatch();
		Stopwatch _stepWatch = new Stopwatch();
		Stopwatch _snapWatch = new Stopwatch();

		double _continueTotal;
		double _snapTotal;
		double _stepTotal;

		string[] _currStepStack;
		StepDetails _currStepDetails;
		ProfileNode _rootNode;
		int _numContinues;

		struct StepDetails {
			public string type;
			public Runtime.Object obj;
			public double time;
		}
		List<StepDetails> _stepDetails = new List<StepDetails>();

		static double _millisecsPerTick = 1000.0 / Stopwatch.Frequency;
	}


    /// <summary>
    /// Node used in the hierarchical tree of timings used by the Profiler.
    /// Each node corresponds to a single line viewable in a UI-based representation.
    /// </summary>
	public class ProfileNode {

        /// <summary>
        /// The key for the node corresponds to the printable name of the callstack element.
        /// </summary>		
        public readonly string key;


        #pragma warning disable 0649
        /// <summary>
        /// Horribly hacky field only used by ink unity integration,
        /// but saves constructing an entire data structure that mirrors
        /// the one in here purely to store the state of whether each
        /// node in the UI has been opened or not  /// </summary>
        public bool openInUI;
        #pragma warning restore 0649

        /// <summary>
        /// Whether this node contains any sub-nodes - i.e. does it call anything else
        /// that has been recorded?
        /// </summary>
        /// <value><c>true</c> if has children; otherwise, <c>false</c>.</value>
		public bool hasChildren {
			get {
				return _nodes != null && _nodes.Count > 0;
			}
		}

        /// <summary>
        /// Total number of milliseconds this node has been active for.
        /// </summary>
		public int totalMillisecs {
			get {
				return (int)_totalMillisecs;
			}
		}

		public ProfileNode() {

		}

		public ProfileNode(string key) {
			this.key = key;
		}

		public void AddSample(string[] stack, double duration) {
			AddSample(stack, -1, duration);
		}

		void AddSample(string[] stack, int stackIdx, double duration) {

			_totalSampleCount++;
			_totalMillisecs += duration;

			if( stackIdx == stack.Length-1 ) {
				_selfSampleCount++;
				_selfMillisecs += duration;
			}

			if( stackIdx+1 < stack.Length )
				AddSampleToNode(stack, stackIdx+1, duration);
		}

		void AddSampleToNode(string[] stack, int stackIdx, double duration)
		{
			var nodeKey = stack[stackIdx];
			if( _nodes == null ) _nodes = new Dictionary<string, ProfileNode>();

			ProfileNode node;
			if( !_nodes.TryGetValue(nodeKey, out node) ) {
				node = new ProfileNode(nodeKey);
				_nodes[nodeKey] = node;
			}

			node.AddSample(stack, stackIdx, duration);
		}

        /// <summary>
        /// Returns a sorted enumerable of the nodes in descending order of
        /// how long they took to run.
        /// </summary>
		public IEnumerable<KeyValuePair<string, ProfileNode>> descendingOrderedNodes {
			get {
				if( _nodes == null ) return null;
				return _nodes.OrderByDescending(keyNode => keyNode.Value._totalMillisecs);
			}
		}

		void PrintHierarchy(StringBuilder sb, int indent)
		{
			Pad(sb, indent);

			sb.Append(key);
			sb.Append(": ");
			sb.AppendLine(ownReport);

			if( _nodes == null ) return;

			foreach(var keyNode in descendingOrderedNodes) {
				keyNode.Value.PrintHierarchy(sb, indent+1);
			}
		}

        /// <summary>
        /// Generates a string giving timing information for this single node, including
        /// total milliseconds spent on the piece of ink, the time spent within itself
        /// (v.s. spent in children), as well as the number of samples (instruction steps)
        /// recorded for both too.
        /// </summary>
        /// <value>The own report.</value>
		public string ownReport {
			get {
				var sb = new StringBuilder();
				sb.Append("total ");
				sb.Append(Profiler.FormatMillisecs(_totalMillisecs));
				sb.Append(", self ");
				sb.Append(Profiler.FormatMillisecs(_selfMillisecs));
				sb.Append(" (");
				sb.Append(_selfSampleCount);
				sb.Append(" self samples, ");
				sb.Append(_totalSampleCount);
				sb.Append(" total)");
				return sb.ToString();
			}
			
		}

		void Pad(StringBuilder sb, int spaces)
		{
			for(int i=0; i<spaces; i++) sb.Append("   ");
		}

        /// <summary>
        /// String is a report of the sub-tree from this node, but without any of the header information
        /// that's prepended by the Profiler in its Report() method.
        /// </summary>
		public override string ToString ()
		{
			var sb = new StringBuilder();
			PrintHierarchy(sb, 0);
			return sb.ToString();
		}

		Dictionary<string, ProfileNode> _nodes;
		double _selfMillisecs;
		double _totalMillisecs;
		int _selfSampleCount;
		int _totalSampleCount;
	}
}

using Sandbox;

[TestClass]
public partial class LibraryTests
{
	[TestMethod]
	public void SceneTest()
	{
		var scene = new Scene();
		using ( scene.Push() )
		{
			var go = new GameObject();

			Assert.AreEqual( 1, scene.Directory.GameObjectCount );
		}
	}

}
using System.Text;

namespace Ink.Runtime
{
	public class Divert : Runtime.Object
	{
        public Path targetPath { 
            get { 
                // Resolve any relative paths to global ones as we come across them
                if (_targetPath != null && _targetPath.isRelative) {
                    var targetObj = targetPointer.Resolve();
                    if (targetObj) {
                        _targetPath = targetObj.path;
                    }
                }
                return _targetPath;
            }
            set {
                _targetPath = value;
                _targetPointer = Pointer.Null;
            } 
        }
        Path _targetPath;

        public Pointer targetPointer {
            get {
                if (_targetPointer.isNull) {
                    var targetObj = ResolvePath (_targetPath).obj;

                    if (_targetPath.lastComponent.isIndex) {
                        _targetPointer.container = targetObj.parent as Container;
                        _targetPointer.index = _targetPath.lastComponent.index;
                    } else {
                        _targetPointer = Pointer.StartOf (targetObj as Container);
                    }
                }
                return _targetPointer;
            }
        }
        Pointer _targetPointer;
        

        public string targetPathString {
            get {
                if (targetPath == null)
                    return null;

                return CompactPathString (targetPath);
            }
            set {
                if (value == null) {
                    targetPath = null;
                } else {
                    targetPath = new Path (value);
                }
            }
        }
            
        public string variableDivertName { get; set; }
        public bool hasVariableTarget { get { return variableDivertName != null; } }

        public bool pushesToStack { get; set; }
        public PushPopType stackPushType;

        public bool isExternal { get; set; }
        public int externalArgs { get; set; }

        public bool isConditional { get; set; }

		public Divert ()
		{
            pushesToStack = false;
		}

        public Divert(PushPopType stackPushType)
        {
            pushesToStack = true;
            this.stackPushType = stackPushType;
        }

        public override bool Equals (object obj)
        {
            var otherDivert = obj as Divert;
            if (otherDivert) {
                if (this.hasVariableTarget == otherDivert.hasVariableTarget) {
                    if (this.hasVariableTarget) {
                        return this.variableDivertName == otherDivert.variableDivertName;
                    } else {
                        return this.targetPath.Equals(otherDivert.targetPath);
                    }
                }
            }
            return false;
        }

        public override int GetHashCode ()
        {
            if (hasVariableTarget) {
                const int variableTargetSalt = 12345;
                return variableDivertName.GetHashCode() + variableTargetSalt;
            } else {
                const int pathTargetSalt = 54321;
                return targetPath.GetHashCode() + pathTargetSalt;
            }
        }

        public override string ToString ()
        {
            if (hasVariableTarget) {
                return "Divert(variable: " + variableDivertName + ")";
            }
            else if (targetPath == null) {
                return "Divert(null)";
            } else {

                var sb = new StringBuilder ();

                string targetStr = targetPath.ToString ();
                int? targetLineNum = DebugLineNumberOfPath (targetPath);
                if (targetLineNum != null) {
                    targetStr = "line " + targetLineNum;
                }

                sb.Append ("Divert");

                if (isConditional)
                    sb.Append ("?");

                if (pushesToStack) {
                    if (stackPushType == PushPopType.Function) {
                        sb.Append (" function");
                    } else {
                        sb.Append (" tunnel");
                    }
                }

                sb.Append (" -> ");
                sb.Append (targetPathString);

                sb.Append (" (");
                sb.Append (targetStr);
                sb.Append (")");

                return sb.ToString ();
            }
        }
	}
}

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace Ink.Runtime
{
	/// <summary>
	/// Simple ink profiler that logs every instruction in the story and counts frequency and timing.
	/// To use:
	///  
	///   var profiler = story.StartProfiling(), 
	/// 
	///   (play your story for a bit)
	/// 
	///   var reportStr = profiler.Report();
	/// 
	///   story.EndProfiling();
	/// 
	/// </summary>
	public class Profiler
	{
        /// <summary>
        /// The root node in the hierarchical tree of recorded ink timings.
        /// </summary>
		public ProfileNode rootNode {
			get {
				return _rootNode;
			}
		}

		public Profiler() {
			_rootNode = new ProfileNode();
		}

        /// <summary>
        /// Generate a printable report based on the data recording during profiling.
        /// </summary>
		public string Report() {
			var sb = new StringBuilder();
			sb.AppendFormat("{0} CONTINUES / LINES:\n", _numContinues);
			sb.AppendFormat("TOTAL TIME: {0}\n", FormatMillisecs(_continueTotal));
			sb.AppendFormat("SNAPSHOTTING: {0}\n", FormatMillisecs(_snapTotal));
			sb.AppendFormat("OTHER: {0}\n", FormatMillisecs(_continueTotal - (_stepTotal + _snapTotal)));
			sb.Append(_rootNode.ToString());
			return sb.ToString();
		}

		public void PreContinue() {
			_continueWatch.Reset();
			_continueWatch.Start();
		}

		public void PostContinue() {
			_continueWatch.Stop();
			_continueTotal += Millisecs(_continueWatch);
			_numContinues++;
		}

		public void PreStep() {
			_currStepStack = null;
			_stepWatch.Reset();
			_stepWatch.Start();
		}

		public void Step(CallStack callstack) 
		{
			_stepWatch.Stop();

			var stack = new string[callstack.elements.Count];
			for(int i=0; i<stack.Length; i++) {
                string stackElementName = "";
                if(!callstack.elements[i].currentPointer.isNull) {
                    var objPath = callstack.elements[i].currentPointer.path;

                    for(int c=0; c<objPath.length; c++) {
                        var comp = objPath.GetComponent(c);
                        if( !comp.isIndex ) {
                            stackElementName = comp.name;
                            break;
                        }
                    }

                }
                stack[i] = stackElementName;
			}
				
			_currStepStack = stack;

			var currObj = callstack.currentElement.currentPointer.Resolve();

			string stepType = null;
			var controlCommandStep = currObj as ControlCommand;
			if( controlCommandStep )
				stepType = controlCommandStep.commandType.ToString() + " CC";
			else
				stepType = currObj.GetType().Name;

			_currStepDetails = new StepDetails {
				type = stepType,
				obj = currObj
			};

			_stepWatch.Start();
		}

		public void PostStep() {
			_stepWatch.Stop();

			var duration = Millisecs(_stepWatch);
			_stepTotal += duration;

			_rootNode.AddSample(_currStepStack, duration);

			_currStepDetails.time = duration;
			_stepDetails.Add(_currStepDetails);
		}

        /// <summary>
        /// Generate a printable report specifying the average and maximum times spent
        /// stepping over different internal ink instruction types.
        /// This report type is primarily used to profile the ink engine itself rather
        /// than your own specific ink.
        /// </summary>
		public string StepLengthReport()
		{
			var sb = new StringBuilder();

			sb.AppendLine("TOTAL: "+_rootNode.totalMillisecs+"ms");

			var averageStepTimes = _stepDetails
				.GroupBy(s => s.type)
				.Select(typeToDetails => new KeyValuePair<string, double>(typeToDetails.Key, typeToDetails.Average(d => d.time)))
				.OrderByDescending(stepTypeToAverage => stepTypeToAverage.Value)
				.Select(stepTypeToAverage => {
					var typeName = stepTypeToAverage.Key;
					var time = stepTypeToAverage.Value;
					return typeName + ": " + time + "ms";
				})
				.ToArray();

			sb.AppendLine("AVERAGE STEP TIMES: "+string.Join(", ", averageStepTimes));

			var accumStepTimes = _stepDetails
				.GroupBy(s => s.type)
				.Select(typeToDetails => new KeyValuePair<string, double>(typeToDetails.Key + " (x"+typeToDetails.Count()+")", typeToDetails.Sum(d => d.time)))
				.OrderByDescending(stepTypeToAccum => stepTypeToAccum.Value)
				.Select(stepTypeToAccum => {
					var typeName = stepTypeToAccum.Key;
					var time = stepTypeToAccum.Value;
					return typeName + ": " + time;
				})
				.ToArray();

			sb.AppendLine("ACCUMULATED STEP TIMES: "+string.Join(", ", accumStepTimes));

			return sb.ToString();
		}

        /// <summary>
        /// Create a large log of all the internal instructions that were evaluated while profiling was active.
        /// Log is in a tab-separated format, for easy loading into a spreadsheet application.
        /// </summary>
		public string Megalog()
		{
			var sb = new StringBuilder();

			sb.AppendLine("Step type\tDescription\tPath\tTime");

			foreach(var step in _stepDetails) {
				sb.Append(step.type);
				sb.Append("\t");
				sb.Append(step.obj.ToString());
				sb.Append("\t");
				sb.Append(step.obj.path);
				sb.Append("\t");
				sb.AppendLine(step.time.ToString("F8"));
			}

			return sb.ToString();
		}

		public void PreSnapshot() {
			_snapWatch.Reset();
			_snapWatch.Start();
		}

		public void PostSnapshot() {
			_snapWatch.Stop();
			_snapTotal += Millisecs(_snapWatch);
		}

		double Millisecs(Stopwatch watch)
		{
			var ticks = watch.ElapsedTicks;
			return ticks * _millisecsPerTick;
		}

		public static string FormatMillisecs(double num) {
			if( num > 5000 ) {
				return string.Format("{0:N1} secs", num / 1000.0);
			} if( num > 1000 ) {
				return string.Format("{0:N2} secs", num / 1000.0);
			} else if( num > 100 ) {
				return string.Format("{0:N0} ms", num);
			} else if( num > 1 ) {
				return string.Format("{0:N1} ms", num);
			} else if( num > 0.01 ) {
				return string.Format("{0:N3} ms", num);
			} else {
				return string.Format("{0:N} ms", num);
			}
		}

		Stopwatch _continueWatch = new Stopwatch();
		Stopwatch _stepWatch = new Stopwatch();
		Stopwatch _snapWatch = new Stopwatch();

		double _continueTotal;
		double _snapTotal;
		double _stepTotal;

		string[] _currStepStack;
		StepDetails _currStepDetails;
		ProfileNode _rootNode;
		int _numContinues;

		struct StepDetails {
			public string type;
			public Runtime.Object obj;
			public double time;
		}
		List<StepDetails> _stepDetails = new List<StepDetails>();

		static double _millisecsPerTick = 1000.0 / Stopwatch.Frequency;
	}


    /// <summary>
    /// Node used in the hierarchical tree of timings used by the Profiler.
    /// Each node corresponds to a single line viewable in a UI-based representation.
    /// </summary>
	public class ProfileNode {

        /// <summary>
        /// The key for the node corresponds to the printable name of the callstack element.
        /// </summary>		
        public readonly string key;


        #pragma warning disable 0649
        /// <summary>
        /// Horribly hacky field only used by ink unity integration,
        /// but saves constructing an entire data structure that mirrors
        /// the one in here purely to store the state of whether each
        /// node in the UI has been opened or not  /// </summary>
        public bool openInUI;
        #pragma warning restore 0649

        /// <summary>
        /// Whether this node contains any sub-nodes - i.e. does it call anything else
        /// that has been recorded?
        /// </summary>
        /// <value><c>true</c> if has children; otherwise, <c>false</c>.</value>
		public bool hasChildren {
			get {
				return _nodes != null && _nodes.Count > 0;
			}
		}

        /// <summary>
        /// Total number of milliseconds this node has been active for.
        /// </summary>
		public int totalMillisecs {
			get {
				return (int)_totalMillisecs;
			}
		}

		public ProfileNode() {

		}

		public ProfileNode(string key) {
			this.key = key;
		}

		public void AddSample(string[] stack, double duration) {
			AddSample(stack, -1, duration);
		}

		void AddSample(string[] stack, int stackIdx, double duration) {

			_totalSampleCount++;
			_totalMillisecs += duration;

			if( stackIdx == stack.Length-1 ) {
				_selfSampleCount++;
				_selfMillisecs += duration;
			}

			if( stackIdx+1 < stack.Length )
				AddSampleToNode(stack, stackIdx+1, duration);
		}

		void AddSampleToNode(string[] stack, int stackIdx, double duration)
		{
			var nodeKey = stack[stackIdx];
			if( _nodes == null ) _nodes = new Dictionary<string, ProfileNode>();

			ProfileNode node;
			if( !_nodes.TryGetValue(nodeKey, out node) ) {
				node = new ProfileNode(nodeKey);
				_nodes[nodeKey] = node;
			}

			node.AddSample(stack, stackIdx, duration);
		}

        /// <summary>
        /// Returns a sorted enumerable of the nodes in descending order of
        /// how long they took to run.
        /// </summary>
		public IEnumerable<KeyValuePair<string, ProfileNode>> descendingOrderedNodes {
			get {
				if( _nodes == null ) return null;
				return _nodes.OrderByDescending(keyNode => keyNode.Value._totalMillisecs);
			}
		}

		void PrintHierarchy(StringBuilder sb, int indent)
		{
			Pad(sb, indent);

			sb.Append(key);
			sb.Append(": ");
			sb.AppendLine(ownReport);

			if( _nodes == null ) return;

			foreach(var keyNode in descendingOrderedNodes) {
				keyNode.Value.PrintHierarchy(sb, indent+1);
			}
		}

        /// <summary>
        /// Generates a string giving timing information for this single node, including
        /// total milliseconds spent on the piece of ink, the time spent within itself
        /// (v.s. spent in children), as well as the number of samples (instruction steps)
        /// recorded for both too.
        /// </summary>
        /// <value>The own report.</value>
		public string ownReport {
			get {
				var sb = new StringBuilder();
				sb.Append("total ");
				sb.Append(Profiler.FormatMillisecs(_totalMillisecs));
				sb.Append(", self ");
				sb.Append(Profiler.FormatMillisecs(_selfMillisecs));
				sb.Append(" (");
				sb.Append(_selfSampleCount);
				sb.Append(" self samples, ");
				sb.Append(_totalSampleCount);
				sb.Append(" total)");
				return sb.ToString();
			}
			
		}

		void Pad(StringBuilder sb, int spaces)
		{
			for(int i=0; i<spaces; i++) sb.Append("   ");
		}

        /// <summary>
        /// String is a report of the sub-tree from this node, but without any of the header information
        /// that's prepended by the Profiler in its Report() method.
        /// </summary>
		public override string ToString ()
		{
			var sb = new StringBuilder();
			PrintHierarchy(sb, 0);
			return sb.ToString();
		}

		Dictionary<string, ProfileNode> _nodes;
		double _selfMillisecs;
		double _totalMillisecs;
		int _selfSampleCount;
		int _totalSampleCount;
	}
}