149 results

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 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!" );
	}
}
using System.Collections.Generic;
using System.Linq;

namespace Ink
{
    /// <summary>
    /// A class representing a character range. Allows for lazy-loading a corresponding <see cref="CharacterSet">character set</see>.
    /// </summary>
    public sealed class CharacterRange
    {
        public static CharacterRange Define(char start, char end, IEnumerable<char> excludes = null)
        {
            return new CharacterRange (start, end, excludes);
        }

        /// <summary>
        /// Returns a <see cref="CharacterSet">character set</see> instance corresponding to the character range
        /// represented by the current instance.
        /// </summary>
        /// <remarks>
        /// The internal character set is created once and cached in memory.
        /// </remarks>
        /// <returns>The char set.</returns>
        public CharacterSet ToCharacterSet ()
        {
            if (_correspondingCharSet.Count == 0) 
            {
                for (char c = _start; c <= _end; c++)
                {
                    if (!_excludes.Contains (c)) 
                    {
                        _correspondingCharSet.Add (c);
                    }
                }
            }
            return _correspondingCharSet;
        }

        public char start { get { return _start; } }
        public char end { get { return _end; } }

        CharacterRange (char start, char end, IEnumerable<char> excludes)
        {
        	_start = start;
        	_end = end;
            _excludes = excludes == null ? new HashSet<char>() : new HashSet<char> (excludes);
        }

        char _start;
        char _end;
        ICollection<char> _excludes;
        CharacterSet _correspondingCharSet = new CharacterSet();
    }    
}
using System;
using Ink.Parsed;
using System.Collections.Generic;

namespace Ink
{
	public partial class InkParser
	{
		protected class InfixOperator
		{
			public string type;
			public int precedence;
            public bool requireWhitespace;

            public InfixOperator(string type, int precedence, bool requireWhitespace) {
				this.type = type;
				this.precedence = precedence;
                this.requireWhitespace = requireWhitespace;
			}

			public override string ToString ()
			{
				return type;
			}
		}

        protected Parsed.Object TempDeclarationOrAssignment()
        {
            Whitespace ();

            bool isNewDeclaration = ParseTempKeyword();

            Whitespace ();

            Identifier varIdentifier = null;
            if (isNewDeclaration) {
                varIdentifier = (Identifier)Expect (IdentifierWithMetadata, "variable name");
            } else {
                varIdentifier = Parse(IdentifierWithMetadata);
            }

            if (varIdentifier == null) {
                return null;
            }

            Whitespace();

            // += -=
            bool isIncrement = ParseString ("+") != null;
            bool isDecrement = ParseString ("-") != null;
            if (isIncrement && isDecrement) Error ("Unexpected sequence '+-'");

            if (ParseString ("=") == null) {
                // Definitely in an assignment expression?
                if (isNewDeclaration) Error ("Expected '='");
                return null;
            }

            Expression assignedExpression = (Expression)Expect (Expression, "value expression to be assigned");

            if (isIncrement || isDecrement) {
                var result = new IncDecExpression (varIdentifier, assignedExpression, isIncrement);
                return result;
            } else {
                var result = new VariableAssignment (varIdentifier, assignedExpression);
                result.isNewTemporaryDeclaration = isNewDeclaration;
                return result;
            }
        }

        protected void DisallowIncrement (Parsed.Object expr)
        {
        	if (expr is Parsed.IncDecExpression)
        		Error ("Can't use increment/decrement here. It can only be used on a ~ line");
        }

        protected bool ParseTempKeyword()
        {
            var ruleId = BeginRule ();

            if (Parse (Identifier) == "temp") {
                SucceedRule (ruleId);
                return true;
            } else {
                FailRule (ruleId);
                return false;
            }
        }

        protected Parsed.Return ReturnStatement()
        {
            Whitespace ();

            var returnOrDone = Parse(Identifier);
            if (returnOrDone != "return") {
                return null;
            }

            Whitespace ();

            var expr = Parse(Expression);

            var returnObj = new Return (expr);
            return returnObj;
        }

		protected Expression Expression() {
			return Expression(minimumPrecedence:0);
		}

		// Pratt Parser
		// aka "Top down operator precedence parser"
		// http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
		// Algorithm overview:
		// The two types of precedence are handled in two different ways:
		//   ((((a . b) . c) . d) . e)			#1
		//   (a . (b . (c . (d . e))))			#2
		// Where #1 is automatically handled by successive loops within the main 'while' in this function,
		// so long as continuing operators have lower (or equal) precedence (e.g. imagine some series of "*"s then "+" above.
		// ...and #2 is handled by recursion of the right hand term in the binary expression parser.
		// (see link for advice on how to extend for postfix and mixfix operators)
		protected Expression Expression(int minimumPrecedence)
		{
			Whitespace ();

			// First parse a unary expression e.g. "-a" or parethensised "(1 + 2)"
			var expr = ExpressionUnary ();
			if (expr == null) {
                return null;
			}

			Whitespace ();

			// Attempt to parse (possibly multiple) continuing infix expressions (e.g. 1 + 2 + 3)
			while(true) {
				var ruleId = BeginRule ();

				// Operator
				var infixOp = ParseInfixOperator ();
				if (infixOp != null && infixOp.precedence > minimumPrecedence) {

					// Expect right hand side of operator
					var expectationMessage = string.Format("right side of '{0}' expression", infixOp.type);
					var multiaryExpr = Expect (() => ExpressionInfixRight (left: expr, op: infixOp), expectationMessage);
                    if (multiaryExpr == null) {

                        // Fail for operator and right-hand side of multiary expression
                        FailRule (ruleId);

                        return null;
                    }

                    expr = SucceedRule(ruleId, multiaryExpr) as Parsed.Expression;

					continue;
				}

                FailRule (ruleId);
				break;
			}

            Whitespace ();

            return expr;
		}

        protected Expression ExpressionUnary()
		{
            // Divert target is a special case - it can't have any other operators
            // applied to it, and we also want to check for it first so that we don't
            // confuse "->" for subtraction.
            var divertTarget = Parse (ExpressionDivertTarget);
            if (divertTarget != null) {
                return divertTarget;
            }

            var prefixOp = (string) OneOf (String ("-"), String ("!"));

            // Don't parse like the string rules above, in case its actually
            // a variable that simply starts with "not", e.g. "notable".
            // This rule uses the Identifier rule, which will scan as much text
            // as possible before returning.
            if (prefixOp == null) {
                prefixOp = Parse(ExpressionNot);
            }

			Whitespace ();

            // - Since we allow numbers at the start of variable names, variable names are checked before literals
            // - Function calls before variable names in case we see parentheses
            var expr = OneOf (ExpressionList, ExpressionParen, ExpressionFunctionCall, ExpressionVariableName, ExpressionLiteral) as Expression;

            // Only recurse immediately if we have one of the (usually optional) unary ops
            if (expr == null && prefixOp != null) {
                expr = ExpressionUnary ();
            }

			if (expr == null)
                return null;

            if (prefixOp != null) {
                expr = UnaryExpression.WithInner(expr, prefixOp);
			}

            Whitespace ();

            var postfixOp = (string) OneOf (String ("++"), String ("--"));
            if (postfixOp != null) {
                bool isInc = postfixOp == "++";

                if (!(expr is VariableReference)) {
                    Error ("can only increment and decrement variables, but saw '" + expr + "'");

                    // Drop down and succeed without the increment after reporting error
                } else {
                    // TODO: Language Server - (Identifier combined into one vs. list of Identifiers)
                    var varRef = (VariableReference)expr;
                    expr = new IncDecExpression(varRef.identifier, isInc);
                }

            }

            return expr;
		}

        protected string ExpressionNot()
        {
            var id = Identifier ();
            if (id == "not") {
                return id;
            }

            return null;
        }

		protected Expression ExpressionLiteral()
		{
            return (Expression) OneOf (ExpressionFloat, ExpressionInt, ExpressionBool, ExpressionString);
		}

        protected Expression ExpressionDivertTarget()
        {
            Whitespace ();

            var divert = Parse(SingleDivert);
            if (divert == null)
                return null;

            if (divert.isThread)
                return null;

            Whitespace ();

            return new DivertTarget (divert);
        }

        protected Number ExpressionInt()
        {
            int? intOrNull = ParseInt ();
            if (intOrNull == null) {
                return null;
            } else {
                return new Number (intOrNull.Value);
            }
        }

        protected Number ExpressionFloat()
        {
            float? floatOrNull = ParseFloat ();
            if (floatOrNull == null) {
                return null;
            } else {
                return new Number (floatOrNull.Value);
            }
        }

        protected StringExpression ExpressionString()
        {
            var openQuote = ParseString ("\"");
            if (openQuote == null)
                return null;

            // Set custom parser state flag so that within the text parser,
            // it knows to treat the quote character (") as an end character
            parsingStringExpression = true;

            List<Parsed.Object> textAndLogic = Parse (MixedTextAndLogic);

            Expect (String ("\""), "close quote for string expression");

            parsingStringExpression = false;

            if (textAndLogic == null) {
                textAndLogic = new List<Ink.Parsed.Object> ();
                textAndLogic.Add (new Parsed.Text (""));
            }

            else if (textAndLogic.Exists (c => c is Divert))
                Error ("String expressions cannot contain diverts (->)");

            return new StringExpression (textAndLogic);
        }

        protected Number ExpressionBool()
        {
            var id = Parse(Identifier);
            if (id == "true") {
                return new Number (true);
            } else if (id == "false") {
                return new Number (false);
            }

            return null;
        }

        protected Expression ExpressionFunctionCall()
        {
            var iden = Parse(IdentifierWithMetadata);
            if (iden == null)
                return null;

            Whitespace ();

            var arguments = Parse(ExpressionFunctionCallArguments);
            if (arguments == null) {
                return null;
            }

            return new FunctionCall(iden, arguments);
        }

        protected List<Expression> ExpressionFunctionCallArguments()
        {
            if (ParseString ("(") == null)
                return null;

            // "Exclude" requires the rule to succeed, but causes actual comma string to be excluded from the list of results
            ParseRule commas = Exclude (String (","));
            var arguments = Interleave<Expression>(Expression, commas);
            if (arguments == null) {
                arguments = new List<Expression> ();
            }

            Whitespace ();

            Expect (String (")"), "closing ')' for function call");

            return arguments;
        }

        protected Expression ExpressionVariableName()
        {
            List<Identifier> path = Interleave<Identifier> (IdentifierWithMetadata, Exclude (Spaced (String ("."))));

            if (path == null || Story.IsReservedKeyword (path[0].name) )
                return null;

            return new VariableReference (path);
        }

		protected Expression ExpressionParen()
		{
			if (ParseString ("(") == null)
                return null;

            var innerExpr = Parse(Expression);
			if (innerExpr == null)
                return null;

			Whitespace ();

            Expect (String(")"), "closing parenthesis ')' for expression");

            return innerExpr;
		}

		protected Expression ExpressionInfixRight(Parsed.Expression left, InfixOperator op)
		{
			Whitespace ();

            var right = Parse(() => Expression (op.precedence));
			if (right) {

				// We assume that the character we use for the operator's type is the same
				// as that used internally by e.g. Runtime.Expression.Add, Runtime.Expression.Multiply etc
				var expr = new BinaryExpression (left, right, op.type);
                return expr;
			}

            return null;

		}

		private InfixOperator ParseInfixOperator()
		{
            foreach (var op in _binaryOperators) {

                int ruleId = BeginRule ();

                if (ParseString (op.type) != null) {

                    if (op.requireWhitespace) {
                        if (Whitespace () == null) {
                            FailRule (ruleId);
                            continue;
                        }
                    }

                    return (InfixOperator) SucceedRule(ruleId, op);
                }

                FailRule (ruleId);
            }

            return null;
		}

        protected Parsed.List ExpressionList ()
        {
            Whitespace ();

            if (ParseString ("(") == null)
                return null;

            Whitespace ();

            // When list has:
            //  - 0 elements (null list) - this is okay, it's an empty list: "()"
            //  - 1 element - it could be confused for a single non-list related
            //    identifier expression in brackets, but this is a useless thing
            //    to do, so we reserve that syntax for a list with one item.
            //  - 2 or more elements - normal!
            List<Identifier> memberNames = SeparatedList (ListMember, Spaced (String (",")));

            Whitespace ();

            // May have failed to parse the inner list - the parentheses may
            // be for a normal expression
            if (ParseString (")") == null)
                return null;

            return new List (memberNames);
        }

        protected Identifier ListMember ()
        {
            Whitespace ();

            Identifier identifier = Parse (IdentifierWithMetadata);
            if (identifier == null)
                return null;

            var dot = ParseString (".");
            if (dot != null) {
                Identifier identifier2 = Expect (IdentifierWithMetadata, "element name within the set " + identifier) as Identifier;
                identifier.name = identifier.name + "." + identifier2?.name;
            }

            Whitespace ();

            return identifier;
        }

		void RegisterExpressionOperators()
		{
            _maxBinaryOpLength = 0;
			_binaryOperators = new List<InfixOperator> ();

            // These will be tried in order, so we need "<=" before "<"
            // for correctness

            RegisterBinaryOperator ("&&", precedence:1);
            RegisterBinaryOperator ("||", precedence:1);
            RegisterBinaryOperator ("and", precedence:1, requireWhitespace: true);
            RegisterBinaryOperator ("or", precedence:1, requireWhitespace: true);

            RegisterBinaryOperator ("==", precedence:2);
            RegisterBinaryOperator (">=", precedence:2);
            RegisterBinaryOperator ("<=", precedence:2);
            RegisterBinaryOperator ("<", precedence:2);
            RegisterBinaryOperator (">", precedence:2);
            RegisterBinaryOperator ("!=", precedence:2);

            // (apples, oranges) + cabbages has (oranges, cabbages) == true
            RegisterBinaryOperator ("?", precedence: 3);
            RegisterBinaryOperator ("has", precedence: 3, requireWhitespace:true);
            RegisterBinaryOperator ("!?", precedence: 3);
            RegisterBinaryOperator ("hasnt", precedence: 3, requireWhitespace: true);
            RegisterBinaryOperator ("^", precedence: 3);

			RegisterBinaryOperator ("+", precedence:4);
			RegisterBinaryOperator ("-", precedence:5);
			RegisterBinaryOperator ("*", precedence:6);
			RegisterBinaryOperator ("/", precedence:7);

            RegisterBinaryOperator ("%", precedence:8);
            RegisterBinaryOperator ("mod", precedence:8, requireWhitespace:true);


		}

        void RegisterBinaryOperator(string op, int precedence, bool requireWhitespace = false)
		{
            _binaryOperators.Add(new InfixOperator (op, precedence, requireWhitespace));
            _maxBinaryOpLength = Math.Max (_maxBinaryOpLength, op.Length);
		}

        List<InfixOperator> _binaryOperators;
        int _maxBinaryOpLength;
	}
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using Editor;
using Sandbox;
using System.Text;

class SpectogramWidget : Widget 
{
    private short[] samples;
    private int sampleRate;
    private List<int> splitPoints = new List<int>();
    private int? dragPoint = null;
    private Label loadingLabel;
    private Label dropLabel;
    private bool isLoading = true;
    public SoundFile CurrentSound { get; private set; }

    public SpectogramWidget(SoundFile soundFile) : base(null)
    {
        MinimumSize = 100;
        MouseTracking = true;
        AcceptDrops = true;

        loadingLabel = new Label(this);
        loadingLabel.Text = "Loading audio data...";
        loadingLabel.Visible = false;
        
        dropLabel = new Label(this);
        dropLabel.Text = "Drop a sound file here";
        dropLabel.SetStyles("font-size: 18px; color: #aaa; text-align: center;");
        
        if (soundFile != null)
        {
            LoadSound(soundFile);
        }
    }

    public async void LoadSound(SoundFile soundFile)
    {
        CurrentSound = soundFile;
        isLoading = true;
        samples = null;
        splitPoints.Clear();
        
        loadingLabel.Visible = true;
        dropLabel.Visible = false;

        await LoadAudioDataAsync(soundFile);
    }

    private async Task LoadAudioDataAsync(SoundFile soundFile)
    {
        try 
        {
            await soundFile.LoadAsync();
            samples = await soundFile.GetSamplesAsync();
            
            if (samples == null)
            {
                loadingLabel.Text = "Failed to load audio data";
                return;
            }

            sampleRate = soundFile.Rate;
            splitPoints.Add(0);
            splitPoints.Add(samples.Length - 1);

            loadingLabel.Visible = false;
            isLoading = false;

            Update();
        }
        catch (Exception ex)
        {
            loadingLabel.Text = $"Error loading audio: {ex.Message}";
        }
    }

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

        if (loadingLabel != null)
        {
            loadingLabel.Position = new Vector2(10, Height / 2 - 10);
            loadingLabel.Size = new Vector2(Width - 20, 20);
        }

        if (dropLabel != null)
        {
            dropLabel.Position = new Vector2(10, Height / 2 - 10);
            dropLabel.Size = new Vector2(Width - 20, 20);
        }
    }

    public override void OnDragDrop(DragEvent e)
    {
        base.OnDragDrop(e);

        if (!e.Data.HasFileOrFolder) return;

        var asset = AssetSystem.FindByPath(e.Data.FileOrFolder);
        if (asset?.AssetType != AssetType.SoundFile) return;

        var soundFile = SoundFile.Load(asset.Path);
        if (soundFile != null)
        {
            LoadSound(soundFile);
        }
    }

    public override void OnDragHover(DragEvent e)
    {
        base.OnDragHover(e);

        if (!e.Data.HasFileOrFolder) return;

        var asset = AssetSystem.FindByPath(e.Data.FileOrFolder);
        if (asset?.AssetType != AssetType.SoundFile) return;

        e.Action = DropAction.Link;
    }

    protected override void OnMouseClick(MouseEvent e)
    {
        if (isLoading) return;
        
        base.OnMouseClick(e);

        if (e.Button == MouseButtons.Left)
        {
            var samplePos = (int)(e.LocalPosition.x / Width * samples.Length);
            var nearPoint = splitPoints.FirstOrDefault(p => Math.Abs(p - samplePos) < (samples.Length / Width * 5));
            
            if (nearPoint != default)
            {
                dragPoint = splitPoints.IndexOf(nearPoint);
            }
            else
            {
                splitPoints.Add(samplePos);
                splitPoints.Sort();
                Update();
            }
        }
    }

    protected override void OnMouseMove(MouseEvent e)
    {
        if (isLoading) return;
        
        base.OnMouseMove(e);

        if (dragPoint.HasValue)
        {
            var samplePos = (int)(e.LocalPosition.x / Width * samples.Length);
            splitPoints[dragPoint.Value] = samplePos;
            splitPoints.Sort();
            Update();
        }
    }

    protected override void OnMouseReleased(MouseEvent e)
    {
        if (isLoading) return;
        
        base.OnMouseReleased(e);
        dragPoint = null;
    }

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

        if (isLoading || samples == null)
        {
            return;
        }

        Paint.ClearPen();
        Paint.SetBrush(Theme.Grey.WithAlpha(0.1f));
        Paint.DrawRect(LocalRect);

        Paint.SetPen(Theme.Blue);
        var samplesPerPixel = samples.Length / Width;
        for (int x = 0; x < Width; x++)
        {
            var startSample = (int)(x * samplesPerPixel);
            var endSample = Math.Min(startSample + samplesPerPixel, samples.Length);
            
            var max = short.MinValue;
            var min = short.MaxValue;
            
            for (int i = startSample; i < endSample; i++)
            {
                max = Math.Max(max, samples[i]);
                min = Math.Min(min, samples[i]);
            }

            var y1 = Height / 2 + (min / (float)short.MaxValue * Height / 2);
            var y2 = Height / 2 + (max / (float)short.MaxValue * Height / 2);
            
            Paint.DrawLine(new Vector2(x, y1), new Vector2(x, y2));
        }

