Search the source of every open source package.
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 Editor;
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!" );
}
}