mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-25 21:31:14 +00:00
e2374102ee
Script run by UDBScript are now executed asynchronously
679 lines
19 KiB
C#
679 lines
19 KiB
C#
#region ================== Copyright (c) 2020 Boris Iwanski
|
|
|
|
/*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
*
|
|
* it under the terms of the GNU General Public License as published by
|
|
*
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
*
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
|
*
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program.If not, see<http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#endregion
|
|
|
|
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Dynamic;
|
|
using System.Windows.Forms;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Text.RegularExpressions;
|
|
using CodeImp.DoomBuilder.Actions;
|
|
using CodeImp.DoomBuilder.Controls;
|
|
using CodeImp.DoomBuilder.Geometry;
|
|
using CodeImp.DoomBuilder.IO;
|
|
using CodeImp.DoomBuilder.Map;
|
|
using CodeImp.DoomBuilder.Plugins;
|
|
using CodeImp.DoomBuilder.Types;
|
|
using CodeImp.DoomBuilder.UDBScript.Wrapper;
|
|
using CodeImp.DoomBuilder.Windows;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.UDBScript
|
|
{
|
|
internal class ScriptDirectoryStructure
|
|
{
|
|
public string Name;
|
|
public bool Expanded;
|
|
public string Hash;
|
|
public List<ScriptDirectoryStructure> Directories;
|
|
public List<ScriptInfo> Scripts;
|
|
|
|
public ScriptDirectoryStructure(string name, bool expanded, string hash)
|
|
{
|
|
Name = name;
|
|
Expanded = expanded;
|
|
Hash = hash;
|
|
Directories = new List<ScriptDirectoryStructure>();
|
|
Scripts = new List<ScriptInfo>();
|
|
}
|
|
}
|
|
|
|
public class BuilderPlug : Plug
|
|
{
|
|
#region ================== Constants
|
|
|
|
private static readonly string SCRIPT_FOLDER = "udbscript";
|
|
public static readonly uint UDB_SCRIPT_VERSION = 3;
|
|
|
|
#endregion
|
|
|
|
private delegate void CallVoidMethodDeletage();
|
|
|
|
#region ================== Constants
|
|
|
|
public const int NUM_SCRIPT_SLOTS = 30;
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
private static BuilderPlug me;
|
|
private ScriptDockerControl panel;
|
|
private Docker docker;
|
|
private string currentscriptfile;
|
|
private ScriptInfo currentscript;
|
|
private ScriptRunner scriptrunner;
|
|
private List<ScriptInfo> scriptinfo;
|
|
private ScriptDirectoryStructure scriptdirectorystructure;
|
|
private FileSystemWatcher watcher;
|
|
private object lockobj;
|
|
private Dictionary<int, ScriptInfo> scriptslots;
|
|
private string editorexepath;
|
|
private PreferencesForm preferencesform;
|
|
private ScriptRunnerForm scriptrunnerform;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
public static BuilderPlug Me { get { return me; } }
|
|
public string CurrentScriptFile { get { return currentscriptfile; } set { currentscriptfile = value; } }
|
|
internal ScriptInfo CurrentScript { get { return currentscript; } set { currentscript = value; } }
|
|
internal ScriptRunner ScriptRunner { get { return scriptrunner; } }
|
|
internal ScriptDirectoryStructure ScriptDirectoryStructure { get { return scriptdirectorystructure; } }
|
|
internal string EditorExePath { get { return editorexepath; } }
|
|
public ScriptRunnerForm ScriptRunnerForm { get { return scriptrunnerform; } }
|
|
|
|
#endregion
|
|
|
|
public override void OnInitialize()
|
|
{
|
|
base.OnInitialize();
|
|
|
|
me = this;
|
|
|
|
lockobj = new object();
|
|
|
|
scriptinfo = new List<ScriptInfo>();
|
|
scriptslots = new Dictionary<int, ScriptInfo>();
|
|
|
|
panel = new ScriptDockerControl(SCRIPT_FOLDER);
|
|
docker = new Docker("udbscript", "Scripts", panel);
|
|
General.Interface.AddDocker(docker);
|
|
|
|
General.Actions.BindMethods(this);
|
|
|
|
watcher = new FileSystemWatcher(Path.Combine(General.AppPath, SCRIPT_FOLDER, "scripts"));
|
|
watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size;
|
|
watcher.IncludeSubdirectories = true;
|
|
watcher.Changed += OnWatcherEvent;
|
|
watcher.Created += OnWatcherEvent;
|
|
watcher.Deleted += OnWatcherEvent;
|
|
watcher.Renamed += OnWatcherEvent;
|
|
|
|
editorexepath = General.Settings.ReadPluginSetting("externaleditor", string.Empty);
|
|
|
|
scriptrunnerform = new ScriptRunnerForm();
|
|
|
|
FindEditor();
|
|
}
|
|
|
|
public override void OnMapNewEnd()
|
|
{
|
|
base.OnMapNewEnd();
|
|
|
|
// Methods called by LoadScripts might sleep for some time, so call LoadScripts asynchronously
|
|
new Task(LoadScripts).Start();
|
|
|
|
watcher.EnableRaisingEvents = true;
|
|
}
|
|
|
|
public override void OnMapOpenEnd()
|
|
{
|
|
base.OnMapOpenEnd();
|
|
|
|
// Methods called by LoadScripts might sleep for some time, so call LoadScripts asynchronously
|
|
new Task(LoadScripts).Start();
|
|
|
|
watcher.EnableRaisingEvents = true;
|
|
}
|
|
|
|
public override void OnMapCloseBegin()
|
|
{
|
|
watcher.EnableRaisingEvents = false;
|
|
|
|
SaveScriptSlotsAndOptions();
|
|
SaveScriptDirectoryExpansionStatus(scriptdirectorystructure);
|
|
}
|
|
|
|
public override void OnShowPreferences(PreferencesController controller)
|
|
{
|
|
base.OnShowPreferences(controller);
|
|
|
|
preferencesform = new PreferencesForm();
|
|
preferencesform.Setup(controller);
|
|
}
|
|
|
|
public override void OnClosePreferences(PreferencesController controller)
|
|
{
|
|
base.OnClosePreferences(controller);
|
|
|
|
preferencesform.Dispose();
|
|
preferencesform = null;
|
|
}
|
|
|
|
private void OnWatcherEvent(object sender, FileSystemEventArgs e)
|
|
{
|
|
// We can't use the filter on the watcher, since for whatever reason that filter also applies to
|
|
// directory names. So we have to do some filtering ourselves.
|
|
bool load = false;
|
|
if (e.ChangeType == WatcherChangeTypes.Deleted || (Directory.Exists(e.FullPath) && e.ChangeType != WatcherChangeTypes.Changed) || Path.GetExtension(e.FullPath).ToLowerInvariant() == ".js")
|
|
load = true;
|
|
|
|
if(load)
|
|
LoadScripts();
|
|
}
|
|
|
|
// This is called when the plugin is terminated
|
|
public override void Dispose()
|
|
{
|
|
base.Dispose();
|
|
|
|
// This must be called to remove bound methods for actions.
|
|
General.Actions.UnbindMethods(this);
|
|
}
|
|
|
|
internal void SaveScriptSlotsAndOptions()
|
|
{
|
|
// Save the script option values
|
|
foreach (ScriptInfo si in scriptinfo)
|
|
si.SaveOptionValues();
|
|
|
|
// Save the script slots
|
|
foreach (KeyValuePair<int, ScriptInfo> kvp in scriptslots)
|
|
{
|
|
if (kvp.Value == null || string.IsNullOrWhiteSpace(kvp.Value.ScriptFile))
|
|
continue;
|
|
|
|
General.Settings.WritePluginSetting("scriptslots.slot" + kvp.Key, kvp.Value.ScriptFile);
|
|
}
|
|
}
|
|
|
|
internal void SaveScriptDirectoryExpansionStatus(ScriptDirectoryStructure root)
|
|
{
|
|
if(root.Expanded)
|
|
{
|
|
General.Settings.DeletePluginSetting("directoryexpand." + root.Hash);
|
|
}
|
|
else
|
|
{
|
|
General.Settings.WritePluginSetting("directoryexpand." + root.Hash, false);
|
|
}
|
|
|
|
foreach (ScriptDirectoryStructure sds in root.Directories)
|
|
SaveScriptDirectoryExpansionStatus(sds);
|
|
}
|
|
|
|
private void FindEditor()
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(editorexepath))
|
|
return;
|
|
|
|
string editor = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "notepad.exe");
|
|
|
|
if (!File.Exists(editor))
|
|
return;
|
|
|
|
editorexepath = editor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the new external editor exe path.
|
|
/// </summary>
|
|
/// <param name="exepath">Path and file name of the external editor</param>
|
|
internal void SetEditor(string exepath)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(exepath))
|
|
{
|
|
editorexepath = exepath;
|
|
General.Settings.WritePluginSetting("externaleditor", editorexepath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens a script in the external editor.
|
|
/// </summary>
|
|
/// <param name="file"></param>
|
|
internal void EditScript(string file)
|
|
{
|
|
if(string.IsNullOrWhiteSpace(editorexepath))
|
|
{
|
|
MessageBox.Show("No external editor set. Please set the external editor in the UDBScript tab in the preferences.");
|
|
return;
|
|
}
|
|
|
|
Process p = new Process();
|
|
p.StartInfo.FileName = editorexepath;
|
|
p.StartInfo.Arguments = "\"" + file + "\""; // File name might contain spaces, so put it in quotes
|
|
p.Start();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a ScriptInfo to a specific slot.
|
|
/// </summary>
|
|
/// <param name="slot">The slot</param>
|
|
/// <param name="si">The ScriptInfo to assign to the slot. Pass null to clear the slot</param>
|
|
public void SetScriptSlot(int slot, ScriptInfo si)
|
|
{
|
|
if (si == null)
|
|
{
|
|
scriptslots.Remove(slot);
|
|
}
|
|
else
|
|
{
|
|
// Check if the ScriptInfo is already assigned to a slot, and remove it if so
|
|
// Have to use ToList because otherwise the collection would be changed while iterating over it
|
|
foreach (int s in scriptslots.Keys.ToList())
|
|
if (scriptslots[s] == si)
|
|
scriptslots[s] = null;
|
|
|
|
scriptslots[slot] = si;
|
|
}
|
|
|
|
SaveScriptSlotsAndOptions();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a ScriptInfo for a specific script slot.
|
|
/// </summary>
|
|
/// <param name="slot">The slot to get the ScriptInfo for</param>
|
|
/// <returns>The ScriptInfo for the slot, or null if the ScriptInfo is at no slot</returns>
|
|
public ScriptInfo GetScriptSlot(int slot)
|
|
{
|
|
if (scriptslots.ContainsKey(slot))
|
|
return scriptslots[slot];
|
|
else
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the script slot by a ScriptInfo.
|
|
/// </summary>
|
|
/// <param name="si">The ScriptInfo to get the slot of</param>
|
|
/// <returns>The slot the ScriptInfo is in, or 0 if the ScriptInfo is not assigned to a slot</returns>
|
|
public int GetScriptSlotByScriptInfo(ScriptInfo si)
|
|
{
|
|
if (!scriptslots.Values.Contains(si))
|
|
return 0;
|
|
|
|
return scriptslots.FirstOrDefault(i => i.Value == si).Key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads all scripts and fills the docker panel.
|
|
/// </summary>
|
|
public void LoadScripts()
|
|
{
|
|
lock (lockobj)
|
|
{
|
|
scriptinfo = new List<ScriptInfo>();
|
|
scriptdirectorystructure = LoadScriptDirectoryStructure(Path.Combine(General.AppPath, SCRIPT_FOLDER, "scripts"));
|
|
|
|
scriptslots = new Dictionary<int, ScriptInfo>();
|
|
for(int i=0; i < NUM_SCRIPT_SLOTS; i++)
|
|
{
|
|
int num = i + 1;
|
|
string file = General.Settings.ReadPluginSetting("scriptslots.slot" + num, string.Empty);
|
|
|
|
if (string.IsNullOrWhiteSpace(file))
|
|
continue;
|
|
|
|
foreach(ScriptInfo si in scriptinfo)
|
|
{
|
|
if (si.ScriptFile == file)
|
|
scriptslots[num] = si;
|
|
}
|
|
}
|
|
|
|
// This might not be called from the main thread when called by the file system watcher, so use a delegate
|
|
// to run it cleanly
|
|
if (panel.InvokeRequired)
|
|
{
|
|
CallVoidMethodDeletage d = panel.FillTree;
|
|
panel.Invoke(d);
|
|
}
|
|
else
|
|
{
|
|
panel.FillTree();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recursively load information about the script files in a directory and its subdirectories.
|
|
/// </summary>
|
|
/// <param name="path">Path to process</param>
|
|
/// <returns>ScriptDirectoryStructure for the given path</returns>
|
|
private ScriptDirectoryStructure LoadScriptDirectoryStructure(string path)
|
|
{
|
|
string hash = SHA256Hash.Get(path);
|
|
bool expanded = General.Settings.ReadPluginSetting("directoryexpand." + hash, true);
|
|
string name = path.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Last();
|
|
ScriptDirectoryStructure sds = new ScriptDirectoryStructure(name, expanded, hash);
|
|
|
|
foreach (string directory in Directory.GetDirectories(path))
|
|
sds.Directories.Add(LoadScriptDirectoryStructure(directory));
|
|
|
|
foreach (string filename in Directory.GetFiles(path, "*.js"))
|
|
{
|
|
bool retry = true;
|
|
int retrycounter = 5;
|
|
|
|
while (retry)
|
|
{
|
|
try
|
|
{
|
|
ScriptInfo si = new ScriptInfo(filename);
|
|
sds.Scripts.Add(si);
|
|
scriptinfo.Add(si);
|
|
retry = false;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// The FileSystemWatcher can fire the event while the file is still being written, in that case we'll get
|
|
// an IOException (file is locked by another process). So just try to load the file a couple times
|
|
Thread.Sleep(100);
|
|
retrycounter--;
|
|
if (retrycounter == 0)
|
|
retry = false;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Warning, "Failed to process " + filename + ": " + e.Message);
|
|
General.WriteLogLine("Failed to process " + filename + ": " + e.Message);
|
|
retry = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name of the script file. This is either read from the .cfg file of the script or taken from the file name
|
|
/// </summary>
|
|
/// <param name="filename">Full path with file name of the script</param>
|
|
/// <returns></returns>
|
|
public static string GetScriptName(string filename)
|
|
{
|
|
string configfile = Path.Combine(Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename)) + ".cfg";
|
|
|
|
if (File.Exists(configfile))
|
|
{
|
|
Configuration cfg = new Configuration(configfile, true);
|
|
string name = cfg.ReadSetting("name", string.Empty);
|
|
|
|
if (!string.IsNullOrEmpty(name))
|
|
return name;
|
|
}
|
|
|
|
return Path.GetFileNameWithoutExtension(filename);
|
|
}
|
|
|
|
public void EndOptionEdit()
|
|
{
|
|
panel.EndEdit();
|
|
}
|
|
|
|
internal object GetVectorFromObject(object data, bool allow3d)
|
|
{
|
|
if (data is Vector2D)
|
|
return (Vector2D)data;
|
|
else if (data is Vector2DWrapper)
|
|
return new Vector2D(((Vector2DWrapper)data)._x, ((Vector2DWrapper)data)._y);
|
|
else if (data is Vector3DWrapper)
|
|
{
|
|
if(allow3d)
|
|
return new Vector3D(((Vector3DWrapper)data)._x, ((Vector3DWrapper)data)._y, ((Vector3DWrapper)data)._z);
|
|
else
|
|
return new Vector2D(((Vector3DWrapper)data)._x, ((Vector3DWrapper)data)._y);
|
|
}
|
|
else if (data.GetType().IsArray)
|
|
//else if(data is double[])
|
|
{
|
|
object[] vals = (object[])data;
|
|
//double[] vals = (double[])data;
|
|
|
|
// Make sure all values in the array are doubles
|
|
foreach (object v in vals)
|
|
if (!(v is double))
|
|
throw new CantConvertToVectorException("Values in array must be numbers.");
|
|
|
|
if (vals.Length == 2)
|
|
return new Vector2D((double)vals[0], (double)vals[1]);
|
|
if (vals.Length == 3)
|
|
return new Vector3D((double)vals[0], (double)vals[1], (double)vals[2]);
|
|
}
|
|
else if (data is ExpandoObject)
|
|
{
|
|
IDictionary<string, object> eo = data as IDictionary<string, object>;
|
|
double x = double.NaN;
|
|
double y = double.NaN;
|
|
double z = double.NaN;
|
|
|
|
if (eo.ContainsKey("x"))
|
|
{
|
|
try
|
|
{
|
|
x = Convert.ToDouble(eo["x"]);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CantConvertToVectorException("Can not convert 'x' property of data: " + e.Message);
|
|
}
|
|
}
|
|
|
|
if (eo.ContainsKey("y"))
|
|
{
|
|
try
|
|
{
|
|
y = Convert.ToDouble(eo["y"]);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CantConvertToVectorException("Can not convert 'y' property of data: " + e.Message);
|
|
}
|
|
}
|
|
|
|
if (eo.ContainsKey("z"))
|
|
{
|
|
try
|
|
{
|
|
z = Convert.ToDouble(eo["z"]);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
throw new CantConvertToVectorException("Can not convert 'z' property of data: " + e.Message);
|
|
}
|
|
}
|
|
|
|
if (allow3d)
|
|
{
|
|
if (!double.IsNaN(x) && !double.IsNaN(y) && double.IsNaN(z))
|
|
return new Vector2D(x, y);
|
|
else if (!double.IsNaN(x) && !double.IsNaN(y) && !double.IsNaN(z))
|
|
return new Vector3D(x, y, z);
|
|
}
|
|
else
|
|
{
|
|
if (x != double.NaN && y != double.NaN)
|
|
return new Vector2D(x, y);
|
|
}
|
|
}
|
|
|
|
if (allow3d)
|
|
throw new CantConvertToVectorException("Data must be a Vector2D, Vector3D, or an array of numbers.");
|
|
else
|
|
throw new CantConvertToVectorException("Data must be a Vector2D, or an array of numbers.");
|
|
}
|
|
|
|
internal object GetConvertedUniValue(UniValue uv)
|
|
{
|
|
switch ((UniversalType)uv.Type)
|
|
{
|
|
case UniversalType.AngleRadians:
|
|
case UniversalType.AngleDegreesFloat:
|
|
case UniversalType.Float:
|
|
return Convert.ToDouble(uv.Value);
|
|
case UniversalType.AngleDegrees:
|
|
case UniversalType.AngleByte: //mxd
|
|
case UniversalType.Color:
|
|
case UniversalType.EnumBits:
|
|
case UniversalType.EnumOption:
|
|
case UniversalType.Integer:
|
|
case UniversalType.LinedefTag:
|
|
case UniversalType.LinedefType:
|
|
case UniversalType.SectorEffect:
|
|
case UniversalType.SectorTag:
|
|
case UniversalType.ThingTag:
|
|
case UniversalType.ThingType:
|
|
return Convert.ToInt32(uv.Value);
|
|
case UniversalType.Boolean:
|
|
return Convert.ToBoolean(uv.Value);
|
|
case UniversalType.Flat:
|
|
case UniversalType.String:
|
|
case UniversalType.Texture:
|
|
case UniversalType.EnumStrings:
|
|
case UniversalType.ThingClass:
|
|
return Convert.ToString(uv.Value);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal Type GetTypeFromUniversalType(int type)
|
|
{
|
|
switch ((UniversalType)type)
|
|
{
|
|
case UniversalType.AngleRadians:
|
|
case UniversalType.AngleDegreesFloat:
|
|
case UniversalType.Float:
|
|
return typeof(double);
|
|
case UniversalType.AngleDegrees:
|
|
case UniversalType.AngleByte: //mxd
|
|
case UniversalType.Color:
|
|
case UniversalType.EnumBits:
|
|
case UniversalType.EnumOption:
|
|
case UniversalType.Integer:
|
|
case UniversalType.LinedefTag:
|
|
case UniversalType.LinedefType:
|
|
case UniversalType.SectorEffect:
|
|
case UniversalType.SectorTag:
|
|
case UniversalType.ThingTag:
|
|
case UniversalType.ThingType:
|
|
return typeof(int);
|
|
case UniversalType.Boolean:
|
|
return typeof(bool);
|
|
case UniversalType.Flat:
|
|
case UniversalType.String:
|
|
case UniversalType.Texture:
|
|
case UniversalType.EnumStrings:
|
|
case UniversalType.ThingClass:
|
|
return typeof(string);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#region ================== Actions
|
|
|
|
[BeginAction("udbscriptexecute")]
|
|
public void ScriptExecute()
|
|
{
|
|
if (currentscript == null)
|
|
return;
|
|
|
|
scriptrunner = new ScriptRunner(currentscript);
|
|
scriptrunnerform.ShowDialog();
|
|
}
|
|
|
|
[BeginAction("udbscriptexecuteslot1")]
|
|
[BeginAction("udbscriptexecuteslot2")]
|
|
[BeginAction("udbscriptexecuteslot3")]
|
|
[BeginAction("udbscriptexecuteslot4")]
|
|
[BeginAction("udbscriptexecuteslot5")]
|
|
[BeginAction("udbscriptexecuteslot6")]
|
|
[BeginAction("udbscriptexecuteslot7")]
|
|
[BeginAction("udbscriptexecuteslot8")]
|
|
[BeginAction("udbscriptexecuteslot9")]
|
|
[BeginAction("udbscriptexecuteslot10")]
|
|
[BeginAction("udbscriptexecuteslot11")]
|
|
[BeginAction("udbscriptexecuteslot12")]
|
|
[BeginAction("udbscriptexecuteslot13")]
|
|
[BeginAction("udbscriptexecuteslot14")]
|
|
[BeginAction("udbscriptexecuteslot15")]
|
|
[BeginAction("udbscriptexecuteslot16")]
|
|
[BeginAction("udbscriptexecuteslot17")]
|
|
[BeginAction("udbscriptexecuteslot18")]
|
|
[BeginAction("udbscriptexecuteslot19")]
|
|
[BeginAction("udbscriptexecuteslot20")]
|
|
[BeginAction("udbscriptexecuteslot21")]
|
|
[BeginAction("udbscriptexecuteslot22")]
|
|
[BeginAction("udbscriptexecuteslot23")]
|
|
[BeginAction("udbscriptexecuteslot24")]
|
|
[BeginAction("udbscriptexecuteslot25")]
|
|
[BeginAction("udbscriptexecuteslot26")]
|
|
[BeginAction("udbscriptexecuteslot27")]
|
|
[BeginAction("udbscriptexecuteslot28")]
|
|
[BeginAction("udbscriptexecuteslot29")]
|
|
[BeginAction("udbscriptexecuteslot30")]
|
|
public void ScriptExecuteSlot()
|
|
{
|
|
// Extract the slot number from the action name. The action name is something like udbscript__udbscriptexecuteslot1.
|
|
// Not super nice, but better than having 30 identical methods for each slot.
|
|
Regex re = new Regex(@"(\d+)$");
|
|
Match m = re.Match(General.Actions.Current.Name);
|
|
|
|
if(m.Success)
|
|
{
|
|
int slot = int.Parse(m.Value);
|
|
|
|
// Check if there's a ScriptInfo in the slot and run it if so
|
|
if (scriptslots.ContainsKey(slot) && scriptslots[slot] != null)
|
|
{
|
|
scriptrunner = new ScriptRunner(scriptslots[slot]);
|
|
scriptrunnerform.ShowDialog();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|