This commit is contained in:
Даниил Заикин
2026-06-17 20:44:53 +03:00
parent 9d153773c2
commit b133e7c656
1880 changed files with 244545 additions and 0 deletions
@@ -0,0 +1,78 @@
using System;
using System.Threading.Tasks;
namespace QFSW.QC.Actions
{
/// <summary>
/// Converts an async Task into an action.
/// </summary>
public class Async : ICommandAction
{
private readonly Task _task;
public bool IsFinished => _task.IsCompleted ||
_task.IsCanceled ||
_task.IsFaulted;
public bool StartsIdle => false;
/// <param name="task">The async Task to convert.</param>
public Async(Task task)
{
_task = task;
}
public void Start(ActionContext context) { }
public void Finalize(ActionContext context)
{
if (_task.IsFaulted)
{
throw _task.Exception.InnerException;
}
if (_task.IsCanceled)
{
throw new TaskCanceledException();
}
}
}
/// <summary>
/// Converts an async Task into an action.
/// </summary>
/// <typeparam name="T">The return type of the Task to convert.</typeparam>
public class Async<T> : ICommandAction
{
private readonly Task<T> _task;
private readonly Action<T> _onResult;
public bool IsFinished => _task.IsCompleted ||
_task.IsCanceled ||
_task.IsFaulted;
public bool StartsIdle => false;
/// <param name="task">The async Task to convert.</param>
/// <param name="onResult">The action to invoke when the Task completes.</param>
public Async(Task<T> task, Action<T> onResult)
{
_task = task;
_onResult = onResult;
}
public void Start(ActionContext context) { }
public void Finalize(ActionContext context)
{
if (_task.IsFaulted)
{
throw _task.Exception.InnerException;
}
if (_task.IsCanceled)
{
throw new TaskCanceledException();
}
_onResult(_task.Result);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aed3880bc1532c7418a020825bd88a2b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QFSW.QC.Utilities;
using UnityEngine;
namespace QFSW.QC.Actions
{
/// <summary>
/// Give the user a selection of choices which can be made by using the arrow keys and enter key.
/// </summary>
/// <typeparam name="T">The type of the choices.</typeparam>
public class Choice<T> : Composite
{
/// <summary>
/// Configuration for the Choice action.
/// </summary>
public struct Config
{
public string ItemFormat;
public string Delimiter;
public Color SelectedColor;
public static readonly Config Default = new Config
{
ItemFormat = "{0} [{1}]",
Delimiter = " ",
SelectedColor = Color.green
};
}
/// <param name="choices">The choices to select between.</param>
/// <param name="onSelect">Action to invoke when a selection is made.</param>
public Choice(IEnumerable<T> choices, Action<T> onSelect)
: this(choices, onSelect, Config.Default)
{ }
/// <param name="choices">The choices to select between.</param>
/// <param name="onSelect">Action to invoke when a selection is made.</param>
/// <param name="config">The configuration to be used.</param>
public Choice(IEnumerable<T> choices, Action<T> onSelect, Config config)
: base(Generate(choices, onSelect, config))
{ }
private static IEnumerator<ICommandAction> Generate(IEnumerable<T> choices, Action<T> onSelect, Config config)
{
QuantumConsole console = null;
StringBuilder builder = new StringBuilder();
IReadOnlyList<T> choiceList = choices as IReadOnlyList<T> ?? choices.ToList();
KeyCode key = KeyCode.None;
int choice = 0;
yield return new GetContext(ctx => console = ctx.Console);
ICommandAction DrawRow()
{
builder.Clear();
for (int i = 0; i < choiceList.Count; i++)
{
string item = console.Serialize(choiceList[i]);
builder.Append(i == choice
? string.Format(config.ItemFormat, item, 'x').ColorText(config.SelectedColor)
: string.Format(config.ItemFormat, item, ' '));
if (i != choiceList.Count - 1)
{
builder.Append(config.Delimiter);
}
}
return new Value(builder.ToString());
}
yield return DrawRow();
while (key != KeyCode.Return)
{
yield return new GetKey(k => key = k);
switch (key)
{
case KeyCode.LeftArrow: choice--; break;
case KeyCode.RightArrow: choice++; break;
case KeyCode.DownArrow: choice++; break;
case KeyCode.UpArrow: choice--; break;
}
choice = (choice + choiceList.Count) % choiceList.Count;
yield return new RemoveLog();
yield return DrawRow();
}
onSelect(choiceList[choice]);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 629e5579947e6da4688f17d834a20b56
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,35 @@
using System.Collections.Generic;
namespace QFSW.QC.Actions
{
/// <summary>
/// Combines a sequence of actions into a single action.
/// </summary>
public class Composite : ICommandAction
{
private ActionContext _context;
private readonly IEnumerator<ICommandAction> _actions;
public bool IsFinished => _actions.Execute(_context) == ActionState.Complete;
public bool StartsIdle => false;
/// <param name="actions">The sequence of actions to create the composite from.</param>
public Composite(IEnumerator<ICommandAction> actions)
{
_actions = actions;
}
/// <param name="actions">The sequence of actions to create the composite from.</param>
public Composite(IEnumerable<ICommandAction> actions) : this(actions.GetEnumerator())
{
}
public void Start(ActionContext context)
{
_context = context;
}
public void Finalize(ActionContext context) { }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ecb8c263614be7489f996de15278bf5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3dba2d4b65cfdad428d18b1bd91c54e7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
namespace QFSW.QC
{
/// <summary>
/// The context that an action is being invoked on.
/// </summary>
public struct ActionContext
{
public QuantumConsole Console;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16c86e1b478f19a4fa02d70bd8aafcde
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
using System.Collections.Generic;
namespace QFSW.QC
{
public static class ActionExecuter
{
/// <summary>
/// Executes an action command until it becomes idle.
/// </summary>
/// <param name="action">The action command to execute.</param>
/// <param name="context">The context that the command is being executed on.</param>
/// <returns>The current state of the action command.</returns>
public static ActionState Execute(this IEnumerator<ICommandAction> action, ActionContext context)
{
ActionState state = ActionState.Running;
bool idle = false;
void MoveNext()
{
if (action.MoveNext())
{
action.Current?.Start(context);
idle = action.Current?.StartsIdle ?? false;
}
else
{
idle = true;
state = ActionState.Complete;
action.Dispose();
}
}
while (!idle)
{
if (action.Current == null)
{
MoveNext();
}
else if (action.Current.IsFinished)
{
action.Current.Finalize(context);
MoveNext();
}
else
{
idle = true;
}
}
return state;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc98ecd40616cc142a69f2c94957435f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,12 @@
namespace QFSW.QC
{
/// <summary>
/// The execution state of an action.
/// </summary>
public enum ActionState
{
Unknown = 0,
Running = 1,
Complete = 2
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c68b189efbe61cc41af4263794f5b1b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,31 @@
namespace QFSW.QC
{
/// <summary>
/// Creates an action that can be yielded in commands.
/// </summary>
public interface ICommandAction
{
/// <summary>
/// Starts the action.
/// </summary>
/// <param name="context">The context that the action is being executed on.</param>
void Start(ActionContext context);
/// <summary>
/// Finalizes the action. Should not be called unless <c>IsFinished</c> is true.
/// </summary>
/// <param name="context">The context that the action is being executed on.</param>
void Finalize(ActionContext context);
/// <summary>
/// If the action has finished. Should not be called before <c>Start</c>.
/// </summary>
bool IsFinished { get; }
/// <summary>
/// If the action should start off idle, causing the execution to suspend until executed again.
/// It is recommended to make this <c>false</c> if the action should be instant.
/// </summary>
bool StartsIdle { get; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ad6ee627978b454d9938922d0e5415c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,36 @@
using System;
namespace QFSW.QC.Actions
{
/// <summary>
/// Custom action implemented via delegates.
/// For more complex actions it is usually recommended to create a new action implementing <c>ICommandAction</c>.
/// </summary>
public class Custom : ICommandAction
{
private readonly Func<bool> _isFinished;
private readonly Func<bool> _startsIdle;
private readonly Action<ActionContext> _start;
private readonly Action<ActionContext> _finalize;
public Custom(
Func<bool> isFinished,
Func<bool> startsIdle,
Action<ActionContext> start,
Action<ActionContext> finalize
)
{
_isFinished = isFinished;
_startsIdle = startsIdle;
_start = start;
_finalize = finalize;
}
public bool IsFinished => _isFinished();
public bool StartsIdle => _startsIdle();
public void Start(ActionContext context) { _start(context); }
public void Finalize(ActionContext context) { _finalize(context); }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b9bd021bf2e291447a56d4cede936d56
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
using System;
namespace QFSW.QC.Actions
{
/// <summary>
/// Gets the <c>ActionContext</c> that the command is currently being invoked on.
/// </summary>
public class GetContext : ICommandAction
{
private readonly Action<ActionContext> _onContext;
public bool IsFinished => true;
public bool StartsIdle => false;
/// <param name="onContext">Action to invoke when the context is retrieved.</param>
public GetContext(Action<ActionContext> onContext)
{
_onContext = onContext;
}
public void Start(ActionContext context) { }
public void Finalize(ActionContext context)
{
_onContext(context);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2b0f8e4c27c89e4caac865a57f52f99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using System;
using System.Linq;
using UnityEngine;
namespace QFSW.QC.Actions
{
/// <summary>
/// Waits for any key to be pressed and returns the key via the given delegate.
/// </summary>
public class GetKey : ICommandAction
{
private KeyCode _key;
private readonly Action<KeyCode> _onKey;
private static readonly KeyCode[] KeyCodes = Enum.GetValues(typeof(KeyCode))
.Cast<KeyCode>()
.Where(k => (int)k < (int)KeyCode.Mouse0)
.ToArray();
public bool IsFinished
{
get
{
_key = GetCurrentKeyDown();
return _key != KeyCode.None;
}
}
public bool StartsIdle => true;
/// <param name="onKey">The action to perform when a key is pressed.</param>
public GetKey(Action<KeyCode> onKey)
{
_onKey = onKey;
}
private KeyCode GetCurrentKeyDown()
{
return KeyCodes.FirstOrDefault(InputHelper.GetKeyDown);
}
public void Start(ActionContext context) { }
public void Finalize(ActionContext context)
{
_onKey(_key);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a0a962a9335e7e408af971ed79d4e8e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,63 @@
using System;
namespace QFSW.QC.Actions
{
/// <summary>
/// Gets the next line of text entered into the console as a user
/// response instead of invoking it as a command.
/// </summary>
public class ReadLine : ICommandAction
{
private readonly Action<string> _getInput;
private readonly ResponseConfig _config;
private QuantumConsole _console;
private string _response;
public bool IsFinished => _response != null;
public bool StartsIdle => true;
/// <param name="getInput">A delegate which returns the input entered by the user.</param>
/// <param name="config">The config to provide the response flow with.</param>
/// <exception cref="ArgumentNullException"></exception>
public ReadLine(Action<string> getInput, ResponseConfig config)
{
// validate
if (getInput == null)
{
throw new ArgumentNullException(nameof(getInput));
}
// set fields
_getInput = getInput;
_config = config;
_console = null;
_response = null;
}
/// <param name="getInput">A delegate which returns the input entered by the user.</param>
/// <param name="config">The config to provide the response flow with.</param>
/// <exception cref="ArgumentNullException"></exception>
public ReadLine(Action<string> getInput) : this(getInput, ResponseConfig.Default)
{
}
public void Finalize(ActionContext context)
{
_getInput(_response); // push value to the caller
}
public void Start(ActionContext context)
{
_response = null; // reset flag
_console = context.Console;
_console.BeginResponse(OnResponseSubmittedHandler, _config);
}
private void OnResponseSubmittedHandler(string response)
{
_response = response; // changes IsFinished flag
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7cda4247941153d44bdace0c87133c6d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
namespace QFSW.QC.Actions
{
/// <summary>
/// Gets the next line of text entered into the console as a user response
/// and parses it to a value of the specified type.
/// </summary>
public class ReadValue<T> : Composite
{
private static readonly QuantumParser Parser = new QuantumParser();
/// <param name="getValue">A delegate which returns the parsed value entered by the user.</param>
/// <param name="config">The config to provide the response flow with.</param>
public ReadValue(Action<T> getValue, ResponseConfig config)
: base(Generate(getValue, config))
{ }
/// <param name="getValue">A delegate which returns the parsed value entered by the user.</param>
public ReadValue(Action<T> getValue)
: this(getValue, ResponseConfig.Default)
{ }
private static IEnumerator<ICommandAction> Generate(Action<T> getValue, ResponseConfig config)
{
string line = default;
yield return new ReadLine(t => line = t, config);
T value = Parser.Parse<T>(line);
getValue(value);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 13f674045cd08b947b6cc784b98e45f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
namespace QFSW.QC.Actions
{
/// <summary>
/// Removes the most recent log from the console.
/// </summary>
public class RemoveLog : ICommandAction
{
public bool IsFinished => true;
public bool StartsIdle => false;
public void Start(ActionContext context) { }
public void Finalize(ActionContext context)
{
context.Console.RemoveLogTrace();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a4e97d9251aaa994aa83824d9bccfd59
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace QFSW.QC.Actions
{
/// <summary>
/// Gradually types a message to the console.
/// </summary>
public class Typewriter : Composite
{
/// <summary>
/// Configuration for the Typewriter action.
/// </summary>
public struct Config
{
public enum ChunkType
{
Character,
Word,
Line
}
public float PrintInterval;
public ChunkType Chunks;
public static readonly Config Default = new Config
{
PrintInterval = 0f,
Chunks = ChunkType.Character
};
}
private static readonly Regex WhiteRegex = new Regex(@"(?<=[\s+])", RegexOptions.Compiled);
private static readonly Regex LineRegex = new Regex(@"(?<=[\n+])", RegexOptions.Compiled);
/// <param name="message">The message to display to the console.</param>
public Typewriter(string message)
: this(message, Config.Default)
{ }
/// <param name="message">The message to display to the console.</param>
/// <param name="config">The configuration to be used.</param>
public Typewriter(string message, Config config)
: base(Generate(message, config))
{ }
private static IEnumerator<ICommandAction> Generate(string message, Config config)
{
string[] chunks;
switch (config.Chunks)
{
case Config.ChunkType.Character: chunks = message.Select(c => c.ToString()).ToArray(); break;
case Config.ChunkType.Word: chunks = WhiteRegex.Split(message); break;
case Config.ChunkType.Line: chunks = LineRegex.Split(message); break;
default: throw new ArgumentException($"Chunk type {config.Chunks} is not supported.");
}
for (int i = 0; i < chunks.Length; i++)
{
yield return new WaitRealtime(config.PrintInterval);
yield return new Value(chunks[i], i == 0);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea886313beb68ed45b9b597a71c3514a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,31 @@
namespace QFSW.QC.Actions
{
/// <summary>
/// Serializes and logs a value to the console.
/// </summary>
public class Value : ICommandAction
{
private readonly object _value;
private readonly bool _newline;
public bool IsFinished => true;
public bool StartsIdle => false;
/// <param name="value">The value to log to the console.</param>
/// <param name="newline">If the value should be logged on a new line.</param>
public Value(object value, bool newline = true)
{
_value = value;
_newline = newline;
}
public void Start(ActionContext context) { }
public void Finalize(ActionContext context)
{
QuantumConsole console = context.Console;
string serialized = _value as string ?? console.Serialize(_value);
console.LogToConsole(serialized, _newline);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa35d5d3d1cdc54429fbcd0a8754323f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using UnityEngine;
namespace QFSW.QC.Actions
{
/// <summary>
/// Waits for the given amount of seconds using scaled time.
/// </summary>
public class Wait : ICommandAction
{
private float _startTime;
private readonly float _duration;
public bool IsFinished => Time.time >= _startTime + _duration;
public bool StartsIdle => true;
/// <param name="seconds">The duration to wait in seconds.</param>
public Wait(float seconds)
{
_duration = seconds;
}
public void Start(ActionContext ctx)
{
_startTime = Time.time;
}
public void Finalize(ActionContext ctx) { }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da3e71593087b0145b4c7416865c73ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
namespace QFSW.QC.Actions
{
/// <summary>
/// Waits until the next frame.
/// </summary>
public class WaitFrame : WaitRealtime
{
public WaitFrame() : base(0)
{
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ec7bf5a05072b33469812750790bff62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using UnityEngine;
namespace QFSW.QC.Actions
{
/// <summary>
/// Waits until the given key is pressed.
/// </summary>
public class WaitKey : WaitUntil
{
/// <param name="key">The key to wait for.</param>
public WaitKey(KeyCode key) : base(() => InputHelper.GetKeyDown(key))
{
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 67f025b392a312e45bb0ed80959d5cd1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using UnityEngine;
namespace QFSW.QC.Actions
{
/// <summary>
/// Waits for the given amount of seconds using real time.
/// </summary>
public class WaitRealtime : ICommandAction
{
private float _startTime;
private readonly float _duration;
public bool IsFinished => Time.realtimeSinceStartup >= _startTime + _duration;
public bool StartsIdle => true;
/// <param name="seconds">The duration to wait for in seconds.</param>
public WaitRealtime(float seconds)
{
_duration = seconds;
}
public void Start(ActionContext ctx)
{
_startTime = Time.realtimeSinceStartup;
}
public void Finalize(ActionContext ctx) { }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32cadea9087bc5d4682ac8455fde1fd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,16 @@
using System;
namespace QFSW.QC.Actions
{
/// <summary>
/// Waits until the given condition is met.
/// </summary>
public class WaitUntil : WaitWhile
{
/// <param name="condition">The condition to wait on.</param>
public WaitUntil(Func<bool> condition) : base(() => !condition())
{
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10b1ff66cd0d51d4a9363f7ad3477830
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,25 @@
using System;
namespace QFSW.QC.Actions
{
/// <summary>
/// Waits while the given condition is met.
/// </summary>
public class WaitWhile : ICommandAction
{
private readonly Func<bool> _condition;
public bool IsFinished => !_condition();
public bool StartsIdle => true;
/// <param name="condition">The condition to wait on.</param>
public WaitWhile(Func<bool> condition)
{
_condition = condition;
}
public void Start(ActionContext context) { }
public void Finalize(ActionContext context) { }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8050f33217e30684a9a0ea5dfefdfd45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: