UltimateZoneBuilder/Source/Plugins/UDBScript/ScriptRunner.cs
biwa 634225b77b UDBScript: Exported the classes Linedef, Sector, Sidedef, Thing, and Vertex, so that they can be used with instanceof
UDBScript: Map class: the getSidedefsFromSelectedLinedefs() method now correctly only returns the Sidedefs of selected Linedefs in visual mode (and not also the highlighted one)
UDBScript: Map class: added a new getSidedefsFromSelectedOrHighlightedLinedefs() method as the equivalent to the other getSelectedOrHighlighted*() methods
UDBScript: Sector class: added new floorSelected, ceilingSelected, floorHighlighted, and ceilingHighlighted properties. Those are mostly useful in visual mode, since they always return true when the Sector is selected or highlighted in the classic modes. The properties are read-only
UDBScript: Sidedef class: added new upperSelected, middleSelected, lowerSelected, upperHighlighted, middleHighlighted, and lowerHighlighted properties. Those are mostly useful in visual mode, since they always return true when the parent Linedef is selected or highlighted in the classic modes. The properties are read-only
UDBScript: added new example to apply textures for floor/ceiling and upper/middle/lower texture for selected map elements
UDBScript: updated documentation
2021-12-25 14:43:56 +01:00

334 lines
11 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.Diagnostics;
using System.IO;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Windows;
using CodeImp.DoomBuilder.UDBScript.Wrapper;
using CodeImp.DoomBuilder.UDBScript.API;
using Jint;
using Jint.Runtime;
using Jint.Runtime.Interop;
using Esprima;
#endregion
namespace CodeImp.DoomBuilder.UDBScript
{
class ScriptRunner
{
#region ================== Variables
private ScriptInfo scriptinfo;
Engine engine;
Stopwatch stopwatch;
#endregion
#region ================== Constructor
public ScriptRunner(ScriptInfo scriptoption)
{
this.scriptinfo = scriptoption;
stopwatch = new Stopwatch();
}
#endregion
#region ================== Methods
/// <summary>
/// Stops the timer, pausing the script's runtime constraint
/// </summary>
public void StopTimer()
{
stopwatch.Stop();
}
/// <summary>
/// Resumes the timer, resuming the script's runtime constraint
/// </summary>
public void ResumeTimer()
{
stopwatch.Start();
}
/// <summary>
/// Shows a message box with an "OK" button
/// </summary>
/// <param name="message">Message to show</param>
public void ShowMessage(object message)
{
if (message == null)
message = string.Empty;
stopwatch.Stop();
MessageForm mf = new MessageForm("OK", null, message.ToString());
DialogResult result = mf.ShowDialog();
stopwatch.Start();
if (result == DialogResult.Abort)
throw new UserScriptAbortException();
}
/// <summary>
/// Shows a message box with an "Yes" and "No" button
/// </summary>
/// <param name="message">Message to show</param>
/// <returns>true if "Yes" was clicked, false if "No" was clicked</returns>
public bool ShowMessageYesNo(object message)
{
if (message == null)
message = string.Empty;
stopwatch.Stop();
MessageForm mf = new MessageForm("Yes", "No", message.ToString());
DialogResult result = mf.ShowDialog();
stopwatch.Start();
if (result == DialogResult.Abort)
throw new UserScriptAbortException();
return result == DialogResult.OK ? true : false;
}
/// <summary>
/// Exist the script prematurely without undoing its changes.
/// </summary>
/// <param name="s"></param>
private void ExitScript(string s = null)
{
if (string.IsNullOrEmpty(s))
throw new ExitScriptException();
throw new ExitScriptException(s);
}
/// <summary>
/// Exist the script prematurely with undoing its changes.
/// </summary>
/// <param name="s"></param>
private void DieScript(string s = null)
{
if (string.IsNullOrEmpty(s))
throw new DieScriptException();
throw new DieScriptException(s);
}
public JavaScriptException CreateRuntimeException(string message)
{
return new JavaScriptException(engine.Realm.Intrinsics.Error, message);
}
/// <summary>
/// Imports the code of all script library files in a single string
/// </summary>
/// <param name="engine">Scripting engine to load the code into</param>
/// <param name="errortext">Errors that occured while loading the library code</param>
/// <returns>true if there were no errors, false if there were errors</returns>
private bool ImportLibraryCode(Engine engine, out string errortext)
{
string path = Path.Combine(General.AppPath, "UDBScript", "Libraries");
string[] files = Directory.GetFiles(path, "*.js", SearchOption.AllDirectories);
errortext = string.Empty;
foreach (string file in files)
{
try
{
ParserOptions po = new ParserOptions(file.Remove(0, General.AppPath.Length));
engine.Execute(File.ReadAllText(file), po);
}
catch (Esprima.ParserException e)
{
MessageBox.Show("There was an error while loading the library " + file + ":\n\n" + e.Message, "Script error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
catch (Jint.Runtime.JavaScriptException e)
{
if (e.Error.Type != Jint.Runtime.Types.String)
{
//MessageBox.Show("There is an error in the script in line " + e.LineNumber + ":\n\n" + e.Message + "\n\n" + e.StackTrace, "Script error", MessageBoxButtons.OK, MessageBoxIcon.Error);
UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, e.StackTrace);
sef.ShowDialog();
}
else
General.Interface.DisplayStatus(StatusType.Warning, e.Message); // We get here if "throw" is used in a script
return false;
}
}
return true;
}
/// <summary>
/// Runs the script
/// </summary>
public void Run()
{
string importlibraryerrors;
bool abort = false;
// If the script requires a higher version of UDBScript than this version ask the user if they want
// to execute it anyways. Remember the choice for this session if "yes" was selected.
if(scriptinfo.Version > BuilderPlug.UDB_SCRIPT_VERSION && !scriptinfo.IgnoreVersion)
{
if (MessageBox.Show("The script requires a higher version of the feature set than this version of UDBScript supports. Executing this script might fail\n\nRequired feature version: " + scriptinfo.Version + "\nUDBScript feature version: " + BuilderPlug.UDB_SCRIPT_VERSION + "\n\nExecute anyway?", "UDBScript feature version too low", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
return;
scriptinfo.IgnoreVersion = true;
}
// Read the current script file
string script = File.ReadAllText(scriptinfo.ScriptFile);
// Make sure the option value gets saved if an option is currently being edited
BuilderPlug.Me.EndOptionEdit();
General.Interface.Focus();
// Set engine options
Options options = new Options();
options.Constraint(new RuntimeConstraint(stopwatch));
options.AllowOperatorOverloading();
options.SetTypeResolver(new TypeResolver {
MemberFilter = member => member.Name != nameof(GetType)
});
// Create the script engine
engine = new Engine(options);
engine.SetValue("showMessage", new Action<object>(ShowMessage));
engine.SetValue("showMessageYesNo", new Func<object, bool>(ShowMessageYesNo));
engine.SetValue("exit", new Action<string>(ExitScript));
engine.SetValue("die", new Action<string>(DieScript));
engine.SetValue("QueryOptions", TypeReference.CreateTypeReference(engine, typeof(QueryOptions)));
engine.SetValue("ScriptOptions", scriptinfo.GetScriptOptionsObject());
engine.SetValue("Map", new MapWrapper());
engine.SetValue("GameConfiguration", new GameConfigurationWrapper());
engine.SetValue("Angle2D", TypeReference.CreateTypeReference(engine, typeof(Angle2DWrapper)));
engine.SetValue("Vector3D", TypeReference.CreateTypeReference(engine, typeof(Vector3DWrapper)));
engine.SetValue("Vector2D", TypeReference.CreateTypeReference(engine, typeof(Vector2DWrapper)));
engine.SetValue("Line2D", TypeReference.CreateTypeReference(engine, typeof(Line2DWrapper)));
engine.SetValue("UniValue", TypeReference.CreateTypeReference(engine, typeof(UniValue)));
engine.SetValue("Data", TypeReference.CreateTypeReference(engine, typeof(DataWrapper)));
// These can not be directly instanciated and don't have static method, but it's required to
// for example use "instanceof" in scripts
engine.SetValue("Linedef", TypeReference.CreateTypeReference(engine, typeof(LinedefWrapper)));
engine.SetValue("Sector", TypeReference.CreateTypeReference(engine, typeof(SectorWrapper)));
engine.SetValue("Sidedef", TypeReference.CreateTypeReference(engine, typeof(SidedefWrapper)));
engine.SetValue("Thing", TypeReference.CreateTypeReference(engine, typeof(ThingWrapper)));
engine.SetValue("Vertex", TypeReference.CreateTypeReference(engine, typeof(VertexWrapper)));
#if DEBUG
engine.SetValue("log", new Action<object>(Console.WriteLine));
#endif
// Import all library files into the current engine
if (ImportLibraryCode(engine, out importlibraryerrors) == false)
return;
// Tell the mode that a script is about to be run
General.Editing.Mode.OnScriptRunBegin();
// Run the script file
try
{
General.Map.UndoRedo.CreateUndo("Run script " + scriptinfo.Name);
General.Map.Map.ClearAllMarks(false);
ParserOptions po = new ParserOptions(scriptinfo.ScriptFile.Remove(0, General.AppPath.Length));
stopwatch.Start();
engine.Execute(script, po);
stopwatch.Stop();
}
catch (UserScriptAbortException)
{
General.Interface.DisplayStatus(StatusType.Warning, "Script aborted");
abort = true;
}
catch (ParserException e)
{
MessageBox.Show("There is an error while parsing the script:\n\n" + e.Message, "Script error", MessageBoxButtons.OK, MessageBoxIcon.Error);
abort = true;
}
catch (Jint.Runtime.JavaScriptException e)
{
if (e.Error.Type != Jint.Runtime.Types.String)
{
//MessageBox.Show("There is an error in the script in line " + e.LineNumber + ":\n\n" + e.Message + "\n\n" + e.StackTrace, "Script error", MessageBoxButtons.OK, MessageBoxIcon.Error);
UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, e.StackTrace);
sef.ShowDialog();
}
else
General.Interface.DisplayStatus(StatusType.Warning, e.Message); // We get here if "throw" is used in a script
abort = true;
}
catch(ExitScriptException e)
{
if (!string.IsNullOrEmpty(e.Message))
General.Interface.DisplayStatus(StatusType.Ready, e.Message);
}
catch(DieScriptException e)
{
if (!string.IsNullOrEmpty(e.Message))
General.Interface.DisplayStatus(StatusType.Warning, e.Message);
abort = true;
}
catch (Exception e) // Catch anything else we didn't think about
{
UDBScriptErrorForm sef = new UDBScriptErrorForm(e.Message, e.StackTrace);
sef.ShowDialog();
abort = true;
}
if (abort)
{
General.Map.UndoRedo.WithdrawUndo();
}
// Do some updates
General.Map.Map.Update();
General.Map.ThingsFilter.Update();
//General.Interface.RedrawDisplay();
// Tell the mode that running the script ended
General.Editing.Mode.OnScriptRunEnd();
}
#endregion
}
}