        Paint.SetPen(Theme.Red);
        foreach (var point in splitPoints)
        {
            var x = point / (float)samples.Length * Width;
            Paint.DrawLine(new Vector2(x, 0), new Vector2(x, Height));
        }
    }

    public List<int> GetSplitPoints()
    {
        return new List<int>(splitPoints);
    }

    public void SplitCurrentSound(Action<SoundFile> onSoundCreated)
    {
        if (CurrentSound == null || samples == null) return;

        var splitPoints = GetSplitPoints();
        if (splitPoints.Count < 2) return;

        try
        {
            var baseFileName = Path.GetFileNameWithoutExtension(CurrentSound.ResourcePath);
            var outputDir = Path.Combine(
                Project.Current.GetAssetsPath(),
                "generated",
                $"{baseFileName}_splits"
            );
            Directory.CreateDirectory(outputDir);

            for (int i = 0; i < splitPoints.Count - 1; i++)
            {
                var start = splitPoints[i];
                var end = splitPoints[i + 1];
                var length = end - start;

                var segmentSamples = new short[length];
                Array.Copy(samples, start, segmentSamples, 0, length);

                var wavPath = Path.Combine(outputDir, $"{baseFileName}_part_{i + 1}.wav");

                using (var writer = new BinaryWriter(File.Create(wavPath)))
                {
                    writer.Write(Encoding.ASCII.GetBytes("RIFF"));
                    writer.Write(36 + (segmentSamples.Length * 2));
                    writer.Write(Encoding.ASCII.GetBytes("WAVE"));

                    writer.Write(Encoding.ASCII.GetBytes("fmt "));
                    writer.Write(16);
                    writer.Write((short)1);
                    writer.Write((short)CurrentSound.Channels);
                    writer.Write(CurrentSound.Rate);
                    writer.Write(CurrentSound.Rate * CurrentSound.Channels * 2);
                    writer.Write((short)(CurrentSound.Channels * 2));
                    writer.Write((short)16);

                    writer.Write(Encoding.ASCII.GetBytes("data"));
                    writer.Write(segmentSamples.Length * 2);

                    foreach (var sample in segmentSamples)
                    {
                        writer.Write(sample);
                    }
                }

                var asset = AssetSystem.RegisterFile(wavPath);
                if (asset != null)
                {
                    var soundFile = SoundFile.Load(asset.RelativePath);
                    if (soundFile != null)
                    {
                        onSoundCreated?.Invoke(soundFile);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Log.Error($"Error splitting sound: {ex.Message}");
        }
    }
}
global using System;
global using System.Linq;
global using System.Collections.Generic;
global using Editor;
global using Sandbox;
global using PathTool;
global using Application = Editor.Application;
using System.Collections.Generic;

namespace Ink.Parsed
{
    // Used by the FlowBase when constructing the weave flow from
    // a flat list of content objects.
    public class Weave : Parsed.Object
    {
        // Containers can be chained as multiple gather points
        // get created as the same indentation level.
        // rootContainer is always the first in the chain, while
        // currentContainer is the latest.
        public Runtime.Container rootContainer { 
            get {
                if (_rootContainer == null) {
                    GenerateRuntimeObject ();
                }

                return _rootContainer;
            }
        }
        Runtime.Container currentContainer { get; set; }

		public int baseIndentIndex { get; private set; }

        // Loose ends are:
        //  - Choices or Gathers that need to be joined up
        //  - Explicit Divert to gather points (i.e. "->" without a target)
        public List<IWeavePoint> looseEnds;

        public List<GatherPointToResolve> gatherPointsToResolve;
        public class GatherPointToResolve
        {
            public Runtime.Divert divert;
            public Runtime.Object targetRuntimeObj;
        }

        public Parsed.Object lastParsedSignificantObject
        {
            get {
                if (content.Count == 0) return null;

                // Don't count extraneous newlines or VAR/CONST declarations,
                // since they're "empty" statements outside of the main flow.
                Parsed.Object lastObject = null;
                for (int i = content.Count - 1; i >= 0; --i) {
                    lastObject = content [i];

                    var lastText = lastObject as Parsed.Text;
                    if (lastText && lastText.text == "\n") {
                        continue;
                    }

                    if (IsGlobalDeclaration (lastObject))
                        continue;
                    
                    break;
                }

                var lastWeave = lastObject as Weave;
                if (lastWeave)
                    lastObject = lastWeave.lastParsedSignificantObject;
                
                return lastObject;
            }
        }
                        
        public Weave(List<Parsed.Object> cont, int indentIndex=-1) 
        {
            if (indentIndex == -1) {
                baseIndentIndex = DetermineBaseIndentationFromContent (cont);
            } else {
                baseIndentIndex = indentIndex;
            }

            AddContent (cont);

            ConstructWeaveHierarchyFromIndentation ();
        }

        public void ResolveWeavePointNaming ()
        {
            var namedWeavePoints = FindAll<IWeavePoint> (w => !string.IsNullOrEmpty (w.name));

            _namedWeavePoints = new Dictionary<string, IWeavePoint> ();

            foreach (var weavePoint in namedWeavePoints) {

                // Check for weave point naming collisions
                IWeavePoint existingWeavePoint;
                if (_namedWeavePoints.TryGetValue (weavePoint.name, out existingWeavePoint)) {
                    var typeName = existingWeavePoint is Gather ? "gather" : "choice";
                    var existingObj = (Parsed.Object)existingWeavePoint;

                    Error ("A " + typeName + " with the same label name '" + weavePoint.name + "' already exists in this context on line " + existingObj.debugMetadata.startLineNumber, (Parsed.Object)weavePoint);
                }

                _namedWeavePoints [weavePoint.name] = weavePoint;
            }
        }

        void ConstructWeaveHierarchyFromIndentation()
        {
            // Find nested indentation and convert to a proper object hierarchy
            // (i.e. indented content is replaced with a Weave object that contains
            // that nested content)
            int contentIdx = 0;
            while (contentIdx < content.Count) {

                Parsed.Object obj = content [contentIdx];

                // Choice or Gather
                if (obj is IWeavePoint) {
                    var weavePoint = (IWeavePoint)obj;
                    var weaveIndentIdx = weavePoint.indentationDepth - 1;

                    // Inner level indentation - recurse
                    if (weaveIndentIdx > baseIndentIndex) {

                        // Step through content until indent jumps out again
                        int innerWeaveStartIdx = contentIdx;
                        while (contentIdx < content.Count) {
                            var innerWeaveObj = content [contentIdx] as IWeavePoint;
                            if (innerWeaveObj != null) {
                                var innerIndentIdx = innerWeaveObj.indentationDepth - 1;
                                if (innerIndentIdx <= baseIndentIndex) {
                                    break;
                                }
                            }

                            contentIdx++;
                        }

                        int weaveContentCount = contentIdx - innerWeaveStartIdx;

                        var weaveContent = content.GetRange (innerWeaveStartIdx, weaveContentCount);
                        content.RemoveRange (innerWeaveStartIdx, weaveContentCount);

                        var weave = new Weave (weaveContent, weaveIndentIdx);
                        InsertContent (innerWeaveStartIdx, weave);

                        // Continue iteration from this point
                        contentIdx = innerWeaveStartIdx;
                    }

                } 

                contentIdx++;
            }
        }
            
        // When the indentation wasn't told to us at construction time using
        // a choice point with a known indentation level, we may be told to
        // determine the indentation level by incrementing from our closest ancestor.
        public int DetermineBaseIndentationFromContent(List<Parsed.Object> contentList)
        {
            foreach (var obj in contentList) {
                if (obj is IWeavePoint) {
                    return ((IWeavePoint)obj).indentationDepth - 1;
                }
            }

            // No weave points, so it doesn't matter
            return 0;
        }

        public override Runtime.Object GenerateRuntimeObject ()
        {
            _rootContainer = currentContainer = new Runtime.Container();
            looseEnds = new List<IWeavePoint> ();

            gatherPointsToResolve = new List<GatherPointToResolve> ();

            // Iterate through content for the block at this level of indentation
            //  - Normal content is nested under Choices and Gathers
            //  - Blocks that are further indented cause recursion
            //  - Keep track of loose ends so that they can be diverted to Gathers
            foreach(var obj in content) {

                // Choice or Gather
                if (obj is IWeavePoint) {
                    AddRuntimeForWeavePoint ((IWeavePoint)obj);
                } 

                // Non-weave point
                else {

                    // Nested weave
                    if (obj is Weave) {
                        var weave = (Weave)obj;
                        AddRuntimeForNestedWeave (weave);
                        gatherPointsToResolve.AddRange (weave.gatherPointsToResolve);
                    }

                    // Other object
                    // May be complex object that contains statements - e.g. a multi-line conditional
                    else {
                        AddGeneralRuntimeContent (obj.runtimeObject);
                    }
                }
            }

            // Pass any loose ends up the hierarhcy
            PassLooseEndsToAncestors();

            return _rootContainer;
        }

        // Found gather point:
        //  - gather any loose ends
        //  - set the gather as the main container to dump new content in
        void AddRuntimeForGather(Gather gather)
        {
            // Determine whether this Gather should be auto-entered:
            //  - It is auto-entered if there were no choices in the last section
            //  - A section is "since the previous gather" - so reset now
            bool autoEnter = !hasSeenChoiceInSection;
            hasSeenChoiceInSection = false;

            var gatherContainer = gather.runtimeContainer;

            if (gather.name == null) {
                // Use disallowed character so it's impossible to have a name collision
                gatherContainer.name = "g-" + _unnamedGatherCount;
                _unnamedGatherCount++;
            }
                
            // Auto-enter: include in main content
            if (autoEnter) {
                currentContainer.AddContent (gatherContainer);
            } 

            // Don't auto-enter:
            // Add this gather to the main content, but only accessible
            // by name so that it isn't stepped into automatically, but only via
            // a divert from a loose end.
            else {
                _rootContainer.AddToNamedContentOnly (gatherContainer);
            }

            // Consume loose ends: divert them to this gather
            foreach (IWeavePoint looseEndWeavePoint in looseEnds) {

                var looseEnd = (Parsed.Object)looseEndWeavePoint;

                // Skip gather loose ends that are at the same level
                // since they'll be handled by the auto-enter code below
                // that only jumps into the gather if (current runtime choices == 0)
                if (looseEnd is Gather) {
                    var prevGather = (Gather)looseEnd;
                    if (prevGather.indentationDepth == gather.indentationDepth) {
                        continue;
                    }
                }

                Runtime.Divert divert = null;

                if (looseEnd is Parsed.Divert) {
                    divert = (Runtime.Divert) looseEnd.runtimeObject;
                } else {
                    divert = new Runtime.Divert ();
                    var looseWeavePoint = looseEnd as IWeavePoint;
                    looseWeavePoint.runtimeContainer.AddContent (divert);
                }
                   
                // Pass back knowledge of this loose end being diverted
                // to the FlowBase so that it can maintain a list of them,
                // and resolve the divert references later
                gatherPointsToResolve.Add (new GatherPointToResolve{ divert = divert, targetRuntimeObj = gatherContainer });
            }
            looseEnds.Clear ();

            // Replace the current container itself
            currentContainer = gatherContainer;
        }

        void AddRuntimeForWeavePoint(IWeavePoint weavePoint)
        {
            // Current level Gather
            if (weavePoint is Gather) {
                AddRuntimeForGather ((Gather)weavePoint);
            } 

            // Current level choice
            else if (weavePoint is Choice) {

                // Gathers that contain choices are no longer loose ends
                // (same as when weave points get nested content)
                if (previousWeavePoint is Gather) {
                    looseEnds.Remove (previousWeavePoint);
                }

                // Add choice point content
                var choice = (Choice)weavePoint;
                currentContainer.AddContent (choice.runtimeObject);

                // Add choice's inner content to self
                choice.innerContentContainer.name = "c-" + _choiceCount;
                currentContainer.AddToNamedContentOnly (choice.innerContentContainer);
                _choiceCount++;

                hasSeenChoiceInSection = true;
            }

            // Keep track of loose ends
            addContentToPreviousWeavePoint = false; // default
            if (WeavePointHasLooseEnd (weavePoint)) {
                looseEnds.Add (weavePoint);


                var looseChoice = weavePoint as Choice;
                if (looseChoice) {
                    addContentToPreviousWeavePoint = true;
                }
            }
            previousWeavePoint = weavePoint;
        }

        // Add nested block at a greater indentation level
        public void AddRuntimeForNestedWeave(Weave nestedResult)
        {
            // Add this inner block to current container
            // (i.e. within the main container, or within the last defined Choice/Gather)
            AddGeneralRuntimeContent (nestedResult.rootContainer);

            // Now there's a deeper indentation level, the previous weave point doesn't
            // count as a loose end (since it will have content to go to)
            if (previousWeavePoint != null) {
                looseEnds.Remove (previousWeavePoint);
                addContentToPreviousWeavePoint = false;
            }
        }

        // Normal content gets added into the latest Choice or Gather by default,
        // unless there hasn't been one yet.
        void AddGeneralRuntimeContent(Runtime.Object content)
        {
            // Content is allowed to evaluate runtimeObject to null
            // (e.g. AuthorWarning, which doesn't make it into the runtime)
            if (content == null)
                return;
            
            if (addContentToPreviousWeavePoint) {
                previousWeavePoint.runtimeContainer.AddContent (content);
            } else {
                currentContainer.AddContent (content);
            }
        }

        void PassLooseEndsToAncestors()
        {
            if (looseEnds.Count == 0) return;

            // Search for Weave ancestor to pass loose ends to for gathering.
            // There are two types depending on whether the current weave
            // is separated by a conditional or sequence.
            //  - An "inner" weave is one that is directly connected to the current
            //    weave - i.e. you don't have to pass through a conditional or
            //    sequence to get to it. We're allowed to pass all loose ends to
            //    one of these.
            //  - An "outer" weave is one that is outside of a conditional/sequence
            //    that the current weave is nested within. We're only allowed to
            //    pass gathers (i.e. 'normal flow') loose ends up there, not normal
            //    choices. The rule is that choices have to be diverted explicitly
            //    by the author since it's ambiguous where flow should go otherwise.
            //
            // e.g.:
            //
            //   - top                       <- e.g. outer weave
            //   {true:
            //       * choice                <- e.g. inner weave
            //         * * choice 2
            //             more content      <- e.g. current weave
            //       * choice 2
            //   }
            //   - more of outer weave
            //
            Weave closestInnerWeaveAncestor = null;
            Weave closestOuterWeaveAncestor = null;

            // Find inner and outer ancestor weaves as defined above.
            bool nested = false;
            for (var ancestor = this.parent; ancestor != null; ancestor = ancestor.parent)
            {

                // Found ancestor?
                var weaveAncestor = ancestor as Weave;
                if (weaveAncestor != null)
                {
                    if (!nested && closestInnerWeaveAncestor == null)
                        closestInnerWeaveAncestor = weaveAncestor;

                    if (nested && closestOuterWeaveAncestor == null)
                        closestOuterWeaveAncestor = weaveAncestor;
                }


                // Weaves nested within Sequences or Conditionals are
                // "sealed" - any loose ends require explicit diverts.
                if (ancestor is Sequence || ancestor is Conditional)
                    nested = true;
            }

            // No weave to pass loose ends to at all?
            if (closestInnerWeaveAncestor == null && closestOuterWeaveAncestor == null)
                return;

            // Follow loose end passing logic as defined above
            for (int i = looseEnds.Count - 1; i >= 0; i--) {
                var looseEnd = looseEnds[i];

                bool received = false;

                // This weave is nested within a conditional or sequence:
                //  - choices can only be passed up to direct ancestor ("inner") weaves
                //  - gathers can be passed up to either, but favour the closer (inner) weave
                //    if there is one
                if(nested) {
                    if( looseEnd is Choice && closestInnerWeaveAncestor != null) {
                        closestInnerWeaveAncestor.ReceiveLooseEnd(looseEnd);
                        received = true;
                    }

                    else if( !(looseEnd is Choice) ) {
                        var receivingWeave = closestInnerWeaveAncestor ?? closestOuterWeaveAncestor;
                        if(receivingWeave != null) {
                            receivingWeave.ReceiveLooseEnd(looseEnd);
                            received = true;
                        }
                    }
                }

                // No nesting, all loose ends can be safely passed up
                else {
                    closestInnerWeaveAncestor.ReceiveLooseEnd(looseEnd);
                    received = true;
                }

                if(received) looseEnds.RemoveAt(i);
            }
        }

        void ReceiveLooseEnd(IWeavePoint childWeaveLooseEnd)
        {
            looseEnds.Add(childWeaveLooseEnd);
        }

        public override void ResolveReferences(Story context)
        {
            base.ResolveReferences (context);

            // Check that choices nested within conditionals and sequences are terminated
            if( looseEnds != null && looseEnds.Count > 0 ) {
                var isNestedWeave = false;
                for (var ancestor = this.parent; ancestor != null; ancestor = ancestor.parent)
                {
                    if (ancestor is Sequence || ancestor is Conditional)
                    {
                        isNestedWeave = true;
                        break;
                    }
                }
                if (isNestedWeave)
                {
                    ValidateTermination(BadNestedTerminationHandler);
                }
            }

            foreach(var gatherPoint in gatherPointsToResolve) {
                gatherPoint.divert.targetPath = gatherPoint.targetRuntimeObj.path;
            }
                
            CheckForWeavePointNamingCollisions ();
        }

        public IWeavePoint WeavePointNamed(string name)
        {
            if (_namedWeavePoints == null)
                return null;

            IWeavePoint weavePointResult = null;
            if (_namedWeavePoints.TryGetValue (name, out weavePointResult))
                return weavePointResult;

            return null;
        }

        // Global VARs and CONSTs are treated as "outside of the flow"
        // when iterating over content that follows loose ends
        bool IsGlobalDeclaration (Parsed.Object obj)
        {

            var varAss = obj as VariableAssignment;
            if (varAss && varAss.isGlobalDeclaration && varAss.isDeclaration)
                return true;
            
            var constDecl = obj as ConstantDeclaration;
            if (constDecl)
                return true;

            return false;
        }

        // While analysing final loose ends, we look to see whether there
        // are any diverts etc which choices etc divert from
        IEnumerable<Parsed.Object> ContentThatFollowsWeavePoint (IWeavePoint weavePoint)
        {
            var obj = (Parsed.Object)weavePoint;

            // Inner content first (e.g. for a choice)
            if (obj.content != null) {
                foreach (var contentObj in obj.content) {

                    // Global VARs and CONSTs are treated as "outside of the flow"
                    if (IsGlobalDeclaration (contentObj)) continue;

                    yield return contentObj;
                }
            }


            var parentWeave = obj.parent as Weave;
            if (parentWeave == null) {
                throw new System.Exception ("Expected weave point parent to be weave?");
            }

            var weavePointIdx = parentWeave.content.IndexOf (obj);

            for (int i = weavePointIdx+1; i < parentWeave.content.Count; i++) {
                var laterObj = parentWeave.content [i];

                // Global VARs and CONSTs are treated as "outside of the flow"
                if (IsGlobalDeclaration (laterObj)) continue;

                // End of the current flow
                if (laterObj is IWeavePoint) 
                    break;

                // Other weaves will be have their own loose ends
                if (laterObj is Weave)
                    break;

                yield return laterObj;
            }
        }

        public delegate void BadTerminationHandler (Parsed.Object terminatingObj);
        public void ValidateTermination (BadTerminationHandler badTerminationHandler)
        {
            // Don't worry if the last object in the flow is a "TODO",
            // even if there are other loose ends in other places
            if (lastParsedSignificantObject is AuthorWarning) {
                return;
            }

            // By now, any sub-weaves will have passed loose ends up to the root weave (this).
            // So there are 2 possible situations:
            //  - There are loose ends from somewhere in the flow.
            //    These aren't necessarily "real" loose ends - they're weave points
            //    that don't connect to any lower weave points, so we just
            //    have to check that they terminate properly.
            //  - This weave is just a list of content with no actual weave points,
            //    so we just need to check that the list of content terminates.

            bool hasLooseEnds = looseEnds != null && looseEnds.Count > 0;

            if (hasLooseEnds) {
                foreach (var looseEnd in looseEnds) {
                    var looseEndFlow = ContentThatFollowsWeavePoint (looseEnd);
                    ValidateFlowOfObjectsTerminates (looseEndFlow, (Parsed.Object)looseEnd, badTerminationHandler);
                }
            }

            // No loose ends... is there any inner weaving at all?
            // If not, make sure the single content stream is terminated correctly
            else {

                // If there's any actual weaving, assume that content is 
                // terminated correctly since we would've had a loose end otherwise
                foreach (var obj in content) {
                    if (obj is IWeavePoint) return;
                }

                // Straight linear flow? Check it terminates
                ValidateFlowOfObjectsTerminates (content, this, badTerminationHandler);
            }
        }

        void BadNestedTerminationHandler(Parsed.Object terminatingObj)
        {
            Conditional conditional = null;
            for (var ancestor = terminatingObj.parent; ancestor != null; ancestor = ancestor.parent) {
                if( ancestor is Sequence || ancestor is Conditional ) {
                    conditional = ancestor as Conditional;
                    break;
                }
            }

            var errorMsg = "Choices nested in conditionals or sequences need to explicitly divert afterwards.";

            // Tutorialise proper choice syntax if this looks like a single choice within a condition, e.g.
            // { condition:
            //      * choice
            // }
            if (conditional != null) {
                var numChoices = conditional.FindAll<Choice>().Count;
                if( numChoices == 1 ) {
                    errorMsg = "Choices with conditions should be written: '* {condition} choice'. Otherwise, "+ errorMsg.ToLower();
                }
            }

            Error(errorMsg, terminatingObj);
        }

        void ValidateFlowOfObjectsTerminates (IEnumerable<Parsed.Object> objFlow, Parsed.Object defaultObj, BadTerminationHandler badTerminationHandler)
        {
            bool terminated = false;
            Parsed.Object terminatingObj = defaultObj;
            foreach (var flowObj in objFlow) {

                var divert = flowObj.Find<Divert> (d => !d.isThread && !d.isTunnel && !d.isFunctionCall && !(d.parent is DivertTarget));
                if (divert != null) {
                    terminated = true;
                }

                if (flowObj.Find<TunnelOnwards> () != null) {
                    terminated = true;
                    break;
                }

                terminatingObj = flowObj;
            }


            if (!terminated) {

                // Author has left a note to self here - clearly we don't need
                // to leave them with another warning since they know what they're doing.
                if (terminatingObj is AuthorWarning) {
                    return;
                }

                badTerminationHandler (terminatingObj);
            }
        }
            
        bool WeavePointHasLooseEnd(IWeavePoint weavePoint)
        {
            // No content, must be a loose end.
            if (weavePoint.content == null) return true;

            // If a weave point is diverted from, it doesn't have a loose end.
            // Detect a divert object within a weavePoint's main content
            // Work backwards since we're really interested in the end,
            // although it doesn't actually make a difference!
            // (content after a divert will simply be inaccessible)
            for (int i = weavePoint.content.Count - 1; i >= 0; --i) {
                var innerDivert = weavePoint.content [i] as Divert;
                if (innerDivert) {
                    bool willReturn = innerDivert.isThread || innerDivert.isTunnel || innerDivert.isFunctionCall;
                    if (!willReturn) return false;
                }
            }

            return true;
        }

        // Enforce rule that weave points must not have the same
        // name as any stitches or knots upwards in the hierarchy
        void CheckForWeavePointNamingCollisions()
        {
            if (_namedWeavePoints == null)
                return;
            
            var ancestorFlows = new List<FlowBase> ();
            foreach (var obj in this.ancestry) {
                var flow = obj as FlowBase;
                if (flow)
                    ancestorFlows.Add (flow);
                else
                    break;
            }


            foreach (var namedWeavePointPair in _namedWeavePoints) {
                var weavePointName = namedWeavePointPair.Key;
                var weavePoint = (Parsed.Object) namedWeavePointPair.Value;

                foreach(var flow in ancestorFlows) {

                    // Shallow search
                    var otherContentWithName = flow.ContentWithNameAtLevel (weavePointName);

                    if (otherContentWithName && otherContentWithName != weavePoint) {
                        var errorMsg = string.Format ("{0} '{1}' has the same label name as a {2} (on {3})", 
                            weavePoint.GetType().Name, 
                            weavePointName, 
                            otherContentWithName.GetType().Name, 
                            otherContentWithName.debugMetadata);

                        Error(errorMsg, (Parsed.Object) weavePoint);
                    }

                }
            }
        }

        // Keep track of previous weave point (Choice or Gather)
        // at the current indentation level:
        //  - to add ordinary content to be nested under it
        //  - to add nested content under it when it's indented
        //  - to remove it from the list of loose ends when
        //     - it has indented content since it's no longer a loose end
        //     - it's a gather and it has a choice added to it
        IWeavePoint previousWeavePoint = null;
        bool addContentToPreviousWeavePoint = false;

        // Used for determining whether the next Gather should auto-enter
        bool hasSeenChoiceInSection = false;

        int _unnamedGatherCount;

        int _choiceCount;


        Runtime.Container _rootContainer;
        Dictionary<string, IWeavePoint> _namedWeavePoints;
    }
}


namespace Ink.Parsed
{
    public class Wrap<T> : Parsed.Object where T : Runtime.Object
    {
        public Wrap (T objToWrap)
        {
            _objToWrap = objToWrap;
        }

        public override Runtime.Object GenerateRuntimeObject ()
        {
            return _objToWrap;
        }

        T _objToWrap;
    }

    // Shorthand for writing Parsed.Wrap<Runtime.Glue> and Parsed.Wrap<Runtime.Tag>
    public class Glue : Wrap<Runtime.Glue> {
        public Glue (Runtime.Glue glue) : base(glue) {}
    }
    public class LegacyTag : Wrap<Runtime.Tag> {
        public LegacyTag (Runtime.Tag tag) : base (tag) { }
    }
    
}

using Editor;
using Sandbox;
using System.Linq;

namespace SFXR.Editor;

//[CustomEditor( typeof( SFXRComponent ) )]
public class SFXRComponentEditor : ComponentEditorWidget
{
    //ParticleFloatControlWidget

    public SFXRComponentEditor( SerializedObject obj ) : base( obj )
    {

        var defaultInspector = new PropertyControlSheet();
        defaultInspector.AddObject( obj );

        Layout = Layout.Column();
        Layout.Add( defaultInspector );
        Layout.AddSpacingCell( 5 );


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

namespace SFXR.Editor;

//[CustomEditor( typeof( SFXRFloat ) )]
// public class SFXRFloatControlWidget : ControlWidget
// {

//     SerializedObject Target;

//     public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
//     {
//         SetSizeMode( SizeMode.Ignore, SizeMode.Default );

//         if ( !property.TryGetAsObject( out Target ) )
//             return;

//         Layout = Layout.Row();
//         Layout.Spacing = 2;

//         var value = Target.GetProperty( "Value" );
//         FloatControlWidget valueWidget = new FloatControlWidget( value );

//         if ( property.TryGetAttribute<RangeAttribute>( out var attribute ) )
//         {
//             valueWidget.Range = new Vector2( attribute.Min, attribute.Max );
//             valueWidget.RangeStep = attribute.Step;
//             valueWidget.HasRange = attribute.Slider;
//             // if ( attribute.Slider )
//             // {
//             //     sliderWidget = new FloatSlider( this );
//             //     sliderWidget.HighlightColor = Theme.Grey;
//             //     sliderWidget.Minimum = attribute.Min;
//             //     sliderWidget.Maximum = attribute.Max;
//             //     sliderWidget.Step = attribute.S;
//             //     sliderWidget.OnValueEdited = delegate
//             //     {
//             //         base.SerializedProperty.As.Float = sliderWidget.Value;
//             //     };
//             // }
//         }

//         Layout.Add( valueWidget );

//         var locked = Target.GetProperty( "Locked" );
//         IconButton lockedButton = new IconButton( "lock_open" );
//         lockedButton.OnClick = () =>
//         {
//             if ( Target.Targets.First() is SFXRFloat sfxrFloat )
//             {
//                 sfxrFloat.Locked = !sfxrFloat.Locked;
//                 Log.Info( sfxrFloat.Locked );
//                 lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
//                 lockedButton.Update();
//             }
//         };
//         lockedButton.ToolTip = "Lock/Unlock this value";
//         Layout.Add( lockedButton );
//     }

//     protected override void OnPaint()
//     {

//     }

// }

// public class SFXRFloatControlWidget : FloatControlWidget
// {

//     SerializedObject Target;

//     public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
//     {
//         if ( !property.TryGetAsObject( out Target ) )
//             return;

//         var locked = Target.GetProperty( "Locked" );
//         IconButton lockedButton = new IconButton( "lock_open" );
//         lockedButton.OnClick = () =>
//         {
//             // if ( Target.Targets.First() is SFXRFloat sfxrFloat )
//             // {
//             //     sfxrFloat.Locked = !sfxrFloat.Locked;
//             //     Log.Info( sfxrFloat.Locked );
//             //     lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
//             //     lockedButton.Update();
//             // }
//         };
//         lockedButton.ToolTip = "Lock/Unlock this value";
//         //Layout.Add( lockedButton );
//     }

//     protected override void PaintControl()
//     {
//         if ( Target is null ) return;

//         base.PaintControl();
//     }

//     protected override void OnPaint()
//     {
//         if ( Target is null ) return;

//         base.OnPaint();
//     }
// }
using Editor;
using Sandbox;
using System.Linq;

namespace SFXR.Editor;

[CustomEditor( typeof( SFXRControls ) )]
public class SFXRSoundControlWidget : ControlWidget
{

    SerializedObject Target;

    public SFXRSoundControlWidget( SerializedProperty property ) : base( property )
    {

        if ( !property.TryGetAsObject( out Target ) )
            return;

        var component = property.Parent.Targets.First() as SFXRComponent;

        // Randomize Button
        var btnRandomize = new Button( "Randomize All", "casino" );
        btnRandomize.Clicked = () =>
        {
            component.Randomize();
            component.PlaySound();
        };
        btnRandomize.ToolTip = "Randomize all sound properties";

        // Play Sound Button
        var btnPlaySound = new Button( "Play Sound", "play_arrow" );
        btnPlaySound.Clicked = () =>
        {
            component.PlaySound();
        };
        btnPlaySound.MinimumWidth = 200;
        btnPlaySound.ToolTip = "Play the current sound";

        // Mutate Button
        var btnMutate = new Button( "Mutate", "shuffle" );
        btnMutate.Clicked = () =>
        {
            component.Mutate();
            component.PlaySound();
        };
        btnMutate.ToolTip = "Mutate all sound properties";

        // Randomize Pickup Button
        var btnRandomizePickup = new Button( "", "monetization_on" );
        btnRandomizePickup.Clicked = () =>
        {
            component.RandomizePickup();
            component.PlaySound();
        };
        btnRandomizePickup.ToolTip = "Generate random pickup sound";

        // Randomize Laser Button
        var btnRandomizeLaser = new Button( "", "bolt" );
        btnRandomizeLaser.Clicked = () =>
        {
            component.RandomizeLaser();
            component.PlaySound();
        };
        btnRandomizeLaser.ToolTip = "Generate random laser sound";

        // Randomize Explosion Button
        var btnRandomizeExplosion = new Button( "", "flare" );
        btnRandomizeExplosion.Clicked = () =>
        {
            component.RandomizeExplosion();
            component.PlaySound();
        };
        btnRandomizeExplosion.ToolTip = "Generate random explosion sound";

        // Randomize Powerup Button
        var btnRandomizePowerup = new Button( "", "star" );
        btnRandomizePowerup.Clicked = () =>
        {
            component.RandomizePowerup();
            component.PlaySound();
        };
        btnRandomizePowerup.ToolTip = "Generate random powerup sound";

        // Randomize Hit Hurt Button
        var btnRandomizeHit = new Button( "", "sentiment_very_dissatisfied" );
        btnRandomizeHit.Clicked = () =>
        {
            component.RandomizeHit();
            component.PlaySound();
        };
        btnRandomizeHit.ToolTip = "Generate random hit/hurt sound";

        // Randomize Jump Button
        var btnRandomizeJump = new Button( "", "settings_accessibility" );
        btnRandomizeJump.Clicked = () =>
        {
            component.RandomizeJump();
            component.PlaySound();
        };
        btnRandomizeJump.ToolTip = "Generate random jump sound";

        // Randomize Blip Select Button
        var btnRandomizeBlipSelect = new Button( "", "menu" );
        btnRandomizeBlipSelect.Clicked = () =>
        {
            component.RandomizeBlip();
            component.PlaySound();
        };
        btnRandomizeBlipSelect.ToolTip = "Generate random blip/select sound";

        Layout = Layout.Column();
        Layout.Spacing = 2;
        Layout.Margin = new Sandbox.UI.Margin( 0, 4 );
        MinimumHeight = 90;

        var grid = Layout.Grid();
        grid.Spacing = 2;
        grid.AddCell( 0, 0, btnRandomizePickup );
        grid.AddCell( 1, 0, btnRandomizeLaser );
        grid.AddCell( 2, 0, btnRandomizeExplosion );
        grid.AddCell( 3, 0, btnRandomizePowerup );
        grid.AddCell( 4, 0, btnRandomizeHit );
        grid.AddCell( 5, 0, btnRandomizeJump );
        grid.AddCell( 6, 0, btnRandomizeBlipSelect );

        var randomRow = Layout.Row();
        randomRow.Spacing = 2;
        randomRow.Add( btnRandomize );
        randomRow.Add( btnMutate );

        Layout.Add( grid );
        Layout.Add( randomRow );
        Layout.Add( btnPlaySound );
    }

    protected override void OnPaint()
    {
    }

}
using System.Collections.Generic;

namespace Ink
{

	public class CharacterSet : HashSet<char>
	{
		public static CharacterSet FromRange(char start, char end) 
		{
			return new CharacterSet ().AddRange (start, end);
		}

		public CharacterSet ()
		{
		}

		public CharacterSet(string str)
		{
            AddCharacters (str);
		}

        public CharacterSet(CharacterSet charSetToCopy)
        {
            AddCharacters (charSetToCopy);
        }

		public CharacterSet AddRange(char start, char end)
		{
			for(char c=start; c<=end; ++c) {
				Add (c);
			}
			return this;
		}

		public CharacterSet AddCharacters(IEnumerable<char> chars)
		{
            foreach (char c in chars) {
				Add (c);
			}
			return this;
		}

        public CharacterSet AddCharacters (string chars)
        {
        	foreach (char c in chars) {
        		Add (c);
        	}
        	return this;
        }

	}
}

using System.Collections.Generic;

namespace Ink
{
    public static class InkStringConversionExtensions
    {
        public static string[] ToStringsArray<T>(this List<T> list) {
            int count = list.Count;
            var strings = new string[count];

            for(int i = 0; i < count; i++) {
                strings[i] = list[i].ToString();
            }

            return strings;
        }
    }
}
using Ink.Parsed;
using System.Collections.Generic;
using System.IO;


namespace Ink
{
    public partial class InkParser
    {
        protected object IncludeStatement()
        {
            Whitespace ();

            if (ParseString ("INCLUDE") == null)
                return null;

            Whitespace ();

            var filename = (string) Expect(() => ParseUntilCharactersFromString ("\n\r"), "filename for include statement");
            filename = filename.TrimEnd (' ', '\t');

            // Working directory should already have been set up relative to the root ink file.
            var fullFilename = _rootParser._fileHandler.ResolveInkFilename (filename);

            if (FilenameIsAlreadyOpen (fullFilename)) {
                Error ("Recursive INCLUDE detected: '" + fullFilename + "' is already open.");
                ParseUntilCharactersFromString("\r\n");
                return new IncludedFile(null);
            } else {
                AddOpenFilename (fullFilename);
            }

            Parsed.Story includedStory = null;
            string includedString = null;
            try {
                includedString = _rootParser._fileHandler.LoadInkFileContents(fullFilename);
            }
            catch {
                Error ("Failed to load: '"+filename+"'");
            }


            if (includedString != null ) {
                InkParser parser = new InkParser(includedString, filename, _externalErrorHandler, _rootParser);
                includedStory = parser.Parse();
            }

            RemoveOpenFilename (fullFilename);

            // Return valid IncludedFile object even if there were errors when parsing.
            // We don't want to attempt to re-parse the include line as something else,
            // and we want to include the bits that *are* valid, so we don't generate
            // more errors than necessary.
            return new IncludedFile (includedStory);
        }

        bool FilenameIsAlreadyOpen(string fullFilename)
        {
            return _rootParser._openFilenames.Contains (fullFilename);
        }

        void AddOpenFilename(string fullFilename)
        {
            _rootParser._openFilenames.Add (fullFilename);
        }

        void RemoveOpenFilename(string fullFilename)
        {
            _rootParser._openFilenames.Remove (fullFilename);
        }
                   
        InkParser _rootParser;
        HashSet<string> _openFilenames;
    }
}


namespace Ink.Parsed
{
    public interface INamedContent
    {
        string name { get; }
    }
}

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

namespace Ink.Parsed
{
    public class ListDefinition : Parsed.Object
    {
        public Identifier identifier;
        public List<ListElementDefinition> itemDefinitions;

        public VariableAssignment variableAssignment;

        public Runtime.ListDefinition runtimeListDefinition {
            get {
                var allItems = new Dictionary<string, int> ();
                foreach (var e in itemDefinitions) {
                    if( !allItems.ContainsKey(e.name) )
                        allItems.Add (e.name, e.seriesValue);
                    else
                        Error("List '"+identifier+"' contains dupicate items called '"+e.name+"'");
                }

                return new Runtime.ListDefinition (identifier?.name, allItems);
            }
        }

        public ListElementDefinition ItemNamed (string itemName)
        {
            if (_elementsByName == null) {
                _elementsByName = new Dictionary<string, ListElementDefinition> ();
                foreach (var el in itemDefinitions) {
                    _elementsByName [el.name] = el;
                }
            }

            ListElementDefinition foundElement;
            if (_elementsByName.TryGetValue (itemName, out foundElement))
                return foundElement;

            return null;
        }

        public ListDefinition (List<ListElementDefinition> elements)
        {
            this.itemDefinitions = elements;

            int currentValue = 1;
            foreach (var e in this.itemDefinitions) {
                if (e.explicitValue != null)
                    currentValue = e.explicitValue.Value;

                e.seriesValue = currentValue;

                currentValue++;
            }

            AddContent (elements);
        }

        public override Runtime.Object GenerateRuntimeObject ()
        {
            var initialValues = new Runtime.InkList ();
            foreach (var itemDef in itemDefinitions) {
                if (itemDef.inInitialList) {
                    var item = new Runtime.InkListItem (this.identifier?.name, itemDef.name);
                    initialValues [item] = itemDef.seriesValue;
                }
            }

            // Set origin name, so
            initialValues.SetInitialOriginName (identifier?.name);

            return new Runtime.ListValue (initialValues);
        }

        public override void ResolveReferences (Story context)
        {
            base.ResolveReferences (context);

            context.CheckForNamingCollisions (this, identifier, Story.SymbolType.List);
        }

        public override string typeName {
            get {
                return "List definition";
            }
        }

        Dictionary<string, ListElementDefinition> _elementsByName;
    }

    public class ListElementDefinition : Parsed.Object
    {
        public string name
        {
            get { return identifier?.name; }
        }
        public Identifier identifier;
        public int? explicitValue;
        public int seriesValue;
        public bool inInitialList;

        public string fullName {
            get {
                var parentList = parent as ListDefinition;
                if (parentList == null)
                    throw new System.Exception ("Can't get full name without a parent list");

                return parentList.identifier + "." + name;
            }
        }

        public ListElementDefinition (Identifier identifier, bool inInitialList, int? explicitValue = null)
        {
            this.identifier = identifier;
            this.inInitialList = inInitialList;
            this.explicitValue = explicitValue;
        }

        public override Runtime.Object GenerateRuntimeObject ()
        {
            throw new System.NotImplementedException ();
        }

        public override void ResolveReferences (Story context)
        {
            base.ResolveReferences (context);

            context.CheckForNamingCollisions (this, identifier, Story.SymbolType.ListItem);
        }

        public override string typeName {
        	get {
                return "List element";
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics;
using System.Text;

namespace Ink
{
	public class StringParser
	{
		public delegate object ParseRule();

        public delegate T SpecificParseRule<T>() where T : class;

        public delegate void ErrorHandler(string message, int index, int lineIndex, bool isWarning);

		public StringParser (string str)
		{
            str = PreProcessInputString (str);

            state = new StringParserState();

            if (str != null) {
                _chars = str.ToCharArray ();
            } else {
                _chars = new char[0];
            }

			inputString = str;
		}

		public class ParseSuccessStruct {};
		public static ParseSuccessStruct ParseSuccess = new ParseSuccessStruct();

		public static CharacterSet numbersCharacterSet = new CharacterSet("0123456789");

        protected ErrorHandler errorHandler { get; set; }

		public char currentCharacter
		{
			get
			{
				if (index >= 0 && remainingLength > 0) {
					return _chars [index];
				} else {
					return (char)0;
				}
			}
		}

		public StringParserState state { get; private set; }

        public bool hadError { get; protected set; }

        // Don't do anything by default, but provide ability for subclasses
        // to manipulate the string before it's used as input (converted to a char array)
        protected virtual string PreProcessInputString(string str)
        {
            return str;
        }

		//--------------------------------
		// Parse state
		//--------------------------------

        protected int BeginRule()
        {
            return state.Push ();
        }

        protected object FailRule(int expectedRuleId)
        {
            state.Pop (expectedRuleId);
            return null;
        }

        protected void CancelRule(int expectedRuleId)
        {
            state.Pop (expectedRuleId);
        }

        protected object SucceedRule(int expectedRuleId, object result = null)
        {
            // Get state at point where this rule stared evaluating
            var stateAtSucceedRule = state.Peek(expectedRuleId);
            var stateAtBeginRule = state.PeekPenultimate ();


            // Allow subclass to receive callback
            RuleDidSucceed (result, stateAtBeginRule, stateAtSucceedRule);

            // Flatten state stack so that we maintain the same values,
            // but remove one level in the stack.
            state.Squash();

            if (result == null) {
                result = ParseSuccess;
            }

            return result;
        }

        protected virtual void RuleDidSucceed(object result, StringParserState.Element startState, StringParserState.Element endState)
        {

        }

        protected object Expect(ParseRule rule, string message = null, ParseRule recoveryRule = null)
		{
            object result = ParseObject(rule);
			if (result == null) {
				if (message == null) {
                    message = rule.Method.Name;
				}

                string butSaw;
                string lineRemainder = LineRemainder ();
                if (lineRemainder == null || lineRemainder.Length == 0) {
                    butSaw = "end of line";
                } else {
                    butSaw = "'" + lineRemainder + "'";
                }

                Error ("Expected "+message+" but saw "+butSaw);

				if (recoveryRule != null) {
					result = recoveryRule ();
				}
			}
			return result;
		}

        protected void Error(string message, bool isWarning = false)
		{
            ErrorOnLine (message, lineIndex + 1, isWarning);
		}

        protected void ErrorWithParsedObject(string message, Parsed.Object result, bool isWarning = false)
        {
            ErrorOnLine (message, result.debugMetadata.startLineNumber, isWarning);
        }

        protected void ErrorOnLine(string message, int lineNumber, bool isWarning)
        {
            if ( !state.errorReportedAlreadyInScope ) {

                var errorType = isWarning ? "Warning" : "Error";

                if (errorHandler == null) {
                    throw new System.Exception (errorType+" on line " + lineNumber + ": " + message);
                } else {
                    errorHandler (message, index, lineNumber-1, isWarning);
                }

                state.NoteErrorReported ();
            }

            if( !isWarning )
                hadError = true;
        }

        protected void Warning(string message)
        {
            Error(message, isWarning:true);
        }

		public bool endOfInput
		{
			get { return index >= _chars.Length; }
		}

		public string remainingString
		{
			get {
				return new string(_chars, index, remainingLength);
			}
		}

        public string LineRemainder()
		{
            return (string) Peek (() => ParseUntilCharactersFromString ("\n\r"));
		}

		public int remainingLength
		{
			get {
				return _chars.Length - index;
			}
		}

		public string inputString { get; private set; }


        public int lineIndex
        {
            set {
                state.lineIndex = value;
            }
            get {
                return state.lineIndex;
            }
        }

        public int characterInLineIndex
        {
            set {
                state.characterInLineIndex = value;
            }
            get {
                return state.characterInLineIndex;
            }
        }

        public int index
        {
            // If we want subclass parsers to be able to set the index directly,
            // then we would need to know what the lineIndex of the new
            // index would be - would we have to step through manually
            // counting the newlines to do so?
            private set {
                state.characterIndex = value;
            }
            get {
                return state.characterIndex;
            }
        }

        public void SetFlag(uint flag, bool trueOrFalse) {
            if (trueOrFalse) {
                state.customFlags |= flag;
            } else {
                state.customFlags &= ~flag;
            }
        }

        public bool GetFlag(uint flag) {
            return (state.customFlags & flag) != 0;
        }

		//--------------------------------
		// Structuring
		//--------------------------------

        public object ParseObject(ParseRule rule)
        {
            int ruleId = BeginRule ();

            var stackHeightBefore = state.stackHeight;

            var result = rule ();

            if (stackHeightBefore != state.stackHeight) {
                throw new System.Exception ("Mismatched Begin/Fail/Succeed rules");
            }

            if (result == null)
                return FailRule (ruleId);

            SucceedRule (ruleId, result);
            return result;
        }

        public T Parse<T>(SpecificParseRule<T> rule) where T : class
        {
            int ruleId = BeginRule ();

            var result = rule () as T;
            if (result == null) {
                FailRule (ruleId);
                return null;
            }

            SucceedRule (ruleId, result);
            return result;
        }

		public object OneOf(params ParseRule[] array)
		{
			foreach (ParseRule rule in array) {
                object result = ParseObject(rule);
				if (result != null)
                    return result;
			}

			return null;
		}

		public List<object> OneOrMore(ParseRule rule)
		{
			var results = new List<object> ();

			object result = null;
			do {
                result = ParseObject(rule);
				if( result != null ) {
					results.Add(result);
				}
			} while(result != null);

			if (results.Count > 0) {
				return results;
			} else {
				return null;
			}
		}

		public ParseRule Optional(ParseRule rule)
		{
			return () => {
                object result = ParseObject(rule);
				if( result == null ) {
					result = ParseSuccess;
				}
				return result;
			};
		}

        // Return ParseSuccess instead the real result so that it gets excluded
        // from result arrays (e.g. Interleave)
        public ParseRule Exclude(ParseRule rule)
        {
            return () => {
                object result = ParseObject(rule);
                if( result == null ) {
                    return null;
                }
                return ParseSuccess;
            };
        }

        // Combination of both of the above
        public ParseRule OptionalExclude(ParseRule rule)
        {
            return () => {
                ParseObject(rule);
                return ParseSuccess;
            };
        }

        // Convenience method for creating more readable ParseString rules that can be combined
        // in other structuring rules (like OneOf etc)
        // e.g. OneOf(String("one"), String("two"))
        protected ParseRule String(string str)
        {
            return () => ParseString (str);
        }

		private void TryAddResultToList<T>(object result, List<T> list, bool flatten = true)
		{
			if (result == ParseSuccess) {
				return;
			}

			if (flatten) {
				var resultCollection = result as System.Collections.ICollection;
				if (resultCollection != null) {
					foreach (object obj in resultCollection) {
						SboxDebug.Assert (obj is T);
						list.Add ((T)obj);
					}
					return;
				}
			}

			SboxDebug.Assert (result is T);
			list.Add ((T)result);
		}


		public List<T> Interleave<T>(ParseRule ruleA, ParseRule ruleB, ParseRule untilTerminator = null, bool flatten = true)
		{
            int ruleId = BeginRule ();

			var results = new List<T> ();

			// First outer padding
            var firstA = ParseObject(ruleA);
			if (firstA == null) {
                return (List<T>) FailRule(ruleId);
			} else {
				TryAddResultToList(firstA, results, flatten);
			}

			object lastMainResult = null, outerResult = null;
			do {

				// "until" condition hit?
				if( untilTerminator != null && Peek(untilTerminator) != null ) {
					break;
				}

				// Main inner
                lastMainResult = ParseObject(ruleB);
				if( lastMainResult == null ) {
					break;
				} else {
					TryAddResultToList(lastMainResult, results, flatten);
				}

				// Outer result (i.e. last A in ABA)
				outerResult = null;
				if( lastMainResult != null ) {
                    outerResult = ParseObject(ruleA);
					if (outerResult == null) {
						break;
					} else {
						TryAddResultToList(outerResult, results, flatten);
					}
				}

			// Stop if there are no results, or if both are the placeholder "ParseSuccess" (i.e. Optional success rather than a true value)
			} while((lastMainResult != null || outerResult != null)
				 && !(lastMainResult == ParseSuccess && outerResult == ParseSuccess) && remainingLength > 0);

			if (results.Count == 0) {
                return (List<T>) FailRule(ruleId);
			}

            return (List<T>) SucceedRule(ruleId, results);
		}

		//--------------------------------
		// Basic string parsing
		//--------------------------------

		public string ParseString(string str)
		{
			if (str.Length > remainingLength) {
				return null;
			}

            int ruleId = BeginRule ();

            // Optimisation from profiling:
            // Store in temporary local variables
            // since they're properties that would have to access
            // the rule stack every time otherwise.
            int i = index;
            int cli = characterInLineIndex;
            int li = lineIndex;

			bool success = true;
			foreach (char c in str) {
				if ( _chars[i] != c) {
					success = false;
					break;
				}
                if (c == '\n') {
                    li++;
                    cli = -1;
                }
				i++;
                cli++;
			}

            index = i;
            characterInLineIndex = cli;
            lineIndex = li;

			if (success) {
                return (string) SucceedRule(ruleId, str);
			}
			else {
                return (string) FailRule (ruleId);
			}
		}

        public char ParseSingleCharacter()
        {
            if (remainingLength > 0) {
                char c = _chars [index];
                if (c == '\n') {
                    lineIndex++;
                    characterInLineIndex = -1;
                }
                index++;
                characterInLineIndex++;
                return c;
            } else {
                return (char)0;
            }
        }

		public string ParseUntilCharactersFromString(string str, int maxCount = -1)
		{
			return ParseCharactersFromString(str, false, maxCount);
		}

		public string ParseUntilCharactersFromCharSet(CharacterSet charSet, int maxCount = -1)
		{
			return ParseCharactersFromCharSet(charSet, false, maxCount);
		}

		public string ParseCharactersFromString(string str, int maxCount = -1)
		{
			return ParseCharactersFromString(str, true, maxCount);
		}

		public string ParseCharactersFromString(string str, bool shouldIncludeStrChars, int maxCount = -1)
		{
			return ParseCharactersFromCharSet (new CharacterSet(str), shouldIncludeStrChars);
		}

		public string ParseCharactersFromCharSet(CharacterSet charSet, bool shouldIncludeChars = true, int maxCount = -1)
		{
			if (maxCount == -1) {
				maxCount = int.MaxValue;
			}

			int startIndex = index;

            // Optimisation from profiling:
            // Store in temporary local variables
            // since they're properties that would have to access
            // the rule stack every time otherwise.
            int i = index;
            int cli = characterInLineIndex;
            int li = lineIndex;

			int count = 0;
            while ( i < _chars.Length && charSet.Contains (_chars [i]) == shouldIncludeChars && count < maxCount ) {
                if (_chars [i] == '\n') {
                    li++;
                    cli = -1;
                }
                i++;
                cli++;
				count++;
			}

            index = i;
            characterInLineIndex = cli;
            lineIndex = li;

			int lastCharIndex = index;
			if (lastCharIndex > startIndex) {
				return new string (_chars, startIndex, index - startIndex);
			} else {
				return null;
			}
		}

		public object Peek(ParseRule rule)
		{
			int ruleId = BeginRule ();
			object result = rule ();
            CancelRule (ruleId);
			return result;
		}

		public string ParseUntil(ParseRule stopRule, CharacterSet pauseCharacters = null, CharacterSet endCharacters = null)
		{
			int ruleId = BeginRule ();


			CharacterSet pauseAndEnd = new CharacterSet ();
			if (pauseCharacters != null) {
				pauseAndEnd.UnionWith (pauseCharacters);
			}
			if (endCharacters != null) {
				pauseAndEnd.UnionWith (endCharacters);
			}

			StringBuilder parsedString = new StringBuilder ();
			object ruleResultAtPause = null;

			// Keep attempting to parse strings up to the pause (and end) points.
			//  - At each of the pause points, attempt to parse according to the rule
			//  - When the end point is reached (or EOF), we're done
			do {

				// TODO: Perhaps if no pause or end characters are passed, we should check *every* character for stopRule?
				string partialParsedString = ParseUntilCharactersFromCharSet(pauseAndEnd);
				if( partialParsedString != null ) {
					parsedString.Append(partialParsedString);
				}

				// Attempt to run the parse rule at this pause point
				ruleResultAtPause = Peek(stopRule);

				// Rule completed - we're done
				if( ruleResultAtPause != null ) {
					break;
				} else {

					if( endOfInput ) {
						break;
					}

					// Reached a pause point, but rule failed. Step past and continue parsing string
					char pauseCharacter = currentCharacter;
					if( pauseCharacters != null && pauseCharacters.Contains(pauseCharacter) ) {
						parsedString.Append(pauseCharacter);
                        if( pauseCharacter == '\n' ) {
                            lineIndex++;
                            characterInLineIndex = -1;
                        }
						index++;
                        characterInLineIndex++;
						continue;
					} else {
						break;
					}
				}

			} while(true);

			if (parsedString.Length > 0) {
                return (string) SucceedRule (ruleId, parsedString.ToString ());
			} else {
                return (string) FailRule (ruleId);
			}

		}

        // No need to Begin/End rule since we never parse a newline, so keeping oldIndex is good enough
		public int? ParseInt()
		{
			int oldIndex = index;
            int oldCharacterInLineIndex = characterInLineIndex;

			bool negative = ParseString ("-") != null;

			// Optional whitespace
			ParseCharactersFromString (" \t");

			var parsedString = ParseCharactersFromCharSet (numbersCharacterSet);
            if(parsedString == null) {
                // Roll back and fail
                index = oldIndex;
                characterInLineIndex = oldCharacterInLineIndex;
                return null;
            }

			int parsedInt;
			if (int.TryParse (parsedString, out parsedInt)) {
				return negative ? -parsedInt : parsedInt;
			}

            Error("Failed to read integer value: " + parsedString + ". Perhaps it's out of the range of acceptable numbers ink supports? (" + int.MinValue + " to " + int.MaxValue + ")");
            return null;
        }

        // No need to Begin/End rule since we never parse a newline, so keeping oldIndex is good enough
        public float? ParseFloat()
        {
            int oldIndex = index;
            int oldCharacterInLineIndex = characterInLineIndex;

            int? leadingInt = ParseInt ();
            if (leadingInt != null) {

                if (ParseString (".") != null) {

                    var afterDecimalPointStr = ParseCharactersFromCharSet (numbersCharacterSet);
                    return float.Parse (leadingInt+"." + afterDecimalPointStr, System.Globalization.CultureInfo.InvariantCulture);
                }
            }

            // Roll back and fail
            index = oldIndex;
            characterInLineIndex = oldCharacterInLineIndex;
            return null;
        }

        // You probably want "endOfLine", since it handles endOfFile too.
        protected string ParseNewline()
        {
            int ruleId = BeginRule();

            // Optional \r, definite \n to support Windows (\r\n) and Mac/Unix (\n)
            // 2nd May 2016: Always collapse \r\n to just \n
            ParseString ("\r");

            if( ParseString ("\n") == null ) {
                return (string) FailRule(ruleId);
            } else {
                return (string) SucceedRule(ruleId, "\n");
            }
        }

		private char[] _chars;
	}
}

using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Reflection;
using Sandbox;
using System.Collections.Generic;
using System.Linq;

namespace Editor;

public static class SoundAddons
{
	private static string previousSoundGenerationPromp;
	private static string previousSoundGenerationDuration;

	// [ContextMenuFor( typeof( SoundFile ), "Generate sound", "create" )]
	public static void Generate( SoundEvent soundEvent, Action<SoundFile> finished )
	{
		string defaultInput = !string.IsNullOrEmpty( previousSoundGenerationPromp )
			? previousSoundGenerationPromp
			: "A dog barking";

		Dialog.AskString( ( description ) =>
		{
			previousSoundGenerationPromp = description;

			Dialog.AskString( async ( durationStr ) =>
			{
				previousSoundGenerationDuration = durationStr;
				if ( !float.TryParse( durationStr, out float duration ) || duration < 0.5f || duration > 22f )
				{
					Log.Error( "Duration must be between 0.5 and 22 seconds" );
					return;
				}

				try
				{
					string apiKey = SoundSettings.Settings.ElevenLabsApiKey;
					var client = new HttpClient();
					client.DefaultRequestHeaders.Add( "xi-api-key", apiKey );

					var content = new StringContent( JsonSerializer.Serialize( new
					{
						text = description,
						duration_seconds = duration,
						prompt_influence = 0.3
					} ), Encoding.UTF8, "application/json" );

					var response = await client.PostAsync(
						"https://api.elevenlabs.io/v1/sound-generation",
						content
					);

					if ( response.IsSuccessStatusCode )
					{
						var bytes = await response.Content.ReadAsByteArrayAsync();

						string safeFileName = description.Replace( " ", "_" ).Replace( "/", "_" ).Replace( "\\", "_" );
						var mp3Path = Path.Combine(
							SoundSettings.Settings.GenerationPath,
							$"{safeFileName}_{DateTime.Now:yyyyMMddHHmmss}.mp3"
						);

						var fullMp3Path = Path.Combine( Project.Current.GetAssetsPath(), mp3Path );
						Directory.CreateDirectory( Path.GetDirectoryName( fullMp3Path ) );

						await File.WriteAllBytesAsync( fullMp3Path, bytes );
						Log.Info( "File saved to: " + fullMp3Path );

						var mp3Asset = AssetSystem.RegisterFile( fullMp3Path );
						if ( mp3Asset != null )
						{
							var soundFile = SoundFile.Load( mp3Asset.RelativePath );
							if ( soundFile != null )
							{
								finished(soundFile);
								Log.Info( $"Successfully generated and linked sound: {mp3Asset.RelativePath}" );
							}
							else
							{
								Log.Error( "Failed to load generated sound file" );
							}
						}
						else
						{
							Log.Error( "Failed to register generated mp3 file as asset" );
						}
					}
					else
					{
						var errorContent = await response.Content.ReadAsStringAsync();
						Log.Error( $"Failed to generate sound: {response.StatusCode}. Error: {errorContent}" );
					}
				}
				catch ( Exception ex )
				{
					Log.Error( $"Error generating sound: {ex.Message}" );
				}
			},
			"Enter duration in seconds (0.5 to 22):",
			"Generate",
			"Cancel",
			previousSoundGenerationDuration,
			"Set Sound Duration" );
		},
		"Describe the sound effect to generate:",
		"Next",
		"Cancel",
		defaultInput,
		"Generate Sound Effect" );
	}

	// [ContextMenuFor( typeof( SoundFile ), "Split sound", "content_cut" )]
	public static async void SplitSound( Widget parent, SerializedProperty property )
	{
		var soundResource = property.GetValue<SoundFile>();
		if ( soundResource == null || !soundResource.IsValid )
		{
			Log.Error( "No valid sound file selected" );
			return;
		}

		var dialog = new Dialog();
		dialog.Window.Title = "Split Sound";
		dialog.Window.Size = new Vector2( 800, 400 );

		dialog.Layout = Layout.Column();
		var mainLayout = dialog.Layout.AddColumn();
		mainLayout.Margin = 16f;

		var loadingLabel = new Label( "Loading audio data..." );
		mainLayout.Add( loadingLabel );

		dialog.Show();

		var samples = await soundResource.GetSamplesAsync();
		if ( samples == null )
		{
			loadingLabel.Text = "Failed to load audio samples";
			return;
		}

		loadingLabel.Destroy();

		var spectogramWidget = new SpectogramWidget( soundResource );
		mainLayout.Add( spectogramWidget, 1 );

		var controlsLayout = mainLayout.AddRow();

		var splitButton = new Button.Primary( "Split", "content_cut" );
		controlsLayout.Add( splitButton );

		splitButton.Clicked = () =>
		{
			var splitPoints = spectogramWidget.GetSplitPoints();
			if ( splitPoints.Count < 1 ) return;

			var listControlWidget = parent.GetAncestor<ListControlWidget>();
			if ( listControlWidget == null )
			{
				Log.Error( "Could not find ListControlWidget parent" );
				return;
			}

			try
			{
				var baseFileName = Path.GetFileNameWithoutExtension( soundResource.ResourcePath );
				var outputDir = Path.Combine(
					Project.Current.GetAssetsPath(),
					"generated",
					$"{baseFileName}_splits"
				);
				Directory.CreateDirectory( outputDir );

				var newSoundFiles = new List<SoundFile>();

				for ( int i = 0; i < splitPoints.Count - 1; i++ )
				{
					var start = splitPoints[i];
					var end = splitPoints[i + 1];
					var length = end - start;

					var segmentSamples = new short[length];
					Array.Copy( samples, start, segmentSamples, 0, length );

					var wavPath = Path.Combine( outputDir, $"{baseFileName}_part_{i + 1}.wav" );

					using ( var writer = new BinaryWriter( File.Create( wavPath ) ) )
					{
						writer.Write( Encoding.ASCII.GetBytes( "RIFF" ) );
						writer.Write( 36 + (segmentSamples.Length * 2) );
						writer.Write( Encoding.ASCII.GetBytes( "WAVE" ) );

						writer.Write( Encoding.ASCII.GetBytes( "fmt " ) );
						writer.Write( 16 );
						writer.Write( (short)1 );
						writer.Write( (short)soundResource.Channels );
						writer.Write( soundResource.Rate );
						writer.Write( soundResource.Rate * soundResource.Channels * 2 );
						writer.Write( (short)(soundResource.Channels * 2) );
						writer.Write( (short)16 );

						writer.Write( Encoding.ASCII.GetBytes( "data" ) );
						writer.Write( segmentSamples.Length * 2 );

						foreach ( var sample in segmentSamples )
						{
							writer.Write( sample );
						}
					}

					var asset = AssetSystem.RegisterFile( wavPath );
					if ( asset != null )
					{
						var soundFile = SoundFile.Load( asset.RelativePath );
						if ( soundFile != null )
						{
							newSoundFiles.Add( soundFile );
						}
					}
				}

				if ( newSoundFiles.Count > 0 )
				{
					try
					{
						var collectionField = listControlWidget.GetType()
							.GetFields( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public )
							.FirstOrDefault( f => typeof( SerializedCollection ).IsAssignableFrom( f.FieldType ) );

						if ( collectionField == null )
						{
							return;
						}

						var collection = collectionField.GetValue( listControlWidget ) as SerializedCollection;
						if ( collection == null )
						{
							return;
						}

						if ( property.TryGetAsObject( out var original ) )
						{
							foreach ( var soundFile in newSoundFiles )
							{
								collection.Add( soundFile );
							}

							collection.Remove( property );
						}
					}
					catch ( Exception ex )
					{
						Log.Error( $"Error manipulating collection: {ex.Message}" );
						Log.Error( $"Stack trace: {ex.StackTrace}" );
					}
				}

				dialog.Close();
			}
			catch ( Exception ex )
			{
				Log.Error( $"Error splitting sound: {ex.Message}" );
			}
		};
	}
}
using Sandbox;
using Sandbox.Internal;
using System.Diagnostics;
using System.IO;

public static class MetricsEditor
{
	public static string packageFolders => $"{Project.Current.Package.Org.Ident}/{Project.Current.Package.Ident}";
	public static string dataFolder => Editor.FileSystem.Root.GetFullPath( $"/data/{packageFolders}" );

	public static bool enabled
	{
		get
		{
			return ConsoleSystem.GetValue( "manic_metrics_enabled" ).ToBool();
		}
		set
		{
			ConsoleSystem.SetValue( "manic_metrics_enabled", value ? 1 : 0 );
		}
	}

	public static bool debugLogging
	{
		get
		{
			return ConsoleSystem.GetValue( "manic_metrics_logging" ).ToBool();
		}
		set
		{
			ConsoleSystem.SetValue( "manic_metrics_logging", value ? 1 : 0 );
		}
	}
}
using System;
using System.Linq;
using System.Reflection;
using Editor;
using Editor.Experimental;
using Editor.Inspectors;
using Sandbox;
using Sandbox.UI;
using Button = Editor.Button;

namespace ResourceEmbed.Editor;

[CustomEditor(typeof(GameResource), WithAllAttributes = new[] { typeof(EmbedAttribute) })]
public class EmbeddedResourceEditorWidget : ControlWidget
{
	public override bool SupportsMultiEdit => false;

	private Widget resourceProperty;
	private IconButton dropDownButton;
	private TrimmedAssetInspector inspector;
	private Guid GameObjectId;
	
	string ExpandedCookieString => $"embed_expand.{GameObjectId}.{SerializedProperty.Name}";

	/// <summary>
	/// The user's local preference to having this component expanded or not.
	/// </summary>
	bool ExpandedCookie
	{
		get => ProjectCookie.Get( ExpandedCookieString, true );
		set
		{
			// Don't bother storing the cookie if it's an expanded component
			if ( value )
			{
				ProjectCookie.Remove( ExpandedCookieString );
			}
			else
			{
				ProjectCookie.Set( ExpandedCookieString, value );
			}
		}
	}

	/// <summary>
	/// Is this component currently expanded?
	/// </summary>
	internal bool Expanded { get; set; } = true;

	/// <summary>
	/// Expands/shrinks the component in the component list.
	/// </summary>
	/// <param name="expanded"></param>
	internal void SetExpanded( bool expanded )
	{
		if (expanded)
			Layout.Margin = new Margin(0, 0, 0, 5);
		else
			Layout.Margin = new Margin(0, 0, 0, 0);
		
		Expanded = expanded;
		RebuildContent();
		ExpandedCookie = expanded; 
		dropDownButton.Icon = expanded ? "arrow_drop_down" : "arrow_left";
	}

	public EmbeddedResourceEditorWidget(SerializedProperty property) : base(property)
	{
		GameObjectId = property.Parent.Targets.OfType<Component>().Select( x => x.Id ).Single();
		
		Expanded = ExpandedCookie;
		
		Layout = Layout.Column();
		Layout.Spacing = 2;
		
		resourceProperty = CanEditAttribute.CreateEditorFor(property.PropertyType);
		resourceProperty.Layout = Layout.Row();
		resourceProperty.Layout.Alignment = TextFlag.Right;
		Layout.Add( resourceProperty );
		
		resourceProperty.Bind("Value").From(() =>
			{
				return SerializedProperty.GetValue<Resource>();
			},
			x =>
			{
				var oldValue = SerializedProperty.GetValue<Resource>();
				SerializedProperty.SetValue(x);

				if (x is null || oldValue is null)
				{
					RebuildContent();
					return;
				}

				if (inspector is not null)
				{
					inspector.Asset = AssetSystem.FindByPath( x.ResourcePath );
					inspector.RebuildUI();
				}
			});

		RebuildContent();
	}
	
	private void RebuildContent()
	{
		dropDownButton?.Destroy();
		inspector?.Destroy();
		
		var resource = SerializedProperty.GetValue<Resource>();
		
		if (resource is null)
			return;
		
		dropDownButton = new IconButton("expand_more", () => SetExpanded(!Expanded), resourceProperty);
		dropDownButton.FixedSize = resourceProperty.MinimumHeight;
		dropDownButton.IconSize = resourceProperty.MinimumHeight;
		resourceProperty.Layout.Add(dropDownButton); 
		
		var asset = AssetSystem.FindByPath(resource.ResourcePath);
		
		inspector = new TrimmedAssetInspector( asset.GetSerialized() );
		
		if (!Expanded)
			return;
		
		Layout.Add( inspector );
	}
}
using System.Linq;
using System.Threading.Tasks;
using Editor;
using Sandbox;
using Button = Editor.Button;
using Label = Editor.Label;

namespace LibraryPlus;

internal class LibraryDetail : Widget
{
	/// <summary>
	///     The "install" button
	/// </summary>
	private Button ActionButton;

	/// <summary>
	///     If this is installed, this will be the installed project
	/// </summary>
	private Library Installed;

	/// <summary>
	///     The package holding the description for this library
	/// </summary>
	private Package Package;

	/// <summary>
	///     The revision currently selected in the version list
	/// </summary>
	private Package.IRevision SelectedRevision;

	/// <summary>
	///     The "uninstall" button
	/// </summary>
	private Button UninstallButton;

	/// <summary>
	///     List of versions
	/// </summary>
	private ComboBox VersionList;

	public LibraryDetail( Package package )
	{
		Package = package;

		Layout = Layout.Column();
		Layout.Margin = 8;
		Layout.Spacing = 4;

		_ = FetchAndBuild();
	}

	private async Task FetchAndBuild()
	{
		if ( !this.IsValid() )
		{
			return;
		}

		Layout.Clear( true );

		if ( !Package.TryParseIdent( Package.FullIdent, out var ident ) )
		{
			return;
		}

		Installed = LibrarySystemPlus.All.FirstOrDefault( x =>
			x.Package.ReferenceIdent() == Package.ReferenceIdent() );

		// do we have this package installed? what is the version?

		if ( ident.org != "local" )
		{
			Package = await Package.FetchAsync( Package.ReferenceIdent(), false ) ?? Package;
			if ( !this.IsValid() )
			{
				return;
			}
		}

		// Header, todo, icon and title
		var titleLayout = Layout.Row();
		titleLayout.Alignment = TextFlag.Left;
		titleLayout.Spacing = 4;
		titleLayout.Add( new Label( Package.Title ) );

		if ( Package.Org.Ident != "local" )
		{
			// org icon and title
			titleLayout.Add( new Label.Small( $"by {Package.Org.Title}" ), 1 );
		}

		Layout.Add( titleLayout );

		Layout.Add( new Label.Small( Package.ReferenceIdent() ) );

		// all of the versions
		VersionList = new ComboBox( this );
		Layout.Add( VersionList );

		{
			ActionButton = new Button( "Install", this ) { Pressed = () => _ = OnActionPressed() };
			Layout.Add( ActionButton );
			ActionButton.Hide();
		}

		{
			UninstallButton = new Button( "Remove Library", "remove" )
			{
				Pressed = () => Dialog.AskConfirm( OnUninstall, $"Are you sure you want to remove {Package.Title}?",
					"Remove Library", "Uninstall" )
			};
			Layout.Add( UninstallButton );
			UninstallButton.Visible = Installed != null;
		}

		if ( Installed != null )
		{
			Layout.AddSpacingCell( 10 );
			var referenceLayout = Layout.Grid();
			referenceLayout.AddCell( 0, 0, new Label( "Reference" ) );
			referenceLayout.AddCell( 1, 0, new Label( "Version" ) );
			foreach ( var reference in Installed.Config.LibraryReferences.Select( ( entry, index ) =>
				         new { entry, index = index + 1 } ) )
			{
				referenceLayout.AddCell( 0, reference.index, new Label( reference.entry.Key ) );
				referenceLayout.AddCell( 1, reference.index, new Label(
					$">= {reference.entry.Value.Min}{(reference.entry.Value.Max != null ? $", <= {reference.entry.Value.Min}" : "")}" ) );
				var actionLayout = referenceLayout.AddCell( 2, reference.index, Layout.Row() );
				var editButton = actionLayout.Add( new IconButton( "edit" )
				{
					OnClick = () => ReferenceDialog.Open( "Edit Reference",
						data => LibrarySystemPlus.AddReference( Installed, data ),
						(reference.entry.Key, reference.entry.Value.Min, reference.entry.Value.Max) )
				} );
				editButton.ToolTip = "Edit Reference";
				var removeButton = actionLayout.Add( new IconButton( "remove" )
				{
					OnClick = () => Dialog.AskConfirm(
						() => LibrarySystemPlus.RemoveReference( Installed, reference.entry.Key ),
						$"Are you sure you want to remove {reference.entry.Key}?",
						"Remove Reference", "Remove" )
				} );
				removeButton.ToolTip = "Remove Reference";
			}

			var addButton = new Button( "Add Reference", "add" )
			{
				Pressed = () => ReferenceDialog.Open( "Add Reference",
					data => LibrarySystemPlus.AddReference( Installed, data ), "Add" )
			};
			referenceLayout.AddCell( 0, Installed.Config.LibraryReferences.Count + 1, addButton
				, 3 );

			Layout.Add( referenceLayout );
		}

		// information about this
		Layout.Add( new Label( Package.Description ) );

		Layout.AddStretchCell();

		if ( ident.org == "local" )
		{
			VersionList.Hide();
			return;
		}

		var versions = await Package.FetchVersions( $"{ident.org}.{ident.package}" );
		if ( !this.IsValid() )
		{
			return;
		}

		if ( versions != null )
		{
			if ( Installed != null )
			{
				SelectedRevision = versions.FirstOrDefault( x => x.VersionId == Installed.Version.Build );
			}

			SelectedRevision ??= versions.FirstOrDefault();

			foreach ( var v in versions )
			{
				var version = v;
				var subtitle = Installed != null && Installed.Version.Build == version.VersionId
					? " (Installed)"
					: "";
				VersionList.AddItem( $"{v.Created.DateTime} - {v.VersionId}{subtitle}", null,
					() => OnVersionSelected( v ), null, SelectedRevision == version );
			}

			OnVersionSelected( SelectedRevision );
		}
	}

	private void OnVersionSelected( Package.IRevision revision )
	{
		SelectedRevision = revision;
		// update action button - install/uninstall/update

		// ActionButton

		// Is this fucker installed?
		if ( Installed != null )
		{
			// is this the installed version?
			if ( revision.VersionId == Installed.Version.Build )
			{
				ActionButton.Hide();
			}
			else
			{
				ActionButton.Show();
				ActionButton.Icon = "get_app";
				ActionButton.Text = $"Update to {revision.VersionId}";
			}
		}
		else
		{
			ActionButton.Show();
			ActionButton.Icon = "get_app";
			ActionButton.Text = "Install";
		}
	}

	private async Task OnActionPressed()
	{
		LibrarySystemPlus.Install( Package.FullIdent, SelectedRevision.VersionId );

		// window closed
		if ( !IsValid )
		{
			return;
		}

		await FetchAndBuild();
	}

	private void OnUninstall()
	{
		UninstallButton.Enabled = false;
		Installed.Remove();
		_ = FetchAndBuild();
	}

	protected override Vector2 SizeHint()
	{
		return new Vector2( 300, 100 );
	}

	[EditorEvent.Frame]
	public void Frame()
	{
		if ( !Visible )
		{
			return;
		}

		SetContentHash( Installed?.GetHashCode() ?? 0 );
	}
}
using Sandbox;
using Editor;

public class ReconnecterBar : ToolbarGroup
{
	[Event("tools.headerbar.build", Priority = 150)]
	public static void OnBuildHeaderToolbar(HeadBarEvent e)
	{
		e.RightCenter.Add(new ReconnecterBar(null));
		e.RightCenter.AddSpacingCell(8);
	}

	public ReconnecterBar(Widget parent) : base(parent, "Reconnecter", null)
	{
		ToolTip = "Auto Reconnect Clients";
	}

	public override void Build()
	{
		AddToggleButton("Auto Reconnect Clients", "autorenew", () => ReconnecterEditor.autoReconnectEnabled, SetAutoReconnect);
		AddToggleButton("Allow Instance Launching", "person_add", () => ReconnecterEditor.allowLaunchInstance, SetAllowLaunchInstance);
		AddButton("Force Reconnect Clients", "group", ForceAutoReconnect);
	}

	public void SetAutoReconnect(bool enabled)
	{
		ReconnecterEditor.autoReconnectEnabled = enabled;
	}

	public void SetAllowLaunchInstance(bool enabled)
	{
		ReconnecterEditor.allowLaunchInstance = enabled;
	}

	public void ForceAutoReconnect()
	{
		ReconnecterEditor.CreateSessionText(true);
	}
}
using Sandbox;
using Sandbox.Internal;
using System.Diagnostics;
using System.IO;

public static class ReconnecterEditor
{
	public static string packageFolders => $"{Project.Current.Package.Org.Ident}/{Project.Current.Package.Ident}";
	public static string dataFolder => Editor.FileSystem.Root.GetFullPath($"/data/{packageFolders}");

	public static bool autoReconnectEnabled
	{
		get
		{
			return GlobalToolsNamespace.EditorCookie.Get<bool>("reconnecter_enabled", true);
		}
		set
		{
			GlobalToolsNamespace.EditorCookie.Set<bool>("reconnecter_enabled", value);
		}
	}
	public static bool allowLaunchInstance
	{
		get
		{
			return GlobalToolsNamespace.EditorCookie.Get<bool>("reconnecter_allowLaunchInstance", true);
		}
		set
		{
			GlobalToolsNamespace.EditorCookie.Set<bool>("reconnecter_allowLaunchInstance", value);
		}
	}

	static ReconnecterEditor()
	{
		ReconnecterSystem.RegisterOnRequestWriteSession(CreateSessionText);
	}

	public static void CreateSessionText(bool force = false)
	{
		if (!autoReconnectEnabled && !force)
		{
			return;
		}

		string filePath = $"{dataFolder}/{ReconnecterSystem.SESSION_FILE_PATH}";

		string destinationDirectory = Path.GetDirectoryName(filePath);
		if (!Directory.Exists(destinationDirectory))
		{
			Directory.CreateDirectory(destinationDirectory);
		}

		File.WriteAllText(filePath, System.DateTime.UtcNow.ToString());
		//Log.Info($"CreateSessionText() filePath: {filePath}");
	}

	[Event("scene.play", Priority = int.MinValue)]
	public static void ScenePlay()
	{		
		ReconnecterSystem.OnPlayInEditor();

		if (!autoReconnectEnabled || !allowLaunchInstance)
		{
			return;
		}

		Process[] processes = Process.GetProcessesByName("sbox");
		bool isProcessRunning = processes.Length > 0;

		if (isProcessRunning)
		{
			return;
		}
		SpawnProcess();
	}

	public static void SpawnProcess()
	{
		var p = new Process();
		p.StartInfo.FileName = "sbox.exe";
		p.StartInfo.WorkingDirectory = System.Environment.CurrentDirectory;
		p.StartInfo.CreateNoWindow = true;
		p.StartInfo.RedirectStandardOutput = true;
		p.StartInfo.RedirectStandardError = true;
		p.StartInfo.UseShellExecute = false;

		p.StartInfo.ArgumentList.Add("-joinlocal");

		// This doesn't seem to work because it doesn't use Steam's Launch Options?
		p.StartInfo.ArgumentList.Add("-sw");

		p.Start();
	}
}
using Sandbox;
namespace Editor;

[GameResource( "Motivation", "motivate", "Citizens motivate you every 15-30 minutes.", Icon = "support_agent", Category = "Editor", IconBgColor = "#E4E2E4", IconFgColor = "#93BDDD" )]
public class MotivationResource : GameResource
{
	[ImageAssetPath]
	public string[] Portraits { get; set; }

	public string[] Messages { get; set; }

	/// <summary>
	/// Selects a random citizen portrait from this type.
	/// </summary>
	/// <returns>Portrait file path</returns>
	public string GetPortrait()
	{
		var portraitPath = Game.Random.FromArray( Portraits );

		return FileSystem.Mounted.GetFullPath( portraitPath );
	}

	/// <summary>
	/// Selects a random motivational response from this type.
	/// </summary>
	/// <returns>Response with linebreaks</returns>
	public string GetMessage()
	{
		return Game.Random.FromArray( Messages );
	}
}
using Sandbox;
using Editor;
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox.UI;

namespace SFXR.Editor;

public class SFXRNoteSheet : Widget
{
    public SFXRNoteSheet( SerializedObject obj ) : base( null )
    {
        var propertySheet = new PropertyControlSheet();
        propertySheet.AddObject( obj );

        Layout = Layout.Column();
        Layout.Add( propertySheet );
    }

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

        Color color = Color.FromRgb( 0x111111 );
        Paint.SetBrush( color );
        Paint.SetPen( color );

        Paint.DrawRect( new Rect( 0, 0, Width, Height ), 2 );
    }

}

//[CustomEditor( typeof( SFXRFloat ) )]
// public class SFXRFloatControlWidget : ControlWidget
// {

//     SerializedObject Target;

//     public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
//     {
//         SetSizeMode( SizeMode.Ignore, SizeMode.Default );

//         if ( !property.TryGetAsObject( out Target ) )
//             return;

//         Layout = Layout.Row();
//         Layout.Spacing = 2;

//         var value = Target.GetProperty( "Value" );
//         FloatControlWidget valueWidget = new FloatControlWidget( value );

//         if ( property.TryGetAttribute<RangeAttribute>( out var attribute ) )
//         {
//             valueWidget.Range = new Vector2( attribute.Min, attribute.Max );
//             valueWidget.RangeStep = attribute.Step;
//             valueWidget.HasRange = attribute.Slider;
//             // if ( attribute.Slider )
//             // {
//             //     sliderWidget = new FloatSlider( this );
//             //     sliderWidget.HighlightColor = Theme.Grey;
//             //     sliderWidget.Minimum = attribute.Min;
//             //     sliderWidget.Maximum = attribute.Max;
//             //     sliderWidget.Step = attribute.S;
//             //     sliderWidget.OnValueEdited = delegate
//             //     {
//             //         base.SerializedProperty.As.Float = sliderWidget.Value;
//             //     };
//             // }
//         }

//         Layout.Add( valueWidget );

//         var locked = Target.GetProperty( "Locked" );
//         IconButton lockedButton = new IconButton( "lock_open" );
//         lockedButton.OnClick = () =>
//         {
//             if ( Target.Targets.First() is SFXRFloat sfxrFloat )
//             {
//                 sfxrFloat.Locked = !sfxrFloat.Locked;
//                 Log.Info( sfxrFloat.Locked );
//                 lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
//                 lockedButton.Update();
//             }
//         };
//         lockedButton.ToolTip = "Lock/Unlock this value";
//         Layout.Add( lockedButton );
//     }

//     protected override void OnPaint()
//     {

//     }

// }

// public class SFXRFloatControlWidget : FloatControlWidget
// {

//     SerializedObject Target;

//     public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
//     {
//         if ( !property.TryGetAsObject( out Target ) )
//             return;

//         var locked = Target.GetProperty( "Locked" );
//         IconButton lockedButton = new IconButton( "lock_open" );
//         lockedButton.OnClick = () =>
//         {
//             // if ( Target.Targets.First() is SFXRFloat sfxrFloat )
//             // {
//             //     sfxrFloat.Locked = !sfxrFloat.Locked;
//             //     Log.Info( sfxrFloat.Locked );
//             //     lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
//             //     lockedButton.Update();
//             // }
//         };
//         lockedButton.ToolTip = "Lock/Unlock this value";
//         //Layout.Add( lockedButton );
//     }

//     protected override void PaintControl()
//     {
//         if ( Target is null ) return;

//         base.PaintControl();
//     }

//     protected override void OnPaint()
//     {
//         if ( Target is null ) return;

//         base.OnPaint();
//     }
// }
using Editor;

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

namespace Editor;

public static class MotivationManager
{
	static RealTimeUntil Cooldown;

	static MotivationManager()
	{
		Game.SetRandomSeed( DateTime.Now.Second );
		Cooldown = 5;
	}

	private static bool _hasMotivation => NoticeManager.All.FirstOrDefault( x => x is MotivationNotice ) != null;

	private static SoundFile sound = SoundFile.Load("sounds/baka.wav");

	[EditorEvent.FrameAttribute]
	public static void Frame()
	{
		if ( NoticeManager.All.Any( x => x.GetType().ToString() == "Editor.CodeCompileNotice" && x is NoticeWidget widget && widget.BorderColor == Theme.Red ) )
		{
			ShowBaka();
		}
		else
		{
			HideBaka();
		}
	}
	
	private static void ShowBaka()
	{
		if (_hasMotivation) return;
		EditorUtility.PlayRawSound( "sounds/baka.wav" );
		var s = new MotivationNotice();
		NoticeManager.Remove( s, 30 );
	}
	
	private static void HideBaka()
	{
		if ( _hasMotivation )
		{
			NoticeManager.Remove( NoticeManager.All.FirstOrDefault( x => x is MotivationNotice ) );
		}
	}
	
}
using System.Collections.Generic;
using System.Linq;
using Editor;
using Editor.ActionGraphs;
using Editor.Widgets;
using Microsoft.VisualBasic;
using Sandbox;

[CustomEditor( typeof( WebsocketTools ) )]
public class WebsocketToolsControlWidget : ControlWidget
{
	public WebsocketToolsControlWidget( SerializedProperty property ) : base( property )
	{
		Layout = Layout.Column();
		if ( property.IsNull )
		{
			property.SetValue( new WebsocketTools() );
		}

		var serializedObject = property.GetValue<WebsocketTools>()?.GetSerialized();
		if ( serializedObject is null ) return;


		var controlSheet = new ControlSheet();
		controlSheet.AddObject( serializedObject );
		Layout.Add( controlSheet );
	}
}
/*
[CustomEditor( typeof( JsonTags ) )]
public class JsonTagsControlWidget : ControlWidget
{
	public JsonTagsControlWidget( SerializedProperty property ) : base( property )
	{
		Layout = Layout.Column();
		PaintBackground = false;
		if ( property.IsNull )
		{
			property.SetValue( new JsonTags() );
		}

		var serializedObject = property.GetValue<JsonTags>()?.GetSerialized();
		if ( serializedObject is null ) return;

		serializedObject.TryGetProperty( nameof( JsonTags.value ), out var value );
		serializedObject.TryGetProperty( nameof( JsonTags.tag ), out var tag );

		var controlSheet = new ControlSheet();
		controlSheet.AddRow( tag );
		controlSheet.AddRow( value );
		Layout.Add( controlSheet );
	}
}
*/
/*
[EditorForAssetType( "message" )]
public class WebsocketMessageEditor : DockWindow, IAssetEditor
{
	public WebsocketMessage Message;
	public Asset _asset;
	public WebsocketMessageEditor()
	{
		WindowTitle = "Websocket Message Editor";
		MinimumSize = new Vector2( 100, 100 );
		Size = new Vector2( 500, 500 );
		Log.Info( All );
	}

	public void AssetOpen( Asset asset )
	{
		Show();
		Log.Info( asset.AssetType );

		Open( asset.AbsolutePath, asset );
	}


	public void SelectMember( string memberName )
	{
		// Implement the logic to select a member here
	}

	public bool CanOpenMultipleAssets => true; // Or false, depending on your requirements

	protected override void RestoreDefaultDockLayout()
	{

	}

	public void Open( string path, Asset asset = null )
	{
		if ( !string.IsNullOrEmpty( path ) )
		{
			asset ??= AssetSystem.FindByPath( path );
		}
		if ( asset is null ) return;
		if ( asset == _asset )
		{
			Focus();
			return;
		}

		var message = asset.LoadResource<WebsocketMessage>();
		if ( message is null ) return;
		_asset = asset;
		Message = message;
		var mainWidget = new MainWidget( this );
		DockManager.AddDock( null, mainWidget, DockArea.Left, DockManager.DockProperty.HideOnClose );
	}

	public void Save()
	{
		if ( _asset is null ) return;
		Log.Info( "Saving" );
		_asset ??= AssetSystem.RegisterFile( _asset.AbsolutePath );
		_asset.SaveToDisk( Message );
	}
}

public class MainWidget : Widget
{
	public WebsocketMessageEditor Editor { get; set; }
	public List<Dictionary<string, string>> dictionaries = new();
	public List<DictionaryProperty<string, string>> dictionaryProperties = new();
	public int Tags { get; set; } = 0;
	public MainWidget( WebsocketMessageEditor editor ) : base( null )
	{
		Editor = editor;
		Name = "Message Editor";
		WindowTitle = "Message Editor";
		Layout = Layout.Column();
		MinimumWidth = 450f;

		var serializedObject = Editor.Message?.GetSerialized();
		if ( serializedObject is null ) return;
		serializedObject.TryGetProperty( nameof( WebsocketMessage.message ), out var message );

		var controlSheet = new ControlSheet();
		controlSheet.AddRow( message );
		Layout.Add( controlSheet );

		Layout.Add( new Label( "JSON Tags" ) );
		var jsonTagButton = new Button( "Add new JSON Tag" );
		jsonTagButton.Clicked += () =>
		{
			Tags++;

			var dictionary = new Dictionary<string, string>();
			var property = new DictionaryProperty<string, string>( this );

			property.Value = dictionary;
			property.Height = 30;
			property.Width = 300;

			property.SetProperty( "Key", "Tag" + Tags );

			dictionaryProperties.Add( property );
			dictionaries.Add( dictionary );

			Layout.Add( property );
		};

		Layout.Add( jsonTagButton );


		var saveButton = new Button( "Save" );
		saveButton.Clicked += () => Save();
		Layout.Add( saveButton );


	}
	private void Save()
	{
		Editor.Message.jsonTags = dictionaries;
		Editor.Save();
	}
}*/




using Ink.Parsed;
using System.Text;
using System.Collections.Generic;
using System.Linq;

namespace Ink
{
    public partial class InkParser
    {
        void TrimEndWhitespace(List<Parsed.Object> mixedTextAndLogicResults, bool terminateWithSpace)
        {
            // Trim whitespace from end
            if (mixedTextAndLogicResults.Count > 0) {
                var lastObjIdx = mixedTextAndLogicResults.Count - 1;
                var lastObj = mixedTextAndLogicResults[lastObjIdx];
                if (lastObj is Text) {
                    var text = (Text)lastObj;
                    text.text = text.text.TrimEnd (' ', '\t');

                    if (terminateWithSpace)
                        text.text += " ";

                    // No content left at all? trim the whole object
                    else if( text.text.Length == 0 ) {
                        mixedTextAndLogicResults.RemoveAt(lastObjIdx);

                        // Recurse in case there's more whitespace
                        TrimEndWhitespace(mixedTextAndLogicResults, terminateWithSpace:false);
                    }
                }
            }
        }

        protected List<Parsed.Object> LineOfMixedTextAndLogic()
        {
            // Consume any whitespace at the start of the line
            // (Except for escaped whitespace)
            Parse (Whitespace);

            var result = Parse(MixedTextAndLogic);
            if (result == null || result.Count == 0)
                return null;

            // Warn about accidentally writing "return" without "~"
            var firstText = result[0] as Text;
            if (firstText) {
                if (firstText.text.StartsWith ("return")) {
                    Warning ("Do you need a '~' before 'return'? If not, perhaps use a glue: <> (since it's lowercase) or rewrite somehow?");
                }
            }
            if (result.Count == 0)
                return null;

            var lastObj = result [result.Count - 1];
            if (!(lastObj is Divert)) {
                TrimEndWhitespace (result, terminateWithSpace:false);
            }

            EndTagIfNecessary(result);

            // If the line doens't actually contain any normal text content
            // but is in fact entirely a tag, then let's not append
            // a newline, since we want the tag (or tags) to be associated
            // with the line below rather than being completely independent.
            bool lineIsPureTag = result.Count > 0 && result[0] is Parsed.Tag && ((Parsed.Tag)result[0]).isStart;
            if( !lineIsPureTag )
                result.Add (new Text ("\n"));

            Expect(EndOfLine, "end of line", recoveryRule: SkipToNextLine);

            return result;
        }

        protected List<Parsed.Object> MixedTextAndLogic()
        {
            // Check for disallowed "~" within this context
            var disallowedTilda = ParseObject(Spaced(String("~")));
            if (disallowedTilda != null)
                Error ("You shouldn't use a '~' here - tildas are for logic that's on its own line. To do inline logic, use { curly braces } instead");

            // Either, or both interleaved
            var results = Interleave<Parsed.Object>(Optional (ContentText), Optional (InlineLogicOrGlueOrStartTag));

            // Terminating divert?
            // (When parsing content for the text of a choice, diverts aren't allowed.
            //  The divert on the end of the body of a choice is handled specially.)
            if (!_parsingChoice) {

                var diverts = Parse (MultiDivert);
                if (diverts != null) {

                    // May not have had any results at all if there's *only* a divert!
                    if (results == null)
                        results = new List<Parsed.Object> ();

                    // End previously active tag if necessary
                    EndTagIfNecessary(results);

                    TrimEndWhitespace (results, terminateWithSpace:true);

                    results.AddRange (diverts);
                }

            }
                
            if (results == null)
                return null;

            return results;
        }

        protected Parsed.Text ContentText()
        {
            return ContentTextAllowingEcapeChar ();
        }

        protected Parsed.Text ContentTextAllowingEcapeChar()
        {
            StringBuilder sb = null;

            do {
                var str = Parse(ContentTextNoEscape);
                bool gotEscapeChar = ParseString(@"\") != null;

                if( gotEscapeChar || str != null ) {
                    if( sb == null ) {
                        sb = new StringBuilder();
                    }

                    if( str != null ) {
                        sb.Append(str);
                    }

                    if( gotEscapeChar ) {
                        char c = ParseSingleCharacter();
                        sb.Append(c);
                    }

                } else {
                    break;
                }

            } while(true);

            if (sb != null ) {
                return new Parsed.Text (sb.ToString ());

            } else {
                return null;
            }
        }

        // Content text is an unusual parse rule compared with most since it's
        // less about saying "this is is the small selection of stuff that we parse"
        // and more "we parse ANYTHING except this small selection of stuff".
        protected string ContentTextNoEscape()
        {
            // Eat through text, pausing at the following characters, and
            // attempt to parse the nonTextRule.
            // "-": possible start of divert or start of gather
            // "<": possible start of glue
            if (_nonTextPauseCharacters == null) {
                _nonTextPauseCharacters = new CharacterSet ("-<");
            }

            // If we hit any of these characters, we stop *immediately* without bothering to even check the nonTextRule
            // "{" for start of logic
            // "|" for mid logic branch
            if (_nonTextEndCharacters == null) {
                _nonTextEndCharacters = new CharacterSet ("{}|\n\r\\#");
                _notTextEndCharactersChoice = new CharacterSet (_nonTextEndCharacters);
                _notTextEndCharactersChoice.AddCharacters ("[]");
                _notTextEndCharactersString = new CharacterSet (_nonTextEndCharacters);
                _notTextEndCharactersString.AddCharacters ("\"");
            }

            // When the ParseUntil pauses, check these rules in case they evaluate successfully
            ParseRule nonTextRule = () => OneOf (ParseDivertArrow, ParseThreadArrow, EndOfLine, Glue);

            CharacterSet endChars = null;
            if (parsingStringExpression) {
                endChars = _notTextEndCharactersString;
            } 
            else if (_parsingChoice) {
                endChars = _notTextEndCharactersChoice;
            } 
            else {
                endChars = _nonTextEndCharacters;
            }

            string pureTextContent = ParseUntil (nonTextRule, _nonTextPauseCharacters, endChars);
            if (pureTextContent != null ) {
                return pureTextContent;

            } else {
                return null;
            }

        }

        CharacterSet _nonTextPauseCharacters;
        CharacterSet _nonTextEndCharacters;
        CharacterSet _notTextEndCharactersChoice;
        CharacterSet _notTextEndCharactersString;



    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using Ink.Parsed;

namespace Ink
{
	public partial class InkParser
	{
		protected enum StatementLevel
		{
            InnerBlock,
			Stitch,
			Knot,
			Top
		}

		protected List<Parsed.Object> StatementsAtLevel(StatementLevel level)
		{
            // Check for error: Should not be allowed gather dashes within an inner block
            if (level == StatementLevel.InnerBlock) {
                object badGatherDashCount = Parse(GatherDashes);
                if (badGatherDashCount != null) {
                    Error ("You can't use a gather (the dashes) within the { curly braces } context. For multi-line sequences and conditions, you should only use one dash.");
                }
            }
                
			return Interleave<Parsed.Object>(
                Optional (MultilineWhitespace), 
                () => StatementAtLevel (level), 
                untilTerminator: () => StatementsBreakForLevel(level));
		}
            
        protected object StatementAtLevel(StatementLevel level)
        {            
            ParseRule[] rulesAtLevel = _statementRulesAtLevel[(int)level];

            var statement = OneOf (rulesAtLevel);

            // For some statements, allow them to parse, but create errors, since
            // writers may think they can use the statement, so it's useful to have 
            // the error message.
            if (level == StatementLevel.Top) {
                if( statement is Return ) 
                    Error ("should not have return statement outside of a knot");
            }

            return statement;
        }

        protected object StatementsBreakForLevel(StatementLevel level)
        {
            Whitespace ();

            ParseRule[] breakRules = _statementBreakRulesAtLevel[(int)level];

            var breakRuleResult = OneOf (breakRules);
            if (breakRuleResult == null)
                return null;

            return breakRuleResult;
        }

		void GenerateStatementLevelRules()
		{
            var levels = Enum.GetValues (typeof(StatementLevel)).Cast<StatementLevel> ().ToList();

            _statementRulesAtLevel = new ParseRule[levels.Count][];
            _statementBreakRulesAtLevel = new ParseRule[levels.Count][];

            foreach (var level in levels) {
                List<ParseRule> rulesAtLevel = new List<ParseRule> ();
                List<ParseRule> breakingRules = new List<ParseRule> ();

                // Diverts can go anywhere
                rulesAtLevel.Add(Line(MultiDivert));

                // Knots can only be parsed at Top/Global scope
                if (level >= StatementLevel.Top)
                    rulesAtLevel.Add (KnotDefinition);

                rulesAtLevel.Add(Line(Choice));

                rulesAtLevel.Add(Line(AuthorWarning));

                // Gather lines would be confused with multi-line block separators, like
                // within a multi-line if statement
                if (level > StatementLevel.InnerBlock) {
                    rulesAtLevel.Add (Gather);
                }

                // Stitches (and gathers) can (currently) only go in Knots and top level
                if (level >= StatementLevel.Knot) {
                    rulesAtLevel.Add (StitchDefinition);
                }

                // Global variable declarations can go anywhere
                rulesAtLevel.Add(Line(ListDeclaration));
                rulesAtLevel.Add(Line(VariableDeclaration));
                rulesAtLevel.Add(Line(ConstDeclaration));
                rulesAtLevel.Add(Line(ExternalDeclaration));

                // Global include can go anywhere
                rulesAtLevel.Add(Line(IncludeStatement));

                // Normal logic / text can go anywhere
                rulesAtLevel.Add(LogicLine);
                rulesAtLevel.Add(LineOfMixedTextAndLogic);

                // --------
                // Breaking rules

                // Break current knot with a new knot
                if (level <= StatementLevel.Knot) {
                    breakingRules.Add (KnotDeclaration);
                }

                // Break current stitch with a new stitch
                if (level <= StatementLevel.Stitch) {
                    breakingRules.Add (StitchDeclaration);
                }

                // Breaking an inner block (like a multi-line condition statement)
                if (level <= StatementLevel.InnerBlock) {
                    breakingRules.Add (ParseDashNotArrow);
                    breakingRules.Add (String ("}"));
                }

                _statementRulesAtLevel [(int)level] = rulesAtLevel.ToArray ();
                _statementBreakRulesAtLevel [(int)level] = breakingRules.ToArray ();
            }
		}

		protected object SkipToNextLine()
		{
			ParseUntilCharactersFromString ("\n\r");
			ParseNewline ();
			return ParseSuccess;
		}

		// Modifier to turn a rule into one that expects a newline on the end.
		// e.g. anywhere you can use "MixedTextAndLogic" as a rule, you can use 
		// "Line(MixedTextAndLogic)" to specify that it expects a newline afterwards.
		protected ParseRule Line(ParseRule inlineRule)
		{
			return () => {
				object result = ParseObject(inlineRule);
                if (result == null) {
                    return null;
                }

				Expect(EndOfLine, "end of line", recoveryRule: SkipToNextLine);

				return result;
			};
		}


        ParseRule[][] _statementRulesAtLevel;
        ParseRule[][] _statementBreakRulesAtLevel;
	}
}

using System;
using System.Linq;
using System.Text;
using Editor;
using NPBehave;
using Sandbox.Utils;

namespace Sandbox.BehaviorTreeVisualizer;

public class BehaviorTreeNode : TreeNode<Node>
{
	private Color _nodeColor;
	
	Color NodeColor
	{
		get => _nodeColor;
		set
		{
			if( _nodeColor == value )
				return;
			_nodeColor = value;
			Dirty();
		}
	}

	public BehaviorTreeNode( Node dir ) : base( dir )
	{
		Height = 40;
	}

	protected override void BuildChildren()
	{
		Clear();
		
		if ( Value is Container container )
		{
			foreach ( var child in container.DebugChildren )
			{
				AddItem( CreateChildFor( child ) );
			}
		}
	}

	protected virtual TreeNode CreateChildFor( Node child ) => new BehaviorTreeNode( child );


	public override void OnSelectionChanged( bool state )
	{
		if ( state )
		{
			TreeView?.Toggle( this );
		}
		base.OnSelectionChanged( state );
	}

	public override bool OnContextMenu()
	{
		var m = new Editor.Menu( TreeView );
		if ( Value.CurrentState == Node.State.Active )
		{
			m.AddOption( "Stop", action: () =>
			{
				Value.Stop();
			} );
		}
		else if(Value is Root root && Value.CurrentState == Node.State.Inactive )
		{
			m.AddOption( "Start", action: () => root.Start() );
		}
		
		m.OpenAtCursor( false );
		return true;
	}

	public override int GetHashCode()
	{
		return HashCode.Combine(Value.GetHashCode(), Value.CurrentState, Value.IsActive, Value.Name, Value.ComputedLabel) ;
	}

	public override int ValueHash => GetHashCode();

	protected override void Think()
	{
		Color newColor = Theme.Black;

		if ( Value.IsActive )
		{
			newColor = Theme.Green;
		}
		
		else if ( Value.DebugLastSuccessAt < 0.5f )
		{
			newColor = Theme.Green;
		}
		else if ( Value.DebugLastFailureAt < 0.5f )
		{
			newColor = Theme.Red;
		}
		
		NodeColor = newColor;
			
		
		base.Think();
	}

	protected override void RebuildOnDirty()
	{
		TreeView?.Update();
		if(Value is Container container && container.DebugChildren.Length != Children.Count() )
		{
			BuildChildren();
		}
		//base.RebuildOnDirty();
	}

	protected override void OnHashChanged()
	{
		Dirty();
		if(Value is Container container && container.DebugChildren.Length != Children.Count() )
		{
			BuildChildren();
		}
	}

	public override void OnPaint( VirtualWidget item )
	{
		var open = item.IsOpen;
		
		var backgroundRect = item.Rect;
		backgroundRect.Bottom -= 1;
		
		if ( item.Selected )
		{
			Paint.SetPen( Theme.Primary.WithAlpha( 0.9f ) );
			Paint.SetBrush( NodeColor.WithAlpha( 0.6f ) );
			Paint.DrawRect( backgroundRect.Shrink( 2 ) );
		}
		else if ( item.Hovered )
		{
			Paint.SetPen( Theme.Primary.WithAlpha( 0.9f ) );
			Paint.SetBrush( NodeColor.WithAlpha( 0.7f ) );
			Paint.DrawRect( backgroundRect.Shrink( 1 ) );
		}
		else
		{
			Paint.ClearPen();
			Paint.SetBrush( NodeColor.WithAlpha( 0.7f ) );
			Paint.DrawRect( backgroundRect );
		}
		
		var rect = backgroundRect.Shrink( 8 );

		if ( !string.IsNullOrWhiteSpace( Value.DebugIcon ) )
		{
			Paint.SetPen( Theme.Yellow.WithAlphaMultiplied( 1.0f) );
			var i = Paint.DrawIcon( rect, Value.DebugIcon, 22, TextFlag.LeftCenter );
			rect.Left = i.Right + 8;
		}


		Paint.SetPen( Theme.White.WithAlpha(1.0f));
		Paint.SetFont( "Poppins", 12, 450 );

		StringBuilder text = new StringBuilder();

		if ( !string.IsNullOrWhiteSpace( Value.Label ) )
		{
			text.Append( $" {Value.Label}" );
		}
		if( !string.IsNullOrWhiteSpace( Value.ComputedLabel ) )
		{
			text.Append( $" {Value.ComputedLabel}" );
		}
		if ( text.Length == 0 )
		{
			text.Append( Value.Name.AddSpacesToSentence( ) );
		}
		else
		{
			text.Append( $" ({Value.Name.AddSpacesToSentence( )})" );
		}
		
		
		var textRect = Paint.DrawText( rect, text.ToString(), TextFlag.LeftCenter );
	}
}
using System;
using System.IO;
using System.Text.Json;
using Editor;
using Sandbox;

public class SoundSettings : Widget
{
    public class Config : ConfigData
    {
        public string ElevenLabsApiKey { get; set; } = "";
        public string GenerationPath { get; set; } = "generated";
    }

    private LineEdit apiKeyInput;
    private LineEdit pathInput;
    private static Config _settings;
    private Button saveButton;

    public static Config Settings 
    {
        get
        {
            if (_settings == null)
                _settings = EditorUtility.LoadProjectSettings<Config>("settings.json");
            return _settings;
        }
    }

    public SoundSettings() : base(null)
    {
        Layout = Layout.Column();
        var mainLayout = Layout.Add(Layout.Column());
        mainLayout.Margin = 16;

        var apiKeyGroup = mainLayout.AddRow();
        apiKeyGroup.Add(new Label("ElevenLabs API Key:"));
        
        apiKeyInput = new LineEdit(this);
        apiKeyInput.MinimumWidth = 300;
        apiKeyInput.PlaceholderText = "Enter your ElevenLabs API key";
        apiKeyInput.Text = Settings.ElevenLabsApiKey;
        apiKeyGroup.Add(apiKeyInput);

        mainLayout.AddSpacingCell(8);

        var pathGroup = mainLayout.AddRow();
        pathGroup.Add(new Label("Generation Path:"));
        
        pathInput = new LineEdit(this);
        pathInput.MinimumWidth = 300;
        pathInput.PlaceholderText = "Enter generation path (relative to assets)";
        pathInput.Text = Settings.GenerationPath;
        pathGroup.Add(pathInput);

        mainLayout.AddStretchCell();

        var buttonLayout = mainLayout.AddRow();
        buttonLayout.AddStretchCell();

        saveButton = new Button.Primary("Save", "save");
        saveButton.Clicked = SaveSettings;
        buttonLayout.Add(saveButton);
    }

    private void SaveSettings()
    {
        Settings.ElevenLabsApiKey = apiKeyInput.Text;
        Settings.GenerationPath = pathInput.Text;
        EditorUtility.SaveProjectSettings(Settings, "settings.json");
        Dialog.AskConfirm(() => {}, "Settings saved successfully.", "Success", "Okay");
    }

    [Menu("Editor", "AI Tools/Settings")]
    public static void OpenSettings()
    {
        var dialog = new Dialog();
        dialog.Window.Title = "Sound Generation Settings";
        dialog.Window.Size = new Vector2(500, 200);
        dialog.Layout = Layout.Column();
        dialog.Layout.Add(new SoundSettings());
        dialog.Show();
    }

    public static string GetGenerationPath()
    {
        return Path.Combine(Project.Current.GetAssetsPath(), Settings.GenerationPath);
    }
}
using Editor;

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

public partial class GridMapTool
{
	private SceneTraceResult projectedPoint;
	public int FloorHeight = 128;

	public SceneTraceResult CursorRay (Ray cursorRay )
	{
		var tr = Scene.Trace.Ray( cursorRay, 5000 )
		.UseRenderMeshes( true )
		.UsePhysicsWorld( false )
		.WithTag( "gridtile" )
		.Run();

		return tr;
	}
	public void HandlePlacement( SceneTraceResult tr, Ray cursorRay )
	{
		//if ( SelectedJsonObject is null ) return;
		using var scope = SceneEditorSession.Scope();
		
		projectedPoint = ProjectRayOntoGroundPlane( cursorRay.Position, cursorRay.Forward, floors );
		
		if ( projectedPoint.Hit )
		{
			// Snap the projected point to the grid and adjust for floor height
			var snappedPosition = projectedPoint.EndPosition;

			if ( SelectedRandomJsonObject is not null && SelectedRandomJsonObject.Count != 0 )
			{
				// Create an instance of the Random class
				Random random = new Random();

				// Get a random index
				int randomIndex = random.Next( SelectedRandomJsonObject.Count );

				// Select a random tile from the list
				var randomTileJson = SelectedRandomJsonObject[randomIndex];

				// Instantiate the game object
				var go = new GameObject( true, "GridTile" );
				PrefabUtility.MakeGameObjectsUnique( randomTileJson );
				go.Deserialize( randomTileJson );
				go.Parent = CurrentGameObjectCollection;
				go.Transform.Position = snappedPosition;
				go.Transform.Rotation = Rotation.FromPitch( -90 ) * rotation;
				go.Tags.Remove( "group" );
				go.Tags.Add( "gridtile" );

				go.EditLog( "Grid Placed", go );
			}

			if ( SelectedJsonObject is not null )
			{
				var go = new GameObject( true, "GridTile" );
				PrefabUtility.MakeGameObjectsUnique( SelectedJsonObject );
				go.Deserialize( SelectedJsonObject );
				go.Parent = CurrentGameObjectCollection;
				go.Transform.Position = snappedPosition;
				go.Transform.Rotation = Rotation.FromPitch( -90 ) * rotation;
				go.Tags.Remove( "group" );
				go.Tags.Add( "gridtile" );

				go.EditLog( "Grid Placed", go );
			}
		}
	}

	public void HandleDecalPlace()
	{
		var cursorRay = Gizmo.CurrentRay;

		var trdecal = SceneEditorSession.Active.Scene.Trace
			.Ray( cursorRay, 5000 )
				.UseRenderMeshes( true )
				.UsePhysicsWorld( true )
				.Run();

		if(trdecal.Hit )
		{
			if ( SelectedJsonObject is not null )
			{
				var go = new GameObject( true, "GridTile" );
				PrefabUtility.MakeGameObjectsUnique( SelectedJsonObject );
				go.Deserialize( SelectedJsonObject );
				go.Parent = CurrentGameObjectCollection;
				go.Transform.Position = GizmoGameObject.Transform.Position;
				go.Transform.Rotation = GizmoGameObject.Transform.Rotation;
				go.Tags.Remove( "group" );
				go.Tags.Add( "gridtile" );
				var addition = go.Components.Get<DecalRenderer>();
				addition.TriPlanar = DecalTriPlanar;
				addition.Size = new Vector3( decalX, decalY, decalZ );

				go.EditLog( "Grid Placed", go );
			}
		}
	}

	public void HandlePlaceDuplicatedGroup( SceneTraceResult tr, Ray cursorRay )
	{
		projectedPoint = ProjectRayOntoGroundPlane( cursorRay.Position, cursorRay.Forward, floors );

		foreach ( var obj in GizmoDuplicateObject.Children )
		{

			obj.Flags = GameObjectFlags.None;
			obj.Tags.Remove( "isgizmoobject" );

			using var scope = SceneEditorSession.Scope();
			
			var options = new GameObject.SerializeOptions();
			var selection = obj;
			var json = selection.Serialize( options );

			SceneUtility.MakeGameObjectsUnique( json );
			var go = SceneEditorSession.Active.Scene.CreateObject();
		
			go.Deserialize( json );
			go.MakeNameUnique();
			go.Parent = CurrentGameObjectCollection;
			go.Transform.Position = obj.Transform.Position;
			go.Transform.Rotation = obj.Transform.Rotation;

			go.Tags.Add( "gridtile" );

			Log.Info( $"Duplicated:{obj.Name}" );
		}
		
	}

	public void HandleRemove( Ray cursorRay )
	{		
		if ( CursorRay(cursorRay).Hit )
		{
			Log.Info( $"Remove {CursorRay( cursorRay ).GameObject.Name}" );
			CursorRay( cursorRay ).GameObject.Destroy();
		}
	}
	public void HandleGetMove( Ray cursorRay )
	{
		if ( CursorRay( cursorRay ).Hit )
		{
			Log.Info( $"Start Moving {CursorRay( cursorRay ).GameObject.Name}" );
			SelectedObject = CursorRay( cursorRay ).GameObject;
			lastRot = SelectedObject.Transform.Rotation;
			beenRotated = false;
		}
	}
	Rotation lastRot;
	bool beenRotated;
	public void HandleMove( Ray cursorRay )
	{
		projectedPoint = ProjectRayOntoGroundPlane( cursorRay.Position, cursorRay.Forward, floors );

		if ( projectedPoint.Hit )
		{
			// Snap the projected point to the grid and adjust for floor height
			var snappedPosition = projectedPoint.EndPosition;

			SelectedObject.Transform.Position = snappedPosition;

			// Only update rotation if 'shouldRotate' is true
			if ( beenRotated )
			{
				SelectedObject.Transform.Rotation = Rotation.FromPitch( -90 ) * rotation;
			}
			else
			{
				// Keep the last rotation
				SelectedObject.Transform.Rotation = lastRot;
			}
		}
	}

	public void HandleCopyPlace( SceneTraceResult trace, Ray cursorRay )
	{
		if ( CursorRay( cursorRay ).Hit )
		{
			using var scope = SceneEditorSession.Scope();

			var options = new GameObject.SerializeOptions();
			var selection = CopyObject;
			var json = selection.Serialize( options );
			
			SceneUtility.MakeGameObjectsUnique( json );
			var go = SceneEditorSession.Active.Scene.CreateObject();

			go.Deserialize( json );
			go.MakeNameUnique();
			go.Parent = CurrentGameObjectCollection;
			go.Transform.Position = GetGizmoPosition( trace, cursorRay );
			go.Transform.Rotation = GizmoGameObject.Transform.Rotation;

			go.Tags.Add( "gridtile" );
		}
	}

	public void HandleCopy( Ray cursorRay )
	{
		if ( CursorRay( cursorRay ).Hit )
		{
			if( CursorRay( cursorRay ).GameObject.IsPrefabInstance)
			{
				var prefab = CursorRay( cursorRay ).GameObject.Root;
				CopyObject = prefab;
			}
			else
			{
				CopyObject = CursorRay( cursorRay ).GameObject;
			}

			Log.Info( $"Copy {CopyObject}" );
			beenRotated = false;
		}
	}

	bool _decalX = false;
	bool _decalY = false;
	bool _decalZ = false;
	bool _decalMinZ = false;


	public void DecalScale()
	{
		if ( CurrentPaintMode != PaintMode.Decal ) return;

		if (Gizmo.IsShiftPressed && Application.IsKeyDown( KeyCode.Q ) && !_decalX )
		{
			decalX += 2;
			Log.Info( $"Decal X: {decalX}" );
		}
		else if ( Gizmo.IsAltPressed && Application.IsKeyDown( KeyCode.Q ) && !_decalX )
		{
			decalX -= 2;
			Log.Info( $"Decal XWSWWW: {decalX}" );
		}
		else if ( Gizmo.IsShiftPressed && Application.IsKeyDown( KeyCode.W ) && !_decalY )
		{
			decalY += 2;
			Log.Info( $"Decal Y: {decalY}" );
		}
		else if ( Gizmo.IsAltPressed && Application.IsKeyDown( KeyCode.W ) && !_decalY )
		{
			decalY -= 2;
			Log.Info( $"Decal Y: {decalY}" );
		}
		else if ( Gizmo.IsShiftPressed && Application.IsKeyDown( KeyCode.E ) && !_decalZ )
		{
			decalZ += 2;
			Log.Info( $"Decal Z: {decalZ}" );
		}
		else if ( Gizmo.IsAltPressed && Application.IsKeyDown( KeyCode.E ) && !_decalZ )
		{
			decalZ -= 2;
			Log.Info( $"Decal Z: {decalZ}" );
		}

		_decalX = Application.IsKeyDown( KeyCode.Q );
		_decalY = Application.IsKeyDown( KeyCode.W );
		_decalZ = Application.IsKeyDown( KeyCode.E );
	}
	
	bool _prevlessFloor = false;
	bool _prevmoreFloor = false;
	public void FloorHeightShortCut()
	{
		if ( CurrentPaintMode == PaintMode.Decal ) return;

		if ( Application.IsKeyDown( KeyCode.Q ) && !_prevlessFloor )
		{
			DoFloors( -FloorHeight )();
			so.Delete();
			so = null;
			Grid( new Vector2( 16384, 16384 ), gridRotation, Gizmo.Settings.GridSpacing, SceneViewportWidget.LastSelected.State.GridOpacity );

			if ( floorLabel.IsValid() )
			{
				floorLabel.Text = floorCount.ToString();
			}
			floorcontrolLabel.Text = $"Floor Level: {floorCount}";
		}
		else if ( Application.IsKeyDown( KeyCode.E ) && !_prevmoreFloor )
		{
			DoFloors( FloorHeight )();
			so.Delete();
			so = null;
			Grid( new Vector2( 16384, 16384 ), gridRotation, Gizmo.Settings.GridSpacing, SceneViewportWidget.LastSelected.State.GridOpacity );

			if ( floorLabel.IsValid() )
			{
				floorLabel.Text = floorCount.ToString();
			}
			floorcontrolLabel.Text = $"Floor Level: {floorCount}";
		}

		_prevlessFloor = Application.IsKeyDown( KeyCode.Q );
		_prevmoreFloor = Application.IsKeyDown( KeyCode.E );
	}
	
	//Nasty
	bool _prevlessRotationZ = false;
	bool _prevmoreRotationZ = false;
	bool _prevlessRotationX = false;
	bool _prevmoreRotationX = false;
	bool _prevlessRotationY = false;
	bool _prevmoreRotationY = false;

	public void HandleRotation()
	{

		if ( Application.IsKeyDown( KeyCode.Num1 ) && Gizmo.IsShiftPressed && !_prevlessRotationZ )
		{
			DoRotation( true, GroundAxis.Z )();
			SnapToClosest( GroundAxis.Z );
		}
		else if ( Application.IsKeyDown( KeyCode.Num1 ) && Gizmo.IsAltPressed && !_prevmoreRotationZ )
		{
			DoRotation( false, GroundAxis.Z )();
			SnapToClosest( GroundAxis.Z );
		}

		if ( Application.IsKeyDown( KeyCode.Num2 ) && Gizmo.IsShiftPressed && !_prevlessRotationX )
		{
			DoRotation( true, GroundAxis.X )();
			SnapToClosest( GroundAxis.X );
		}
		else if ( Application.IsKeyDown( KeyCode.Num2 ) && Gizmo.IsAltPressed && !_prevmoreRotationX )
		{
			DoRotation( false, GroundAxis.X )();
			SnapToClosest( GroundAxis.X );
		}

		if ( Application.IsKeyDown( KeyCode.Num3 ) && Gizmo.IsShiftPressed && !_prevlessRotationY )
		{
			DoRotation( true, GroundAxis.Y )();
			SnapToClosest( GroundAxis.Y );
		}
		else if ( Application.IsKeyDown( KeyCode.Num3 ) && Gizmo.IsAltPressed && !_prevmoreRotationY )
		{
			DoRotation( false, GroundAxis.Y )();
			SnapToClosest( GroundAxis.Y );
		}

		_prevlessRotationZ = Application.IsKeyDown( KeyCode.Num1 ) && Gizmo.IsShiftPressed;
		_prevmoreRotationZ = Application.IsKeyDown( KeyCode.Num1 ) && Gizmo.IsAltPressed;
		_prevlessRotationX = Application.IsKeyDown( KeyCode.Num2 ) && Gizmo.IsShiftPressed;
		_prevmoreRotationX = Application.IsKeyDown( KeyCode.Num2 ) && Gizmo.IsAltPressed;
		_prevlessRotationY = Application.IsKeyDown( KeyCode.Num3 ) && Gizmo.IsShiftPressed;
		_prevmoreRotationY = Application.IsKeyDown( KeyCode.Num3 ) && Gizmo.IsAltPressed;
	}

	bool _prevlessRotationSnap = false;
	bool _prevmoreRotationSnap = false;

	public void UpdateRotationSnapWithKeybind()
	{
		if ( Gizmo.IsShiftPressed && Application.IsKeyDown( KeyCode.Num4 ) && !_prevlessRotationSnap )
		{
			if( rotationSnapBox.CurrentIndex != 0)
			rotationSnapBox.CurrentIndex = rotationSnapBox.CurrentIndex - 1;
			rotationLabel.Text = $"Rotation Snap: {rotationSnap}";
		}
		else if ( Gizmo.IsShiftPressed && Application.IsKeyDown( KeyCode.Num5 ) && !_prevmoreRotationSnap )
		{
			if ( rotationSnapBox.CurrentIndex != rotationSnapBox.Count -1 )
				rotationSnapBox.CurrentIndex = rotationSnapBox.CurrentIndex + 1;
			rotationLabel.Text = $"Rotation Snap: {rotationSnap}";
		}

		_prevlessRotationSnap = Gizmo.IsShiftPressed && Application.IsKeyDown( KeyCode.Num4 );
		_prevmoreRotationSnap = Gizmo.IsShiftPressed && Application.IsKeyDown( KeyCode.Num5 );
	}
}
using System.Text;

namespace Ink.Parsed
{
    public class Choice : Parsed.Object, IWeavePoint, INamedContent
	{
        public ContentList startContent { get; protected set; }
        public ContentList choiceOnlyContent { get; protected set; }
        public ContentList innerContent { get; protected set; }

        public string name
        {
            get { return identifier?.name; }
        }
        public Identifier identifier { get; set; }

        public Expression condition {
            get {
                return _condition;
            }
            set {
                _condition = value;
                if( _condition )
                    AddContent (_condition);
            }
        }

        public bool onceOnly { get; set; }
        public bool isInvisibleDefault { get; set; }

        public int    indentationDepth { get; set; }// = 1;
        public bool   hasWeaveStyleInlineBrackets { get; set; }

        // Required for IWeavePoint interface
        // Choice's target container. Used by weave to append any extra
        // nested weave content into.
        public Runtime.Container runtimeContainer { get { return _innerContentContainer; } }


        public Runtime.Container innerContentContainer {
            get {
                return _innerContentContainer;
            }
        }

        public override Runtime.Container containerForCounting {
            get {
                return _innerContentContainer;
            }
        }

        // Override runtimePath to point to the Choice's target content (after it's chosen),
        // as opposed to the default implementation which would point to the choice itself
        // (or it's outer container), which is what runtimeObject is.
        public override Runtime.Path runtimePath
        {
            get {
                return _innerContentContainer.path;
            }
        }

        public Choice (ContentList startContent, ContentList choiceOnlyContent, ContentList innerContent)
		{
            this.startContent = startContent;
            this.choiceOnlyContent = choiceOnlyContent;
            this.innerContent = innerContent;
			this.indentationDepth = 1;

            if (startContent)
                AddContent (this.startContent);

            if (choiceOnlyContent)
                AddContent (this.choiceOnlyContent);

            if( innerContent )
                AddContent (this.innerContent);

            this.onceOnly = true; // default
		}


		public override Runtime.Object GenerateRuntimeObject ()
        {
            _outerContainer = new Runtime.Container ();

            // Content names for different types of choice:
            //  * start content [choice only content] inner content
            //  * start content   -> divert
            //  * start content
            //  * [choice only content]

            // Hmm, this structure has become slightly insane!
            //
            // [
            //     EvalStart
            //     assign $r = $r1   -- return target = return label 1
            //     BeginString
            //     -> s
            //     [(r1)]            -- return label 1 (after start content)
            //     EndString
            //     BeginString
            //     ... choice only content
            //     EndEval
            //     Condition expression
            //     choice: -> "c-0"
            //     (s) = [
            //         start content
            //         -> r          -- goto return label 1 or 2
            //     ]
            //  ]
            //
            //  in parent's container: (the inner content for the choice)
            //
            //  (c-0) = [
            //      EvalStart
            //      assign $r = $r2   -- return target = return label 2
            //      EndEval
            //      -> s
            //      [(r2)]            -- return label 1 (after start content)
            //      inner content
            //  ]
            //

            _runtimeChoice = new Runtime.ChoicePoint (onceOnly);
            _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault;

            if (startContent || choiceOnlyContent || condition) {
                _outerContainer.AddContent (Runtime.ControlCommand.EvalStart ());
            }

            // Start content is put into a named container that's referenced both
            // when displaying the choice initially, and when generating the text
            // when the choice is chosen.
            if (startContent) {

                // Generate start content and return
                //  - We can't use a function since it uses a call stack element, which would
                //    put temporary values out of scope. Instead we manually divert around.
                //  - $r is a variable divert target contains the return point
                _returnToR1 = new Runtime.DivertTargetValue ();
                _outerContainer.AddContent (_returnToR1);
                var varAssign = new Runtime.VariableAssignment ("$r", true);
                _outerContainer.AddContent (varAssign);

                // Mark the start of the choice text generation, so that the runtime
                // knows where to rewind to to extract the content from the output stream.
                _outerContainer.AddContent (Runtime.ControlCommand.BeginString ());

                _divertToStartContentOuter = new Runtime.Divert ();
                _outerContainer.AddContent (_divertToStartContentOuter);

                // Start content itself in a named container
                _startContentRuntimeContainer = startContent.GenerateRuntimeObject () as Runtime.Container;
                _startContentRuntimeContainer.name = "s";

                // Effectively, the "return" statement - return to the point specified by $r
                var varDivert = new Runtime.Divert ();
                varDivert.variableDivertName = "$r";
                _startContentRuntimeContainer.AddContent (varDivert);

                // Add the container
                _outerContainer.AddToNamedContentOnly (_startContentRuntimeContainer);

                // This is the label to return to
                _r1Label = new Runtime.Container ();
                _r1Label.name = "$r1";
                _outerContainer.AddContent (_r1Label);

                _outerContainer.AddContent (Runtime.ControlCommand.EndString ());

                _runtimeChoice.hasStartContent = true;
            }

            // Choice only content - mark the start, then generate it directly into the outer container
            if (choiceOnlyContent) {
                _outerContainer.AddContent (Runtime.ControlCommand.BeginString ());

                var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject () as Runtime.Container;
                _outerContainer.AddContentsOfContainer (choiceOnlyRuntimeContent);

                _outerContainer.AddContent (Runtime.ControlCommand.EndString ());

                _runtimeChoice.hasChoiceOnlyContent = true;
            }

            // Generate any condition for this choice
            if (condition) {
                condition.GenerateIntoContainer (_outerContainer);
                _runtimeChoice.hasCondition = true;
            }

            if (startContent || choiceOnlyContent || condition) {
                _outerContainer.AddContent (Runtime.ControlCommand.EvalEnd ());
            }

            // Add choice itself
            _outerContainer.AddContent (_runtimeChoice);

            // Container that choice points to for when it's chosen
            _innerContentContainer = new Runtime.Container ();

            // Repeat start content by diverting to its container
            if (startContent) {

                // Set the return point when jumping back into the start content
                //  - In this case, it's the $r2 point, within the choice content "c".
                _returnToR2 = new Runtime.DivertTargetValue ();
                _innerContentContainer.AddContent (Runtime.ControlCommand.EvalStart ());
                _innerContentContainer.AddContent (_returnToR2);
                _innerContentContainer.AddContent (Runtime.ControlCommand.EvalEnd ());
                var varAssign = new Runtime.VariableAssignment ("$r", true);
                _innerContentContainer.AddContent (varAssign);

                // Main divert into start content
                _divertToStartContentInner = new Runtime.Divert ();
                _innerContentContainer.AddContent (_divertToStartContentInner);

                // Define label to return to
                _r2Label = new Runtime.Container ();
                _r2Label.name = "$r2";
                _innerContentContainer.AddContent (_r2Label);
            }

            // Choice's own inner content
            if (innerContent) {
				var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject () as Runtime.Container;
                _innerContentContainer.AddContentsOfContainer (innerChoiceOnlyContent);
            }

            if (this.story.countAllVisits) {
                _innerContentContainer.visitsShouldBeCounted = true;
            }

            _innerContentContainer.countingAtStartOnly = true;

            return _outerContainer;
		}

        public override void ResolveReferences(Story context)
		{
			// Weave style choice - target own content container
            if (_innerContentContainer) {
                _runtimeChoice.pathOnChoice = _innerContentContainer.path;

                if (onceOnly)
                    _innerContentContainer.visitsShouldBeCounted = true;
            }

            if (_returnToR1)
                _returnToR1.targetPath = _r1Label.path;

            if (_returnToR2)
                _returnToR2.targetPath = _r2Label.path;

            if( _divertToStartContentOuter )
                _divertToStartContentOuter.targetPath = _startContentRuntimeContainer.path;

            if( _divertToStartContentInner )
                _divertToStartContentInner.targetPath = _startContentRuntimeContainer.path;

            base.ResolveReferences (context);

            if( identifier != null && identifier.name.Length > 0 )
                context.CheckForNamingCollisions (this, identifier, Story.SymbolType.SubFlowAndWeave);
		}

        public override string ToString ()
        {
            if (choiceOnlyContent != null) {
                return string.Format ("* {0}[{1}]...", startContent, choiceOnlyContent);
            } else {
                return string.Format ("* {0}...", startContent);
            }
        }

        Runtime.ChoicePoint _runtimeChoice;
        Runtime.Container _innerContentContainer;
        Runtime.Container _outerContainer;
        Runtime.Container _startContentRuntimeContainer;
        Runtime.Divert _divertToStartContentOuter;
        Runtime.Divert _divertToStartContentInner;
        Runtime.Container _r1Label;
        Runtime.Container _r2Label;
        Runtime.DivertTargetValue _returnToR1;
        Runtime.DivertTargetValue _returnToR2;
        Expression _condition;
	}

}

using System.Collections.Generic;
using System.Text;

namespace Ink.Parsed
{
	public abstract class Object
	{
        public Runtime.DebugMetadata debugMetadata {
            get {
                if (_debugMetadata == null) {
                    if (parent) {
                        return parent.debugMetadata;
                    }
                }

                return _debugMetadata;
            }

            set {
                _debugMetadata = value;
            }
        }
        private Runtime.DebugMetadata _debugMetadata;

        public bool hasOwnDebugMetadata {
            get {
                return _debugMetadata != null;
            }
        }

        public virtual string typeName {
            get {
                return GetType().Name;
            }
        }

		public Parsed.Object parent { get; set; }
        public List<Parsed.Object> content { get; protected set; }

        public Parsed.Story story {
            get {
                Parsed.Object ancestor = this;
                while (ancestor.parent) {
                    ancestor = ancestor.parent;
                }
                return ancestor as Parsed.Story;
            }
        }

		private Runtime.Object _runtimeObject;
		public Runtime.Object runtimeObject
		{
			get {
				if (_runtimeObject == null) {
					_runtimeObject = GenerateRuntimeObject ();
                    if( _runtimeObject )
                        _runtimeObject.debugMetadata = debugMetadata;
				}
				return _runtimeObject;
			}

			set {
				_runtimeObject = value;
			}
		}

        // virtual so that certian object types can return a different
        // path than just the path to the main runtimeObject.
        // e.g. a Choice returns a path to its content rather than
        // its outer container.
        public virtual Runtime.Path runtimePath
        {
            get {
                return runtimeObject.path;
            }
        }

        // When counting visits and turns since, different object
        // types may have different containers that needs to be counted.
        // For most it'll just be the object's main runtime object,
        // but for e.g. choices, it'll be the target container.
        public virtual Runtime.Container containerForCounting
        {
            get {
                return this.runtimeObject as Runtime.Container;
            }
        }

        public Parsed.Path PathRelativeTo(Parsed.Object otherObj)
        {
            var ownAncestry = ancestry;
            var otherAncestry = otherObj.ancestry;

            Parsed.Object highestCommonAncestor = null;
            int minLength = System.Math.Min (ownAncestry.Count, otherAncestry.Count);
            for (int i = 0; i < minLength; ++i) {
                var a1 = ancestry [i];
                var a2 = otherAncestry [i];
                if (a1 == a2)
                    highestCommonAncestor = a1;
                else
                    break;
            }

            FlowBase commonFlowAncestor = highestCommonAncestor as FlowBase;
            if (commonFlowAncestor == null)
                commonFlowAncestor = highestCommonAncestor.ClosestFlowBase ();


            var pathComponents = new List<Identifier> ();
            bool hasWeavePoint = false;
            FlowLevel baseFlow = FlowLevel.WeavePoint;

            var ancestor = this;
            while(ancestor && (ancestor != commonFlowAncestor) && !(ancestor is Story)) {

                if (ancestor == commonFlowAncestor)
                    break;

                if (!hasWeavePoint) {
                    var weavePointAncestor = ancestor as IWeavePoint;
                    if (weavePointAncestor != null && weavePointAncestor.identifier != null) {
                        pathComponents.Add (weavePointAncestor.identifier);
                        hasWeavePoint = true;
                        continue;
                    }
                }

                var flowAncestor = ancestor as FlowBase;
                if (flowAncestor) {
                    pathComponents.Add (flowAncestor.identifier);
                    baseFlow = flowAncestor.flowLevel;
                }

                ancestor = ancestor.parent;
            }

            pathComponents.Reverse ();

            if (pathComponents.Count > 0) {
                return new Path (baseFlow, pathComponents);
            }

            return null;
        }

        public List<Parsed.Object> ancestry
        {
            get {
                var result = new List<Parsed.Object> ();

                var ancestor = this.parent;
                while(ancestor) {
                    result.Add (ancestor);
                    ancestor = ancestor.parent;
                }

                result.Reverse ();

                return result;
            }
        }

        public string descriptionOfScope
        {
            get {
                var locationNames = new List<string> ();

                Parsed.Object ancestor = this;
                while (ancestor) {
                    var ancestorFlow = ancestor as FlowBase;
                    if (ancestorFlow && ancestorFlow.identifier != null) {
                        locationNames.Add ("'" + ancestorFlow.identifier + "'");
                    }
                    ancestor = ancestor.parent;
                }

                var scopeSB = new StringBuilder ();
                if (locationNames.Count > 0) {
                    var locationsListStr = string.Join (", ", locationNames.ToArray());
                    scopeSB.Append (locationsListStr);
                    scopeSB.Append (" and ");
                }

                scopeSB.Append ("at top scope");

                return scopeSB.ToString ();
            }
        }

        // Return the object so that method can be chained easily
        public T AddContent<T>(T subContent) where T : Parsed.Object
        {
            if (content == null) {
                content = new List<Parsed.Object> ();
            }

            // Make resilient to content not existing, which can happen
            // in the case of parse errors where we've already reported
            // an error but still want a valid structure so we can
            // carry on parsing.
            if( subContent ) {
                subContent.parent = this;
                content.Add(subContent);
            }

            return subContent;
        }

        public void AddContent<T>(List<T> listContent) where T : Parsed.Object
        {
            foreach (var obj in listContent) {
                AddContent (obj);
            }
        }

        public T InsertContent<T>(int index, T subContent) where T : Parsed.Object
        {
            if (content == null) {
                content = new List<Parsed.Object> ();
            }

            subContent.parent = this;
            content.Insert (index, subContent);

            return subContent;
        }

        public delegate bool FindQueryFunc<T>(T obj);
        public T Find<T>(FindQueryFunc<T> queryFunc = null) where T : class
        {
            var tObj = this as T;
            if (tObj != null && (queryFunc == null || queryFunc (tObj) == true)) {
                return tObj;
            }

            if (content == null)
                return null;

            foreach (var obj in content) {
                var nestedResult = obj.Find (queryFunc);
                if (nestedResult != null)
                    return nestedResult;
            }

            return null;
        }


        public List<T> FindAll<T>(FindQueryFunc<T> queryFunc = null) where T : class
        {
            var found = new List<T> ();

            FindAll (queryFunc, found);

            return found;
        }

        void FindAll<T>(FindQueryFunc<T> queryFunc, List<T> foundSoFar) where T : class
        {
            var tObj = this as T;
            if (tObj != null && (queryFunc == null || queryFunc (tObj) == true)) {
                foundSoFar.Add (tObj);
            }

            if (content == null)
                return;

            foreach (var obj in content) {
                obj.FindAll (queryFunc, foundSoFar);
            }
        }

		public abstract Runtime.Object GenerateRuntimeObject ();

        public virtual void ResolveReferences(Story context)
		{
            if (content != null) {
                foreach(var obj in content) {
                    obj.ResolveReferences (context);
                }
            }
		}

        public FlowBase ClosestFlowBase()
        {
            var ancestor = this.parent;
            while (ancestor) {
                if (ancestor is FlowBase) {
                    return (FlowBase)ancestor;
                }
                ancestor = ancestor.parent;
            }

            return null;
        }

        public virtual void Error(string message, Parsed.Object source = null, bool isWarning = false)
		{
			if (source == null) {
				source = this;
			}

            // Only allow a single parsed object to have a single error *directly* associated with it
            if (source._alreadyHadError && !isWarning) {
                return;
            }
            if (source._alreadyHadWarning && isWarning) {
                return;
            }

            if (this.parent) {
                this.parent.Error (message, source, isWarning);
            } else {
                throw new System.Exception ("No parent object to send error to: "+message);
            }

            if (isWarning) {
                source._alreadyHadWarning = true;
            } else {
                source._alreadyHadError = true;
            }

		}

        public void Warning(string message, Parsed.Object source = null)
        {
            Error (message, source, isWarning: true);
        }

        // Allow implicit conversion to bool so you don't have to do:
        // if( myObj != null ) ...
        public static implicit operator bool (Object obj)
        {
            var isNull = object.ReferenceEquals (obj, null);
            return !isNull;
        }

        public static bool operator ==(Object a, Object b)
        {
            return object.ReferenceEquals (a, b);
        }

        public static bool operator !=(Object a, Object b)
        {
            return !(a == b);
        }

        public override bool Equals (object obj)
        {
            return object.ReferenceEquals (obj, this);
        }

        public override int GetHashCode ()
        {
            return base.GetHashCode ();
        }

        bool _alreadyHadError;
        bool _alreadyHadWarning;
	}
}

using Editor;
using Sandbox;
using System.Linq;

namespace TikTokTTS.Editor;

[CustomEditor( typeof( TikTokSpeech ) )]
public class TikTokSpeechComponentEditor : ComponentEditorWidget
{

	SerializedObject Target;

	public TikTokSpeechComponentEditor( SerializedObject obj ) : base( obj )
	{
		Target = obj;
		Layout = Layout.Column();
		var sheet = new PropertyControlSheet();
		sheet.AddObject( obj );
		Layout.Add( sheet );
		Layout.Spacing = 2;
		MinimumHeight = 70;

		var buttonPanel = Layout.Column();
		buttonPanel.Margin = new Sandbox.UI.Margin( 12, 0 );
		var button = new Button( "Speak", "play_arrow" )
		{
			Width = 200,
			Clicked = () =>
			{
				var component = Target.Targets.First() as TikTokSpeech;
				component?.Speak();
			}
		};
		buttonPanel.Add( button );
		Layout.Add( buttonPanel );
	}

	protected override void OnPaint()
	{
	}

}
using Sandbox;
using Editor;

namespace SFXR.Editor;

public class PropertyControlSheet : GridLayout
{
	public SerializedObject TargetObject { get; set; }

	int rows = 0;

	public PropertyControlSheet() : base()
	{
		Margin = new Sandbox.UI.Margin( 16, 8, 16, 8 );
		HorizontalSpacing = 10;
		VerticalSpacing = 2;
		SetColumnStretch( 1, 2 );
		SetMinimumColumnWidth( 0, 120 );
	}

	public void AddObject( SerializedObject obj )
	{
		foreach ( var entry in obj )
		{
			if ( !entry.HasAttribute<PropertyAttribute>() )
			{
				continue;
			}
			if ( entry.PropertyType.Name.StartsWith( "Action" ) )
			{
				continue;
			}

			AddRow( entry );
		}
	}

	/// <summary>
	/// Add a serialized property row. This will create an editor for the row and a label.
	/// </summary>
	public void AddRow( SerializedProperty property, float labelIndent = 0.0f )
	{
		var editor = ControlWidget.Create( property );
		if ( editor is null )
			return;

		if ( editor.IsWideMode )
		{
			AddCell( 0, rows++, new Label( property.DisplayName ) { MinimumHeight = Theme.RowHeight, Alignment = TextFlag.LeftCenter }, 2, 1, TextFlag.LeftTop );
			var lo = AddCell( 0, rows, Layout.Column(), 2, 1, TextFlag.LeftTop );

			lo.Margin = new Sandbox.UI.Margin( 16, 0, 0, 0 );
			lo.Add( editor );
		}
		else
		{
			int cell = 0;

			var label = AddCell( cell++, rows, new Label( property.DisplayName ), 1, 1, TextFlag.LeftTop );
			label.MinimumHeight = Theme.RowHeight;
			label.Alignment = TextFlag.LeftCenter;
			label.SetStyles( "color: #aaa;" );

			label.ToolTip = property.Description ?? property.DisplayName;

			if ( labelIndent > 0 )
			{
				label.ContentMargins = new Sandbox.UI.Margin( labelIndent, 0, 0, 0 );
			}

			AddCell( cell++, rows, editor, 1, 1, TextFlag.LeftTop );
		}

		rows++;
	}

	/// <summary>
	/// Add a layout to a double wide cell
	/// </summary>
	public void AddLayout( Layout layout )
	{
		AddCell( 0, rows++, layout, 2, 1, TextFlag.LeftTop );
	}

}
using Editor;
using Sandbox;
using Sandbox.Internal;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static DressinTerry;
using static Editor.EditorEvent;
using static Sandbox.ClothingContainer;

public static class ImporterEditorMenu
{
	public static string dressingTerryDataFolder => $"{Editor.FileSystem.Root.GetFullPath("/data/coomzy/dressin_terry")}";
	public static string dressingTerryProjectRootFolder = $"{Project.Current.RootDirectory.FullName}/Assets/dressin_terry";
	public static string dressingTerryProjectCharactersFolder = $"{dressingTerryProjectRootFolder}/characters";

	[Menu("Editor", "Library/Dressin Terry/Import All Characters to Project")]
	public static void OpenMyMenu()
	{
		var dressingTerryCharactersFolder = $"{dressingTerryDataFolder}/characters";

		var filePaths = Directory.EnumerateFiles(dressingTerryCharactersFolder, $"*.{extension}", SearchOption.AllDirectories);
		bool hasFailed = false;

		foreach (var filePath in filePaths)
		{
			string relativePath = Path.GetRelativePath(dressingTerryCharactersFolder, filePath);
			string destinationPath = Path.Combine(dressingTerryProjectCharactersFolder, relativePath);

			string destinationDirectory = Path.GetDirectoryName(destinationPath);
			if (!Directory.Exists(destinationDirectory))
			{
				Directory.CreateDirectory(destinationDirectory);
			}

			var fileContents = File.ReadAllText(filePath);
			var characterContainer = ClothingContainer.CreateFromJson(fileContents);

			if (!CreateCharacterFromContainer(characterContainer, destinationPath))
			{
				hasFailed = true;
				Log.Info($"Copied FAILED {filePath} to {destinationPath}");
				continue;
			}

			Log.Info($"Copied {filePath} to {destinationPath}");
		}

		if (EditorPreferences.NotificationSounds)
		{
			if (hasFailed)
			{
				EditorUtility.PlayRawSound("sounds/editor/fail.wav");
			}
			else
			{
				EditorUtility.PlayRawSound("sounds/editor/success.wav");
			}
		}
	}

	public static bool CreateCharacterFromContainer(ClothingContainer clothingContainer, string destinationPath)
	{
		Log.Info($"CreateCharacterFromContainer destinationPath: {destinationPath}");
		string destinationPathWithoutExtension = destinationPath.Replace(extension, "");
		var characterAsset = AssetSystem.CreateResource(extension, destinationPathWithoutExtension);

		if (!characterAsset.TryLoadResource<DressinTerryCharacter>(out var character))
		{
			Log.Error($"characterAsset: {characterAsset} failed to TryLoadResource");
			return false;
		}

		character.clothingEntries = clothingContainer.Clothing.Select(inst => ClothingInst.Convert(inst)).ToList();
		character.characterHeight = clothingContainer.Height;
		if (!characterAsset.SaveToDisk(character))
		{
			Log.Error($"characterAsset: {characterAsset} failed to save to disk");
			return false;
		}

		AssetSystem.RegisterFile(destinationPath);
		return true;
	}

	[Event("reload"), Hotload]
	static void Register_Event()
	{
		RegisterOnRequestCreateCharacter(DressinTerry_OnRequestCreateCharacter);
	}

	static void DressinTerry_OnRequestCreateCharacter(ClothingContainer clothingContainer, string relativeFilePath)
	{
		string rootFilePath = dressingTerryProjectCharactersFolder;
		string desiredFileName = relativeFilePath ?? "Generated";
		string fileName = desiredFileName;
		string filePath = $"{rootFilePath}/{fileName}.{extension}";

		if (File.Exists(filePath))
		{
			int generatedCount = 0;
			do
			{
				generatedCount++;
				fileName = $"{desiredFileName}_{generatedCount}";
				filePath = $"{rootFilePath}/{fileName}.{extension}";
			}
			while (File.Exists(filePath));
		}

		CreateCharacterFromContainer(clothingContainer, filePath);
	}

	const string DOCS_LINK = "https://sbox.game/coomzy/dressin_terry/news/how-to-use-6358c4a9";
	[Menu("Editor", "Library/Dressin Terry/Documentation")]
	public static void OpenDocs()
	{ 
		try
		{
			System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(DOCS_LINK) { UseShellExecute = true });
		}
		catch (System.ComponentModel.Win32Exception)
		{
			Log.Error($"Could not open the URL: {DOCS_LINK}");
		}
	}
}
using System;
using System.Threading.Tasks;

class LibraryListProxy : ListView
{
	public Action<Package> OnLibrarySelected { get; set; }

	public bool ShowInstalled { get; set; }
	public bool ShowAvailable { get; set; }

	public string Filter { get; set; } = "";

	public LibraryListProxy( Widget parent ) : base( parent )
	{
		ItemContextMenu = ShowItemContext;
		ItemSelected = OnItemClicked;

		ItemSize = new Vector2( -1, 64 );
	}

	public void OnItemClicked( object value )
	{
		if ( value is LibraryProject lib )
		{
			OnLibrarySelected?.Invoke( lib.Project.Package );
		}

		if ( value is Package p )
		{
			OnLibrarySelected?.Invoke( p );
		}
	}

	private void ShowItemContext( object obj )
	{
		LibraryProject project = obj as LibraryProject;
		Package package = obj as Package ?? project?.Project.Package;

		var m = new Menu();

		if ( package is not null && package.Org.Ident != "local" )
		{
			m.AddOption( "View in Browser", "public", () => EditorUtility.OpenFolder( package.Url ) );
			m.AddSeparator();
		}

		if ( project is not null )
		{
			m.AddOption( "Project Properties", "tune", () => _ = ProjectSettingsWindow.OpenForProject( project.Project ) );
			m.AddSeparator();
			m.AddOption( "Publish Project", "upload_file", () => _ = ProjectSettingsWindow.OpenForProject( project.Project, "upload" ) );
			m.AddSeparator();
			m.AddOption( "Show in Explorer", "folder", () => EditorUtility.OpenFolder( project.Project.RootDirectory.FullName ) );
		}

		m.OpenAt( Editor.Application.CursorPosition );
	}

	protected override void PaintItem( VirtualWidget item )
	{
		if ( item.Object is LibraryProject c )
			PaintItem( item, c.Project.Package );

		if ( item.Object is Package p )
			PaintItem( item, p );
	}

	private void PaintItem( VirtualWidget item, Package c )
	{
		var rect = item.Rect;

		if ( Paint.HasPressed )
			rect = rect.Shrink( 2, 2, 0, 0 );

		var library = LibrarySystem.All.Where( x => x.Project.Package.Ident == c.Ident && x.Project.Package.Org.Ident == c.Org.Ident && x.Project.Package.Ident != "libraryimporter").FirstOrDefault();

		var pen = Color.White.WithAlpha( 0.6f );

		if ( Paint.HasMouseOver || Paint.HasSelected )
		{
			Paint.SetBrush( Theme.Primary.WithAlpha( Paint.HasMouseOver ? 0.8f : 0.5f ) );
			Paint.ClearPen();
			Paint.DrawRect( rect, 4 );

			pen = Color.White.WithAlpha( Paint.HasMouseOver ? 1 : 0.9f );
			Paint.ClearBrush();
		}

		rect = rect.Shrink( 4 );

		// Icon
		{
			var iconRect = rect;
			iconRect.Width = iconRect.Height;
			iconRect = iconRect.Shrink( 8 );

			Paint.SetBrushAndPen( Theme.Grey.WithAlpha( 0.4f ) );

			if ( !string.IsNullOrWhiteSpace( c.Thumb ) && !c.Thumb.StartsWith( "/" ) )
			{
				Paint.Draw( iconRect, c.Thumb );
			}
			else
			{
				Paint.DrawRect( iconRect, 4 );
			}

			rect.Left = iconRect.Right + 8;
		}

		//header
		{
			rect.Top += 8;

			Paint.SetFont( "Poppins", 11, 450 );
			Paint.Pen = pen;
			var r = Paint.DrawText( rect, c.Title, TextFlag.LeftTop );

			r.Left = r.Right + 8;
			r.Right = rect.Right;
			r.Bottom -= 3;

			rect.Top = r.Bottom + 4;

			var installedVersion = library is not null ? $"v{library?.Version}" : "";

			Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
			Paint.SetDefaultFont();
			Paint.DrawText( r, $"{c.Org.Title} {installedVersion}", TextFlag.LeftBottom );
		}

		// body text
		{
			Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
			Paint.SetDefaultFont();
			Paint.DrawText( rect, c.Summary, TextFlag.LeftTop );
		}
	}

	protected override void OnPaint()
	{
		Paint.ClearPen();
		Paint.SetBrush( Theme.ControlBackground );
		Paint.DrawRect( LocalRect );

		Paint.Antialiasing = true;
		Paint.TextAntialiasing = true;

		base.OnPaint();
	}

	[EditorEvent.Frame]
	public void Frame()
	{
		if ( !Visible ) return;

		if ( ShowInstalled )
		{
			if ( SetContentHash( GetContentHash(), 0.5f ) )
			{
				SetItems(LibrarySystem.All.Where(x => x.Project.Package.Ident != "libraryimporter"));
				SelectItem(LibrarySystem.All.FirstOrDefault(x => x.Project.Package.Ident != "libraryimporter") );
			}
		}

		if ( ShowAvailable )
		{
			if ( SetContentHash( GetContentHash(), 0.5f ) )
			{
				_ = UpdateAsync();
			}
		}
	}

	async Task UpdateAsync()
	{
		var search = "";
		if ( !string.IsNullOrEmpty( Filter ) ) search = Filter + " ";
		var found = await Package.FindAsync( $"{search}type:library sort:updated", 200, 0 );
		SetItems( found.Packages );

		SelectItem( found.Packages.FirstOrDefault() );
	}

	int GetContentHash()
	{
		if ( ShowInstalled )
			return LibrarySystem.All.GetHashCode();

		return HashCode.Combine( Filter );
	}

}
using System.IO;

namespace Ink
{
    public interface IFileHandler
    {
        string ResolveInkFilename (string includeName);
        string LoadInkFileContents (string fullFilename);
    }

    public class DefaultFileHandler : Ink.IFileHandler {
        public string ResolveInkFilename (string includeName)
        {
            var workingDir = Directory.GetCurrentDirectory ();
            var fullRootInkPath = Path.Combine (workingDir, includeName);
            return fullRootInkPath;
        }

        public string LoadInkFileContents (string fullFilename)
        {
        	return File.ReadAllText (fullFilename);
        }
    }
}
using System;
using System.Collections.Generic;
using System.IO;

namespace Ink
{
    public partial class InkParser : StringParser
    {
        public InkParser(string str, string filenameForMetadata = null, Ink.ErrorHandler externalErrorHandler = null, IFileHandler fileHandler = null)
            : this(str, filenameForMetadata, externalErrorHandler, null, fileHandler)
        {  }

        InkParser(string str, string inkFilename = null, Ink.ErrorHandler externalErrorHandler = null, InkParser rootParser = null, IFileHandler fileHandler = null) : base(str) {
            _filename = inkFilename;
            RegisterExpressionOperators ();
            GenerateStatementLevelRules ();

            // Built in handler for all standard parse errors and warnings
            this.errorHandler = OnStringParserError;

            // The above parse errors are then formatted as strings and passed
            // to the Ink.ErrorHandler, or it throws an exception
            _externalErrorHandler = externalErrorHandler;

            _fileHandler = fileHandler ?? new DefaultFileHandler();

            if (rootParser == null) {
                _rootParser = this;

                _openFilenames = new HashSet<string> ();

                if (inkFilename != null) {
                    var fullRootInkPath = _fileHandler.ResolveInkFilename (inkFilename);
                    _openFilenames.Add (fullRootInkPath);
                }

            } else {
                _rootParser = rootParser;
            }

        }

        // Main entry point
        public Parsed.Story Parse()
        {
            List<Parsed.Object> topLevelContent = StatementsAtLevel (StatementLevel.Top);

            // Note we used to return null if there were any errors, but this would mean
            // that include files would return completely empty rather than attempting to
            // continue with errors. Returning an empty include files meant that anything
            // that *did* compile successfully would otherwise be ignored, generating way
            // more errors than necessary.
            return new Parsed.Story (topLevelContent, isInclude:_rootParser != this);
        }

        protected List<T> SeparatedList<T> (SpecificParseRule<T> mainRule, ParseRule separatorRule) where T : class
        {
            T firstElement = Parse (mainRule);
            if (firstElement == null) return null;

            var allElements = new List<T> ();
            allElements.Add (firstElement);

            do {

                int nextElementRuleId = BeginRule ();

                var sep = separatorRule ();
                if (sep == null) {
                    FailRule (nextElementRuleId);
                    break;
                }

                var nextElement = Parse (mainRule);
                if (nextElement == null) {
                    FailRule (nextElementRuleId);
                    break;
                }

                SucceedRule (nextElementRuleId);

                allElements.Add (nextElement);

            } while (true);

            return allElements;
        }

        protected override string PreProcessInputString(string str)
        {
            var inputWithCommentsRemoved = (new CommentEliminator (str)).Process();
            return inputWithCommentsRemoved;
        }

        protected Runtime.DebugMetadata CreateDebugMetadata(StringParserState.Element stateAtStart, StringParserState.Element stateAtEnd)
        {
            var md = new Runtime.DebugMetadata ();
            md.startLineNumber = stateAtStart.lineIndex + 1;
            md.endLineNumber = stateAtEnd.lineIndex + 1;
            md.startCharacterNumber = stateAtStart.characterInLineIndex + 1;
            md.endCharacterNumber = stateAtEnd.characterInLineIndex + 1;
            md.fileName = _filename;
            return md;
        }

        protected override void RuleDidSucceed(object result, StringParserState.Element stateAtStart, StringParserState.Element stateAtEnd)
        {
            // Apply DebugMetadata based on the state at the start of the rule
            // (i.e. use line number as it was at the start of the rule)
            var parsedObj = result as Parsed.Object;
            if ( parsedObj) {
                parsedObj.debugMetadata = CreateDebugMetadata(stateAtStart, stateAtEnd);
                return;
            }

            // A list of objects that doesn't already have metadata?
            var parsedListObjs = result as List<Parsed.Object>;
            if (parsedListObjs != null) {
                foreach (var parsedListObj in parsedListObjs) {
                    if (!parsedListObj.hasOwnDebugMetadata) {
                        parsedListObj.debugMetadata = CreateDebugMetadata(stateAtStart, stateAtEnd);
                    }
                }
            }

            var id = result as Parsed.Identifier;
            if (id != null) {
                id.debugMetadata = CreateDebugMetadata(stateAtStart, stateAtEnd);
            }
        }

        protected bool parsingStringExpression
        {
            get {
                return GetFlag ((uint)CustomFlags.ParsingString);
            }
            set {
                SetFlag ((uint)CustomFlags.ParsingString, value);
            }
        }

        protected bool tagActive
        {
            get {
                return GetFlag ((uint)CustomFlags.TagActive);
            }
            set {
                SetFlag ((uint)CustomFlags.TagActive, value);
            }
        }

        protected enum CustomFlags {
            ParsingString = 0x1,
            TagActive = 0x2
        }

        void OnStringParserError(string message, int index, int lineIndex, bool isWarning)
        {
            var warningType = isWarning ? "WARNING:" : "ERROR:";
            string fullMessage;

            if (_filename != null) {
                fullMessage = string.Format(warningType+" '{0}' line {1}: {2}",  _filename, (lineIndex+1), message);
            } else {
                fullMessage = string.Format(warningType+" line {0}: {1}", (lineIndex+1), message);
            }

            if (_externalErrorHandler != null) {
                _externalErrorHandler (fullMessage, isWarning ? ErrorType.Warning : ErrorType.Error);
            } else {
                throw new System.Exception (fullMessage);
            }
        }

        IFileHandler _fileHandler;

        Ink.ErrorHandler _externalErrorHandler;

        string _filename;
    }
}

namespace Editor;

public partial class GridMapTool
{
	void CollectionGroupHighLight()
	{
		if ( timeSinceChangedCollection <= 5 )
		{
			var alpha = 1 - (timeSinceChangedCollection * 4);
			using ( Gizmo.Scope( "Collection" ) )
			{
				Gizmo.Draw.Color = Gizmo.Colors.Pitch.WithAlpha( alpha.LerpTo( 1, 0.0f ) );
				Gizmo.Draw.SolidBox( CurrentGameObjectCollection.GetBounds() );

				Gizmo.Draw.Color = Gizmo.Colors.Pitch.WithAlpha( alpha.LerpTo( 1, 0.0f ) );
				Gizmo.Draw.LineBBox( CurrentGameObjectCollection.GetBounds() );
			}
		}
	}

	public void GroundGizmo( Ray cursorRay )
	{
		projectedPoint = ProjectRayOntoGroundPlane( cursorRay.Position, cursorRay.Forward, floors );

		if ( CurrentPaintMode == PaintMode.Decal ) return;

		using ( Gizmo.Scope( "Ground" ) )
		{
			Gizmo.Draw.LineThickness = 2;

			Gizmo.Draw.Color = Gizmo.Colors.Roll.WithAlpha(0.65f);
			Gizmo.Draw.Line( Vector3.Up , Vector3.Up * 16384 );
			Gizmo.Draw.Line( Vector3.Down , Vector3.Down * 16384 );

			Gizmo.Draw.Color = Gizmo.Colors.Yaw.WithAlpha( 0.65f );
			Gizmo.Draw.Line( Vector3.Left, Vector3.Left * 16384 );
			Gizmo.Draw.Line( Vector3.Right, Vector3.Right * 16384 );

			Gizmo.Draw.Color = Gizmo.Colors.Pitch.WithAlpha( 0.65f );
			Gizmo.Draw.Line( Vector3.Backward, Vector3.Backward * 16384 );
			Gizmo.Draw.Line( Vector3.Forward, Vector3.Forward * 16384 );
		}

		if ( projectedPoint.Hit )
		{
			// Snap the projected point to the grid and adjust for floor height
			var snappedPosition = projectedPoint.EndPosition;

			Vector3 normal = Vector3.Up; // Default for Z-axis
			switch ( Axis )
			{
				case GroundAxis.X:
					normal = Vector3.Forward; // Normal for X-axis
					break;
				case GroundAxis.Y:
					normal = Vector3.Left; // Normal for Y-axis
					break;
					// Z-axis is already set as default
			}

			using ( Gizmo.Scope( "Ground") )
			{
				var axiscolor = Gizmo.Colors.Up;
				switch ( Axis )
				{
					case GroundAxis.X:
						axiscolor = Gizmo.Colors.Forward;
						break;
					case GroundAxis.Y:
						axiscolor = Gizmo.Colors.Left;
						break;
				}

				Gizmo.Draw.Color = axiscolor.WithAlpha(0.25f);

				// Calculate the half size of the grid spacing
				float halfGridSize = Gizmo.Settings.GridSpacing / 2.0f;

				// Create a flat box based on the grid spacing
				Vector3 minCorner = snappedPosition - new Vector3( halfGridSize, halfGridSize, 0 );
				switch ( Axis )
				{
					case GroundAxis.X:
						minCorner = snappedPosition - new Vector3( 0, halfGridSize, halfGridSize );
						break;
					case GroundAxis.Y:
						minCorner = snappedPosition - new Vector3( halfGridSize, 0, halfGridSize );
						break;
				}
				Vector3 maxCorner = snappedPosition + new Vector3( halfGridSize, halfGridSize, 0 );
				switch ( Axis )
				{
					case GroundAxis.X:
						maxCorner = snappedPosition + new Vector3( 0, halfGridSize, halfGridSize );
						break;
					case GroundAxis.Y:
						maxCorner = snappedPosition + new Vector3( halfGridSize, 0, halfGridSize );
						break;
				}
				var bbox = new BBox( minCorner, maxCorner );
				Gizmo.Draw.LineThickness = 6;
				Gizmo.Draw.Color = axiscolor.WithAlpha( 1f );
				Gizmo.Draw.LineBBox( bbox );
				Gizmo.Transform = new Transform( 0, Rotation.FromAxis( Vector3.Up, 45 ) );
			}
		}
	}

	void UpdatePaintObjectGizmo()
	{
		EndGameObjectGizmo();
	}

	public void PaintGizmos( SceneTraceResult tr )
	{
		// Do gizmos and stuff
		var cursorRay = Gizmo.CurrentRay;

		/*
		if ( !boxtr.Hit )
		{
			Vector3 rayOrigin = cursorRay.Position;
			Vector3 rayDirection = cursorRay.Forward;

			boxtr = ProjectRayOntoGroundPlane( rayOrigin, rayDirection, 0 );
		}
		*/
		if ( CurrentPaintMode == PaintMode.Place )
		{
			PlaceGameObjectGizmo( tr, cursorRay );		
		}

		if ( CurrentPaintMode != PaintMode.Place )
		{
		//	EndGameObjectGizmo();
		}

		if(CurrentPaintMode == PaintMode.Decal)
		{
			PlaceDecalObjectGizmo();
		}

		if ( CurrentPaintMode == PaintMode.Remove )
		{
			using ( Gizmo.Scope( "preview" ) )
			{
				var tr2 = Scene.Trace.Ray( cursorRay, 5000 )
						.UseRenderMeshes( true )
						.UsePhysicsWorld( false )
						.WithTag( "gridtile" )
						.Run();

				if ( tr2.Hit )
				{
					Gizmo.Draw.Color = Color.Red.WithAlpha( 0.5f );
					Gizmo.Draw.LineBBox( tr2.GameObject.GetBounds() );
					Gizmo.Draw.SolidBox( tr2.GameObject.GetBounds() );
				}
			}
		}
		else if ( CurrentPaintMode == PaintMode.Move )
		{
			using ( Gizmo.Scope( "preview" ) )
			{
				var tr2 = Scene.Trace.Ray( cursorRay, 5000 )
						.UseRenderMeshes( true )
						.UsePhysicsWorld( false )
						.WithTag( "gridtile" )
						.Run();
				
				if ( tr2.Hit && SelectedObject is null )
				{
					Gizmo.Draw.Color = Theme.Blue.WithAlpha( 0.5f );
					Gizmo.Draw.LineBBox( tr2.GameObject.GetBounds() );
					Gizmo.Draw.SolidBox( tr2.GameObject.GetBounds() );
				}
				else if ( SelectedObject is not null )
				{
					Gizmo.Draw.Color = Theme.Blue.WithAlpha( 0.5f );
					Gizmo.Draw.LineBBox( SelectedObject.GetBounds() );
					Gizmo.Draw.SolidBox( SelectedObject.GetBounds() );
				}
			}
		}
		else if ( CurrentPaintMode == PaintMode.Copy )
		{
			using ( Gizmo.Scope( "preview" ) )
			{
				var tr2 = Scene.Trace.Ray( cursorRay, 5000 )
						.UseRenderMeshes( true )
						.UsePhysicsWorld( false )
						.WithTag( "gridtile" )
						.Run();

				if ( CopyObject is not null )
				{
					Gizmo.Transform = new Transform( GetGizmoPosition( tr2, cursorRay ), Rotation.FromPitch( -90 ) * rotation);
					CopyGameObjectGizmo( tr, cursorRay );
					Gizmo.Draw.Color = Color.Yellow.WithAlpha( 0.15f );
					Gizmo.Draw.LineBBox( GizmoGameObject.GetBounds() );
					Gizmo.Draw.SolidBox( GizmoGameObject.GetBounds() );
				}
				else if ( tr2.Hit && CopyObject is null )
				{
					Gizmo.Draw.Color = Theme.Yellow.WithAlpha( 0.5f );
					Gizmo.Draw.LineBBox( tr2.GameObject.GetBounds() );
					Gizmo.Draw.SolidBox( tr2.GameObject.GetBounds() );
				}
			}
		}
	}
	void CopyGameObjectGizmo( SceneTraceResult trace, Ray cursorRay )
	{
		if ( CopyObject is null ) return;

		if ( GizmoGameObject is null )
		{
			/*
			SceneUtility.MakeGameObjectsUnique( CopyObject );
			//Log.Info( SelectedGameObject.Components.Count );
			GizmoGameObject = new GameObject();
			GizmoGameObject.Deserialize(CopyObject);
			*/
			GizmoGameObject = CopyObject.Clone();
			GizmoGameObject.MakeNameUnique();
			GizmoGameObject.Flags = GameObjectFlags.NotSaved | GameObjectFlags.Hidden;
			GizmoGameObject.Tags.Add( "isgizmoobject" );

		}

		if ( GizmoGameObject is not null )
		{
			GizmoGameObject.Transform.Position = GetGizmoPosition( trace, cursorRay );
			GizmoGameObject.Transform.Rotation = Rotation.FromPitch( -90 ) * rotation;
			Log.Info( "GizmoGameObject is not null" );
		}
	}
	List<DuplicatedItems> gizmoDuplicate = new List<DuplicatedItems>();
	GameObject GizmoDuplicateObject { get; set; }
	public void HandleDuplicate( SceneTraceResult trace, Ray cursorRay )
	{
		projectedPoint = ProjectRayOntoGroundPlane( cursorRay.Position, cursorRay.Forward, floors );

		if ( GizmoDuplicateObject is null )
		{
			GizmoDuplicateObject = new GameObject();
			GizmoDuplicateObject.MakeNameUnique();
			GizmoDuplicateObject.Flags = GameObjectFlags.NotSaved | GameObjectFlags.Hidden;
			GizmoDuplicateObject.Tags.Add( "isgizmoobject" );
			GizmoDuplicateObject.Transform.Position = projectedPoint.EndPosition;
		}
		
		foreach ( var obj in DuplicateObjectCollection )
		{
			if ( !gizmoDuplicate.Contains(obj) )
			{
				var dobj = obj.gameObject.Clone();
				dobj.Parent = GizmoDuplicateObject;
				dobj.Transform.Position = obj.position;
				dobj.Transform.Rotation = obj.rotation;
				dobj.MakeNameUnique();
				dobj.Flags = GameObjectFlags.NotSaved | GameObjectFlags.Hidden;
				dobj.Tags.Add( "isgizmoobject" );
				
				gizmoDuplicate.Add( obj ); 
				Log.Info( $"Duplicated:{obj.gameObject.Name}" );
			}
		}
		
		if ( GizmoDuplicateObject is not null )
		{
			GizmoDuplicateObject.Transform.Position = GetGizmoPosition( trace, cursorRay );
			GizmoDuplicateObject.Transform.Rotation = Rotation.FromPitch( -90 ) * rotation;

			using ( Gizmo.Scope( "selection_box" ) )
			{
				var rect = GizmoDuplicateObject.GetBounds();
				Gizmo.Draw.Color = Color.Blue.WithAlpha( 0.25f );
				Gizmo.Draw.SolidBox( rect );
				Gizmo.Draw.Color = Color.Blue;
				Gizmo.Draw.LineBBox( rect );
			}
		}
	}

	void EndDuplicateGizmo()
	{
		if ( GizmoDuplicateObject is not null && DuplicateObjectCollection is not null )
		{
			GizmoDuplicateObject.Destroy();
			GizmoDuplicateObject = null;
			gizmoDuplicate.Clear();
			DuplicateObjectCollection.Clear();

			Log.Info( "End Duplicate" );
		}
	}
	
	void PlaceGameObjectGizmo( SceneTraceResult trace, Ray cursorRay )
	{
		if ( SelectedJsonObject is not null )
		{
			if ( GizmoGameObject is null )
			{

					//Log.Info( SelectedGameObject.Components.Count );
					GizmoGameObject = new GameObject( true, "GizmoObject" );
					PrefabUtility.MakeGameObjectsUnique( SelectedJsonObject );
					GizmoGameObject.Deserialize( SelectedJsonObject );
					GizmoGameObject.MakeNameUnique();
					GizmoGameObject.Tags.RemoveAll();
					GizmoGameObject.Tags.Add( "isgizmoobject" );
					GizmoGameObject.Name = "GizmoObject";
					GizmoGameObject.Flags |= GameObjectFlags.NotSaved | GameObjectFlags.Hidden;

					Log.Info( "GizmoGameObject is null" );
			}
		}
		
		if(SelectedRandomJsonObject is not null)
		{
			if ( GizmoGameObject is null )
			{
				if ( SelectedRandomJsonObject.Count != 0 )
				{

					//Log.Info( SelectedGameObject.Components.Count );
					GizmoGameObject = new GameObject( true, "GizmoObject" );
					PrefabUtility.MakeGameObjectsUnique( SelectedRandomJsonObject.FirstOrDefault() );
					GizmoGameObject.Deserialize( SelectedRandomJsonObject.FirstOrDefault() );
					GizmoGameObject.MakeNameUnique();
					GizmoGameObject.Tags.RemoveAll();
					GizmoGameObject.Tags.Add( "isgizmoobject" );
					GizmoGameObject.Name = "GizmoObject";
					GizmoGameObject.Flags |= GameObjectFlags.NotSaved | GameObjectFlags.Hidden;

					Log.Info( "GizmoGameObject is not null" );
				}
			}
		}

		if ( GizmoGameObject is not null )
		{
			GizmoGameObject.Transform.Position = GetGizmoPosition( trace, cursorRay );
			GizmoGameObject.Transform.Rotation = Rotation.FromPitch( -90 ) * rotation;
		}

		//Log.Info( SelectedRandomJsonObject.Count );
	}

	void EndGameObjectGizmo()
	{
		if ( GizmoGameObject is not null )
		{
			GizmoGameObject.Destroy();
			GizmoGameObject = null;
		}
	}

	void PlaceDecalObjectGizmo( )
	{
		var cursorRay = Gizmo.CurrentRay;

		var trdecal = SceneEditorSession.Active.Scene.Trace
			.Ray( cursorRay, 5000 )
				.UseRenderMeshes( true )
				.UsePhysicsWorld( true )
				.Run();

		if ( SelectedJsonObject is not null )
		{
			if ( GizmoGameObject is null )
			{
				//Log.Info( SelectedGameObject.Components.Count );
				GizmoGameObject = new GameObject( true, "GizmoObject" );
				PrefabUtility.MakeGameObjectsUnique( SelectedJsonObject );
				GizmoGameObject.Deserialize( SelectedJsonObject );

				GizmoGameObject.MakeNameUnique();
				GizmoGameObject.Tags.RemoveAll();
				GizmoGameObject.Tags.Add( "isgizmoobject" );
				GizmoGameObject.Name = "GizmoObject";
				GizmoGameObject.Flags |= GameObjectFlags.NotSaved | GameObjectFlags.Hidden;

				Log.Info( "GizmoGameObject is null" );
			}
		}

		if ( GizmoGameObject is not null )
		{
			GizmoGameObject.Transform.Position = trdecal.HitPosition.SnapToGrid( Gizmo.Settings.GridSpacing / 2 ) + trdecal.Normal * 10.0f;
			GizmoGameObject.Transform.Rotation = Rotation.LookAt( trdecal.Normal ) * rotation;

			var decal = GizmoGameObject.Components.Get<DecalRenderer>( FindMode.EnabledInSelf );
			if ( decal is not null )
			{
				decal.Size = new Vector3( decalX, decalY, decalZ );
				decal.TriPlanar = DecalTriPlanar;
			}
		}
	}

	private Vector3 GetGizmoPosition( SceneTraceResult trace, Ray cursorRay )
	{
		trace = ProjectRayOntoGroundPlane( cursorRay.Position, cursorRay.Forward, floors );

		if ( trace.Hit )
		{
			var snappedPosition = trace.EndPosition;

			return snappedPosition;
		}

		return Vector3.Zero;
	}

}
using System.IO;
using Sandbox;

namespace LibraryPlus;

public static class Extensions
{
	public static string ReferenceIdent( this Package self )
	{
		return Package.FormatIdent( self.Org.Ident, self.Ident );
	}

	internal static bool IsLibraryPlus( this Package self )
	{
		return ReferenceIdent( self ) == "sandmod.libraryplus";
	}

	internal static bool IsLibraryPlus( this Library self )
	{
		return self.Package.IsLibraryPlus();
	}

	internal static void MoveIfNeeded( this DirectoryInfo self, string destination )
	{
		if ( self.Exists )
		{
			Directory.CreateDirectory( Path.Combine( destination, ".." ) );
			self.MoveTo( destination );
		}
	}

	internal static void DeleteIfNeeded( this DirectoryInfo self, bool recursive = false )
	{
		if ( self.Exists )
		{
			self.Delete( recursive );
		}
	}
}
using System.Collections.Generic;
using System.Text;

namespace Ink.Parsed
{
    public class ContentList : Parsed.Object
    {
        public bool dontFlatten { get; set; }

        public Runtime.Container runtimeContainer {
            get {
                return (Runtime.Container) this.runtimeObject;
            }
        }

        public ContentList (List<Parsed.Object> objects)
        {
            if( objects != null )
                AddContent (objects);
        }

        public ContentList (params Parsed.Object[] objects)
        {
            if (objects != null) {
                var objList = new List<Parsed.Object> (objects);
                AddContent (objList);
            }
        }
            
        public ContentList()
        {
        }

        public void TrimTrailingWhitespace()
        {
            for (int i = this.content.Count - 1; i >= 0; --i) {
                var text = this.content [i] as Text;
                if (text == null)
                    break;

                text.text = text.text.TrimEnd (' ', '\t');
                if (text.text.Length == 0)
                    this.content.RemoveAt (i);
                else
                    break;
            }
        }

        public override Runtime.Object GenerateRuntimeObject ()
        {
            var container = new Runtime.Container ();
            if (content != null) {
                foreach (var obj in content) {
                    var contentObjRuntime = obj.runtimeObject;

                    // Some objects (e.g. author warnings) don't generate runtime objects
                    if( contentObjRuntime )
                        container.AddContent (contentObjRuntime);
                }
            }

            if( dontFlatten )
                story.DontFlattenContainer (container);

            return container;
        }

        public override string ToString ()
        {
            var sb = new StringBuilder ();
            sb.Append ("ContentList(");
            sb.Append(string.Join (", ", content.ToStringsArray()));
            sb.Append (")");
            return sb.ToString ();
        }
    }
}


namespace Ink.Parsed
{
    public class Tag : Parsed.Object
    {

        public bool isStart;
        public bool inChoice;
        
        public override Runtime.Object GenerateRuntimeObject ()
        {
            if( isStart )
                return Runtime.ControlCommand.BeginTag();
            else
                return Runtime.ControlCommand.EndTag();
        }

        public override string ToString ()
        {
            if( isStart )
                return "#StartTag";
            else
                return "#EndTag";
        }
    }
}

using Editor;
using Sandbox;
using System.Linq;

namespace SFXR.Editor;

[CustomEditor( typeof( SFXREnvelope ) )]
public class SFXREnvelopeEditor : ComponentEditorWidget
{
    SFXREnvelope Envelope;

    public SFXREnvelopeEditor( SerializedObject obj ) : base( obj )
    {

        var defaultInspector = new PropertyControlSheet();
        defaultInspector.AddObject( obj );

        Envelope = obj.Targets.First() as SFXREnvelope;

        Layout = Layout.Column();
        Layout.Add( defaultInspector );
        Layout.AddSpacingCell( 5 );

        Layout.AddSpacingCell( 90 );
    }

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

        Rect adsrRect = new Rect( 12, 138, Width - 30, 80 );

        Paint.SetBrush( Theme.ControlBackground );
        Paint.SetPen( Theme.ControlBackground );
        Paint.DrawRect( adsrRect, 2 );

        Paint.SetPen( Theme.Green, 2 );

        Envelope.GetCurve().DrawLine( adsrRect, 3, 0, 1 );

    }
}
using Editor;
using Sandbox;

[CustomEditor( typeof( WheelFrictionInfo ) )]
public class WheelFrictionInfoControlWidget : ControlWidget
{
	public override bool SupportsMultiEdit => true;

	public WheelFrictionInfoControlWidget( SerializedProperty property ) : base( property )
	{
		if ( !property.TryGetAsObject( out var obj ) )
		{
			Log.Error( "Couldn't get Wheel Friction Info" );
		}

		Layout = Layout.Column();
		Layout.Margin = 4f;

		Layout.Add( new Label( "Extremum" ) );
		var extremumRow = Layout.AddRow();
		extremumRow.Add( new FloatControlWidget( obj.GetProperty( "ExtremumSlip" ) ) { Label = "", Icon = "east", ToolTip = "Slip", HighlightColor = Theme.Red }, 1 );
		extremumRow.Add( new FloatControlWidget( obj.GetProperty( "ExtremumValue" ) ) { Label = "",Icon = "functions", ToolTip = "Value", HighlightColor = Theme.Red }, 1 );

		Layout.Add( new Separator( 10 ) );

		Layout.Add( new Label( "Asymptote" ) );
		var asymptoteRow = Layout.AddRow();
		asymptoteRow.Add( new FloatControlWidget( obj.GetProperty( "AsymptoteSlip" ) ) { Label = "", Icon = "east", ToolTip = "Slip", HighlightColor = Theme.Blue }, 1 );
		asymptoteRow.Add( new FloatControlWidget( obj.GetProperty( "AsymptoteValue" ) ) { Label = "",Icon = "functions", ToolTip = "Value", HighlightColor = Theme.Blue }, 1 );

		Layout.Add( new Separator( 10 ) );

		Layout.Add( new Label( "Stiffness" ) );
		Layout.Add( new FloatControlWidget( obj.GetProperty( "Stiffness" ) ) { Label = "", Icon = "vertical_align_center", ToolTip = "Stiffness", HighlightColor = Theme.Green }, 1 );
	}

	protected override void PaintUnder()
	{
		Paint.ClearPen();

		Rect localRect = LocalRect;
		Paint.SetBrush( ControlColor.Lighten( 0.25f ) );
		Paint.DrawRect( in localRect, ControlRadius );
	}
}
using Editor;

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