diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 558849b91..1a0103842 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -414,6 +414,10 @@ + + + + @@ -637,6 +641,7 @@ Code + Designer @@ -890,7 +895,7 @@ - + diff --git a/Artemis/Artemis/ArtemisBootstrapper.cs b/Artemis/Artemis/ArtemisBootstrapper.cs index 07e5ad234..f513d57d4 100644 --- a/Artemis/Artemis/ArtemisBootstrapper.cs +++ b/Artemis/Artemis/ArtemisBootstrapper.cs @@ -10,6 +10,7 @@ using Artemis.Utilities; using Artemis.Utilities.Converters; using Artemis.ViewModels; using Caliburn.Micro; +using MoonSharp.Interpreter; using Newtonsoft.Json; using Ninject; @@ -80,12 +81,15 @@ namespace Artemis _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); + // Configure JSON.NET var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, ContractResolver = _kernel.Get() }; JsonConvert.DefaultSettings = () => settings; + // Configure MoonSharp + UserData.RegisterAssembly(); } protected override void OnExit(object sender, EventArgs e) diff --git a/Artemis/Artemis/Profiles/Lua/LuaEventsWrapper.cs b/Artemis/Artemis/Profiles/Lua/LuaEventsWrapper.cs new file mode 100644 index 000000000..68d740b85 --- /dev/null +++ b/Artemis/Artemis/Profiles/Lua/LuaEventsWrapper.cs @@ -0,0 +1,9 @@ +using MoonSharp.Interpreter; + +namespace Artemis.Profiles.Lua +{ + [MoonSharpUserData] + public class LuaEventsWrapper + { + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Lua/LuaLayerWrapper.cs b/Artemis/Artemis/Profiles/Lua/LuaLayerWrapper.cs new file mode 100644 index 000000000..0a5ee5a16 --- /dev/null +++ b/Artemis/Artemis/Profiles/Lua/LuaLayerWrapper.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Linq; +using Artemis.Profiles.Layers.Models; +using MoonSharp.Interpreter; + +namespace Artemis.Profiles.Lua +{ + /// + /// Serves as a sandboxed wrapper around the LayerModel + /// + [MoonSharpUserData] + public class LuaLayerWrapper + { + private readonly LayerModel _layerModel; + + public LuaLayerWrapper(LayerModel layerModel) + { + _layerModel = layerModel; + } + + #region Child methods + + public List GetChildren() + { + return _layerModel.Children.Select(l => new LuaLayerWrapper(l)).ToList(); + } + + public LuaLayerWrapper GetChildByName(string name) + { + var layer = _layerModel.Children.FirstOrDefault(l => l.Name == name); + return layer == null ? null : new LuaLayerWrapper(layer); + } + + #endregion + + #region General layer properties + + public string Name + { + get { return _layerModel.Name; } + set { _layerModel.Name = value; } + } + + public bool Enabled + { + get { return _layerModel.Enabled; } + set { _layerModel.Enabled = value; } + } + + public bool IsEvent + { + get { return _layerModel.IsEvent; } + set { _layerModel.IsEvent = value; } + } + + public LuaLayerWrapper Parent => new LuaLayerWrapper(_layerModel.Parent); + + #endregion + + #region Advanced layer properties + + public double X + { + get { return _layerModel.Properties.X; } + set { _layerModel.Properties.X = value; } + } + + public double Y + { + get { return _layerModel.Properties.Y; } + set { _layerModel.Properties.Y = value; } + } + + public double Width + { + get { return _layerModel.Properties.Width; } + set { _layerModel.Properties.Width = value; } + } + + public double Height + { + get { return _layerModel.Properties.Height; } + set { _layerModel.Properties.Height = value; } + } + + public bool Contain + { + get { return _layerModel.Properties.Contain; } + set { _layerModel.Properties.Contain = value; } + } + + public double Opacity + { + get { return _layerModel.Properties.Opacity; } + set { _layerModel.Properties.Opacity = value; } + } + + public double AnimationSpeed + { + get { return _layerModel.Properties.AnimationSpeed; } + set { _layerModel.Properties.AnimationSpeed = value; } + } + + public double AnimationProgress + { + get { return _layerModel.Properties.AnimationProgress; } + set { _layerModel.Properties.AnimationProgress = value; } + } + + #endregion + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Lua/LuaProfileWrapper.cs b/Artemis/Artemis/Profiles/Lua/LuaProfileWrapper.cs new file mode 100644 index 000000000..512cd49ea --- /dev/null +++ b/Artemis/Artemis/Profiles/Lua/LuaProfileWrapper.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Linq; +using MoonSharp.Interpreter; + +namespace Artemis.Profiles.Lua +{ + /// + /// Serves as a sandboxed wrapper around the ProfileModel + /// + [MoonSharpUserData] + public class LuaProfileWrapper + { + private readonly ProfileModel _profileModel; + + public LuaProfileWrapper(ProfileModel profileModel) + { + _profileModel = profileModel; + } + + #region General profile properties + + public string Name => _profileModel.Name; + + #endregion + + #region Layer methods + + public List GetLayers() + { + return _profileModel.Layers.Select(l => new LuaLayerWrapper(l)).ToList(); + } + + public LuaLayerWrapper GetLayerByName(string name) + { + var layer = _profileModel.Layers.FirstOrDefault(l => l.Name == name); + return layer == null ? null : new LuaLayerWrapper(layer); + } + + #endregion + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Lua/LuaWrapper.cs b/Artemis/Artemis/Profiles/Lua/LuaWrapper.cs new file mode 100644 index 000000000..634db40b0 --- /dev/null +++ b/Artemis/Artemis/Profiles/Lua/LuaWrapper.cs @@ -0,0 +1,116 @@ +using System; +using System.IO; +using Artemis.Properties; +using Castle.Core.Internal; +using MoonSharp.Interpreter; +using NLog; +using NuGet; + +namespace Artemis.Profiles.Lua +{ + public class LuaWrapper + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public LuaWrapper(ProfileModel profileModel) + { + ProfileModel = profileModel; + LuaProfileWrapper = new LuaProfileWrapper(ProfileModel); + LuaEventsWrapper = new LuaEventsWrapper(); + + SetupLuaScript(); + } + + public ProfileModel ProfileModel { get; set; } + + public LuaEventsWrapper LuaEventsWrapper { get; set; } + + public LuaProfileWrapper LuaProfileWrapper { get; set; } + + public Script LuaScript { get; set; } + + private void SetupLuaScript() + { + LuaScript = new Script(CoreModules.Preset_SoftSandbox); + LuaScript.Options.DebugPrint = LuaPrint; + LuaScript.Globals["Profile"] = LuaProfileWrapper; + LuaScript.Globals["Events"] = LuaEventsWrapper; + + if (ProfileModel.LuaScript.IsNullOrEmpty()) + return; + + try + { + LuaScript.DoString(ProfileModel.LuaScript); + } + catch (ScriptRuntimeException e) + { + Logger.Error(e, "[{0}-LUA]: Error: {1}", ProfileModel.Name, e.DecoratedMessage); + } + } + + #region Private lua functions + + private void LuaPrint(string s) + { + Logger.Debug("[{0}-LUA]: {1}", ProfileModel.Name, s); + } + + #endregion + + #region Editor + + public void OpenEditor() + { + // Create a temp file + var fileName = Guid.NewGuid() + ".lua"; + var file = File.Create(Path.GetTempPath() + fileName); + file.Dispose(); + + // Add instructions to LUA script if it's a new file + if (ProfileModel.LuaScript.IsNullOrEmpty()) + ProfileModel.LuaScript = Resources.lua_placeholder; + File.WriteAllText(Path.GetTempPath() + fileName, ProfileModel.LuaScript); + + // Watch the file for changes + var watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); + watcher.Changed += LuaFileChanged; + watcher.EnableRaisingEvents = true; + + // Open the temp file with the default editor + System.Diagnostics.Process.Start(Path.GetTempPath() + fileName); + } + + private void LuaFileChanged(object sender, FileSystemEventArgs fileSystemEventArgs) + { + if (fileSystemEventArgs.ChangeType != WatcherChangeTypes.Changed) + return; + + using (var fs = new FileStream(fileSystemEventArgs.FullPath, + FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (var sr = new StreamReader(fs)) + { + ProfileModel.LuaScript = sr.ReadToEnd(); + } + } + + DAL.ProfileProvider.AddOrUpdate(ProfileModel); + SetupLuaScript(); + } + + #endregion + + #region Event triggers + + public void TriggerUpdate() + { + } + + public void TriggerDraw() + { + } + + #endregion + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/ProfileModel.cs b/Artemis/Artemis/Profiles/ProfileModel.cs index 145eb1917..3b0e01f68 100644 --- a/Artemis/Artemis/Profiles/ProfileModel.cs +++ b/Artemis/Artemis/Profiles/ProfileModel.cs @@ -8,6 +8,7 @@ using Artemis.DeviceProviders; using Artemis.Models.Interfaces; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Lua; using Artemis.Utilities; using Artemis.Utilities.ParentChild; using Newtonsoft.Json; @@ -22,9 +23,10 @@ namespace Artemis.Profiles public ProfileModel() { Layers = new ChildItemCollection(this); + LuaWrapper = new LuaWrapper(this); DrawingVisual = new DrawingVisual(); } - + /// /// Indicates whether the profile is actively being rendered /// @@ -38,10 +40,14 @@ namespace Artemis.Profiles public string GameName { get; set; } public int Width { get; set; } public int Height { get; set; } + public string LuaScript { get; set; } [JsonIgnore] public DrawingVisual DrawingVisual { get; set; } + [JsonIgnore] + public LuaWrapper LuaWrapper { get; set; } + public void FixOrder() { Layers.Sort(l => l.Order); diff --git a/Artemis/Artemis/Properties/Resources.Designer.cs b/Artemis/Artemis/Properties/Resources.Designer.cs index d414ddf78..39ed3e2b9 100644 --- a/Artemis/Artemis/Properties/Resources.Designer.cs +++ b/Artemis/Artemis/Properties/Resources.Designer.cs @@ -308,6 +308,15 @@ namespace Artemis.Properties { } } + /// + /// Looks up a localized string similar to . + /// + internal static string lua_placeholder { + get { + return ResourceManager.GetString("lua-placeholder", resourceCulture); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Artemis/Artemis/Properties/Resources.resx b/Artemis/Artemis/Properties/Resources.resx index 6bb8fcb3d..555a4ee25 100644 --- a/Artemis/Artemis/Properties/Resources.resx +++ b/Artemis/Artemis/Properties/Resources.resx @@ -208,4 +208,7 @@ ..\Resources\audio.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\lua-placeholder.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + \ No newline at end of file diff --git a/Artemis/Artemis/Resources/lua-placeholder.txt b/Artemis/Artemis/Resources/lua-placeholder.txt new file mode 100644 index 000000000..7641d7eff --- /dev/null +++ b/Artemis/Artemis/Resources/lua-placeholder.txt @@ -0,0 +1,12 @@ +-------------------------------------------------------------------------------- +------------------------------- Artemis LUA file ------------------------------- +-------------------------------------------------------------------------------- + +-- This is a default script to be executed by Artemis. +-- You do not need to use this if you don't want to script. The default profiles +-- should provide you with a lot of functionality out of the box. +-- However, if you wan't to change the way profiles work, this is the ideal way +-- go about it. + +-- Note: You are editing a temporary file. Whenever you save this file the +-- changes are applied to the profile and the script restarted. \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs index 0fd5e12ff..debe2e2dc 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs @@ -76,8 +76,8 @@ namespace Artemis.ViewModels.Profiles public bool EditorEnabled => - SelectedProfile != null && !SelectedProfile.IsDefault && - _mainManager.DeviceManager.ActiveKeyboard != null; + SelectedProfile != null && !SelectedProfile.IsDefault && + _mainManager.DeviceManager.ActiveKeyboard != null; public BindableCollection Profiles { @@ -669,6 +669,20 @@ namespace Artemis.ViewModels.Profiles ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); } + public void EditLua() + { + try + { + SelectedProfile?.LuaWrapper.OpenEditor(); + } + catch (Exception e) + { + DialogService.ShowMessageBox("Couldn't open LUA file", + "Please make sure you have a program such as Notepad associated with the .lua extension.\n\n" + + "Windows error message: \n" + e.Message); + } + } + private void EditorStateHandler(object sender, PropertyChangedEventArgs e) { if (e.PropertyName != "SelectedProfile") diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml b/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml index 97c6c1fe2..860787971 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml +++ b/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml @@ -115,7 +115,7 @@ Margin="5,5,0,0" Text="Note: To edit a default profile, duplicate it first." FontWeight="Bold" /> - + +