diff --git a/.gitignore b/.gitignore
index b7e11e554..30c2caeb2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,6 @@ Source/*.Gen.*
Source/*.csproj
/Package_*/
!Source/Engine/Debug
-/Source/Platforms/Editor/Linux/Mono/etc/mono/registry
PackageEditor_Cert.command
PackageEditor_Cert.bat
PackagePlatforms_Cert.bat
diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax
new file mode 100644
index 000000000..39964e6c5
--- /dev/null
+++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:19fa43eb7b31ee3936348b1f271c464c79d7020a21d33e3cdbe54f98c3b14304
+size 10560899
diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings
index ff396d824..80cf8fa1e 100644
--- a/Flax.sln.DotSettings
+++ b/Flax.sln.DotSettings
@@ -73,8 +73,12 @@
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ AI
LO
+ RPC
+ SDK
VS
+ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
diff --git a/Source/Editor/Content/Proxy/ScriptProxy.cs b/Source/Editor/Content/Proxy/ScriptProxy.cs
index f688f37df..d728dcc38 100644
--- a/Source/Editor/Content/Proxy/ScriptProxy.cs
+++ b/Source/Editor/Content/Proxy/ScriptProxy.cs
@@ -69,8 +69,7 @@ namespace FlaxEditor.Content
///
public override bool IsFileNameValid(string filename)
{
- // Scripts cannot start with digit.
- if (Char.IsDigit(filename[0]))
+ if (char.IsDigit(filename[0]))
return false;
if (filename.Equals("Script"))
return false;
diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
index e1a358b6e..63374361d 100644
--- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
+++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using FlaxEditor.Actions;
using FlaxEditor.Content;
using FlaxEditor.GUI;
@@ -16,6 +18,27 @@ using Object = FlaxEngine.Object;
namespace FlaxEditor.CustomEditors.Dedicated
{
+ internal class NewScriptItem : ItemsListContextMenu.Item
+ {
+ private string _scriptName;
+
+ public string ScriptName
+ {
+ get => _scriptName;
+ set
+ {
+ _scriptName = value;
+ Name = $"Create script '{value}'";
+ }
+ }
+
+ public NewScriptItem(string scriptName)
+ {
+ ScriptName = scriptName;
+ TooltipText = "Create a new script";
+ }
+ }
+
///
/// Drag and drop scripts area control.
///
@@ -74,7 +97,43 @@ namespace FlaxEditor.CustomEditors.Dedicated
{
cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i]));
}
- cm.ItemClicked += item => AddScript((ScriptType)item.Tag);
+ cm.TextChanged += text =>
+ {
+ if (!IsValidScriptName(text))
+ return;
+ if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem))
+ {
+ // If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time
+ var newScriptItem = (NewScriptItem)cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem);
+ if (newScriptItem != null)
+ {
+ newScriptItem.Visible = true;
+ newScriptItem.ScriptName = text;
+ }
+ else
+ {
+ cm.AddItem(new NewScriptItem(text));
+ }
+ }
+ else
+ {
+ // Make sure to hide the create script button if there
+ var newScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem);
+ if (newScriptItem != null)
+ newScriptItem.Visible = false;
+ }
+ };
+ cm.ItemClicked += item =>
+ {
+ if (item.Tag is ScriptType script)
+ {
+ AddScript(script);
+ }
+ else if (item is NewScriptItem newScriptItem)
+ {
+ CreateScript(newScriptItem);
+ }
+ };
cm.SortItems();
cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0));
}
@@ -113,6 +172,19 @@ namespace FlaxEditor.CustomEditors.Dedicated
return false;
}
+ private static bool IsValidScriptName(string text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return false;
+ if (text.Contains(' '))
+ return false;
+ if (char.IsDigit(text[0]))
+ return false;
+ if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
+ return false;
+ return Editor.Instance.ContentDatabase.GetProxy("cs").IsFileNameValid(text);
+ }
+
///
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
{
@@ -163,6 +235,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
if (_dragScripts.HasValidDrag)
{
result = _dragScripts.Effect;
+
AddScripts(_dragScripts.Objects);
}
else if (_dragAssets.HasValidDrag)
@@ -177,7 +250,43 @@ namespace FlaxEditor.CustomEditors.Dedicated
return result;
}
- private void AddScript(ScriptType item)
+ private void CreateScript(NewScriptItem item)
+ {
+ ScriptsEditor.NewScriptName = item.ScriptName;
+ var paths = Directory.GetFiles(Globals.ProjectSourceFolder, "*.Build.cs");
+
+ string moduleName = null;
+ foreach (var p in paths)
+ {
+ var file = File.ReadAllText(p);
+ if (!file.Contains("GameProjectTarget"))
+ continue; // Skip
+
+ if (file.Contains("Modules.Add(\"Game\")"))
+ {
+ // Assume Game represents the main game module
+ moduleName = "Game";
+ break;
+ }
+ }
+
+ // Ensure the path slashes are correct for the OS
+ var correctedPath = Path.GetFullPath(Globals.ProjectSourceFolder);
+ if (string.IsNullOrEmpty(moduleName))
+ {
+ var error = FileSystem.ShowBrowseFolderDialog(Editor.Instance.Windows.MainWindow, correctedPath, "Select a module folder to put the new script in", out moduleName);
+ if (error)
+ return;
+ }
+ var path = Path.Combine(Globals.ProjectSourceFolder, moduleName, item.ScriptName + ".cs");
+ Editor.Instance.ContentDatabase.GetProxy("cs").Create(path, null);
+ }
+
+ ///
+ /// Attach a script to the actor.
+ ///
+ /// The script.
+ public void AddScript(ScriptType item)
{
var list = new List(1) { item };
AddScripts(list);
@@ -224,16 +333,67 @@ namespace FlaxEditor.CustomEditors.Dedicated
private void AddScripts(List items)
{
- var actions = new List(4);
+ var actions = new List();
for (int i = 0; i < items.Count; i++)
{
var scriptType = items[i];
+ RequireScriptAttribute scriptAttribute = null;
+ if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false))
+ {
+ foreach (var e in scriptType.GetAttributes(false))
+ {
+ if (e is not RequireScriptAttribute requireScriptAttribute)
+ continue;
+ scriptAttribute = requireScriptAttribute;
+ break;
+ }
+ }
+
+ // See if script requires a specific actor type
+ RequireActorAttribute actorAttribute = null;
+ if (scriptType.HasAttribute(typeof(RequireActorAttribute), false))
+ {
+ foreach (var e in scriptType.GetAttributes(false))
+ {
+ if (e is not RequireActorAttribute requireActorAttribute)
+ continue;
+ actorAttribute = requireActorAttribute;
+ break;
+ }
+ }
+
var actors = ScriptsEditor.ParentEditor.Values;
for (int j = 0; j < actors.Count; j++)
{
var actor = (Actor)actors[j];
+
+ // If required actor exists but is not this actor type then skip adding to actor
+ if (actorAttribute != null)
+ {
+ if (actor.GetType() != actorAttribute.RequiredType && !actor.GetType().IsSubclassOf(actorAttribute.RequiredType))
+ {
+ Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` not added to `{actor}` due to script requiring an Actor type of `{actorAttribute.RequiredType}`.");
+ continue;
+ }
+ }
+
actions.Add(AddRemoveScript.Add(actor, scriptType));
+ // Check if actor has required scripts and add them if the actor does not.
+ if (scriptAttribute != null)
+ {
+ foreach (var type in scriptAttribute.RequiredTypes)
+ {
+ if (!type.IsSubclassOf(typeof(Script)))
+ {
+ Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(type.Name)}` not added to `{actor}` due to the class not being a subclass of Script.");
+ continue;
+ }
+ if (actor.GetScript(type) != null)
+ continue;
+ actions.Add(AddRemoveScript.Add(actor, new ScriptType(type)));
+ }
+ }
}
}
@@ -440,6 +600,11 @@ namespace FlaxEditor.CustomEditors.Dedicated
///
public override IEnumerable