UltimateZoneBuilder/Source/Plugins/UDBScript/API/LinedefWrapper.cs
biwa 5b2b149b40
UDBScript version 5 (#819)
Improved UDBScript to version 5:

- Added Plane class
- Added BlockMap, BlockEntry, and BlackMapQueryResult classes
- Sector class
  - Added getLabelPositions method to get the position of sector labels (where tags, effects etc. are displayed)
- Added support for JavaScript BigInt for UDMF fields. This means it's not necessary anymore to use UniValue to assign integers to new UDMF fields. Instead it can be done like this: sector.fields.my_int_field = 1n;
- Added type information file (udbscript.d.ts)
2022-11-13 01:15:17 +01:00

752 lines
22 KiB
C#

#region ================== Copyright (c) 2021 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.ComponentModel;
using System.Dynamic;
using System.Linq;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Map;
#endregion
namespace CodeImp.DoomBuilder.UDBScript.Wrapper
{
class LinedefWrapper : MapElementWrapper, IMoreTags, IEquatable<LinedefWrapper>
{
#region ================== Variables
private Linedef linedef;
private MapElementArgumentsWrapper elementargs;
#endregion
#region IEquatable<LinedefWrapper> members
public bool Equals(LinedefWrapper other)
{
return linedef == other.linedef;
}
public override bool Equals(object obj)
{
return Equals((LinedefWrapper)obj);
}
public override int GetHashCode()
{
return linedef.GetHashCode();
}
#endregion
#region ================== Properties
/// <summary>
/// The linedef's index. Read-only.
/// </summary>
public int index
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the index property can not be accessed.");
return linedef.Index;
}
}
/// <summary>
/// The linedef's start `Vertex`.
/// </summary>
public VertexWrapper start
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the start property can not be accessed.");
return new VertexWrapper(linedef.Start);
}
}
/// <summary>
/// The linedef's end `Vertex`.
/// </summary>
public VertexWrapper end
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the end property can not be accessed.");
return new VertexWrapper(linedef.End);
}
}
/// <summary>
/// The `Linedef`'s front `Sidedef`. Is `null` when there is no front (should not happen).
/// </summary>
public SidedefWrapper front
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the front property can not be accessed.");
if (linedef.Front == null)
return null;
return new SidedefWrapper(linedef.Front);
}
}
/// <summary>
/// The `Linedef`'s back `Sidedef`. Is `null` when there is no back.
/// </summary>
public SidedefWrapper back
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the back property can not be accessed.");
if (linedef.Back == null)
return null;
return new SidedefWrapper(linedef.Back);
}
}
/// <summary>
/// The `Line2D` from the `start` to the `end` `Vertex`.
/// </summary>
public Line2DWrapper line
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the line property can not be accessed.");
return new Line2DWrapper(linedef.Line);
}
}
/// <summary>
/// If the `Linedef` is selected or not.
/// </summary>
public bool selected
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the selected property can not be accessed.");
return linedef.Selected;
}
set
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the selected property can not be accessed.");
linedef.Selected = value;
}
}
/// <summary>
/// If the `Linedef` is marked or not. It is used to mark map elements that were created or changed (for example after drawing new geometry).
/// </summary>
public bool marked
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the marked property can not be accessed.");
return linedef.Marked;
}
set
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the marked property can not be accessed.");
linedef.Marked = value;
}
}
/// <summary>
/// The activation flag. Hexen format only.
/// </summary>
public int activate
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the activate property can not be accessed.");
return linedef.Activate;
}
set
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the activate property can not be accessed.");
linedef.Activate = value;
}
}
/// <summary>
/// `Linedef` flags. It's an object with the flags as properties. In Doom format and Hexen format they are identified by numbers, in UDMF by their name.
/// Doom and Hexen:
/// ```
/// ld.flags['64'] = true; // Set the block sound flag
/// ```
/// UDMF:
/// ```
/// ld.flags['blocksound'] = true; // Set the block sound flag
/// ld.flags.blocksound = true; // Also works
/// ```
/// </summary>
public ExpandoObject flags
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the flags property can not be accessed.");
dynamic eo = new ExpandoObject();
IDictionary<string, object> o = eo as IDictionary<string, object>;
foreach (KeyValuePair<string, bool> kvp in linedef.GetFlags())
{
o.Add(kvp.Key, kvp.Value);
}
// Create event that gets called when a property is changed. This sets the flag
((INotifyPropertyChanged)eo).PropertyChanged += new PropertyChangedEventHandler((sender, ea) => {
PropertyChangedEventArgs pcea = ea as PropertyChangedEventArgs;
IDictionary<string, object> so = sender as IDictionary<string, object>;
// Only allow known flags to be set
if (!General.Map.Config.LinedefFlags.Keys.Contains(pcea.PropertyName) && !General.Map.Config.LinedefActivates.Any(lai => lai.Key == pcea.PropertyName))
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Flag name '" + pcea.PropertyName + "' is not valid.");
// New value must be bool
if (!(so[pcea.PropertyName] is bool))
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Flag values must be bool.");
linedef.SetFlag(pcea.PropertyName, (bool)so[pcea.PropertyName]);
});
return eo;
}
}
/// <summary>
/// `Array` of arguments of the `Linedef`. Number of arguments depends on game config (usually 5). Hexen format and UDMF only.
/// </summary>
/// <fakedtstype>number[]</fakedtstype>
public MapElementArgumentsWrapper args
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the args property can not be accessed.");
return elementargs;
}
}
/// <summary>
/// `Linedef` action.
/// </summary>
public int action
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the action property can not be accessed.");
return linedef.Action;
}
set
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the action property can not be accessed.");
linedef.Action = value;
}
}
/// <summary>
/// `Linedef` tag. UDMF only.
/// </summary>
public int tag
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the tag property can not be accessed.");
return linedef.Tag;
}
set
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the tag property can not be accessed.");
linedef.Tag = value;
}
}
/// <summary>
/// The `Linedef`'s squared length. Read-only.
/// </summary>
public double lengthSq
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the lengthSq property can not be accessed.");
return linedef.LengthSq;
}
}
/// <summary>
/// The `Linedef`'s length. Read-only.
/// </summary>
public double length
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the length property can not be accessed.");
return linedef.Length;
}
}
/// <summary>
/// 1.0 / length. Read-only.
/// </summary>
public double lengthInv
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the lengthInv property can not be accessed.");
return linedef.LengthInv;
}
}
/// <summary>
/// The `Linedef`'s angle in degree. Read-only.
/// </summary>
public int angle
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the angle property can not be accessed.");
return linedef.AngleDeg;
}
}
/// <summary>
/// The `Linedef`'s angle in radians. Read-only.
/// </summary>
public double angleRad
{
get
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the angleRad property can not be accessed.");
return linedef.Angle;
}
}
#endregion
#region ================== Constructors
internal LinedefWrapper(Linedef linedef) : base(linedef)
{
this.linedef = linedef;
elementargs = new MapElementArgumentsWrapper(linedef);
}
#endregion
#region ================== Update
internal override void AfterFieldsUpdate()
{
}
#endregion
#region ================== Methods
public override string ToString()
{
return linedef.ToString();
}
/// <summary>
/// Copies the properties of this `Linedef` to another `Linedef`.
/// </summary>
/// <param name="other">The `Linedef` to copy the properties to</param>
public void copyPropertiesTo(LinedefWrapper other)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the copyPropertiesTo method can not be accessed.");
linedef.CopyPropertiesTo(other.linedef);
}
/// <summary>
/// Clears all flags.
/// </summary>
public void clearFlags()
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the clearFlags medhot can not be accessed.");
linedef.ClearFlags();
}
/// <summary>
/// Flips the `Linedef`'s vertex attachments.
/// </summary>
public void flipVertices()
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the flipVertices method can not be accessed.");
linedef.FlipVertices();
}
/// <summary>
/// Flips the `Linedef`'s `Sidedef`s.
/// </summary>
public void flipSidedefs()
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the flipSidedefs method can not be accessed.");
linedef.FlipSidedefs();
}
/// <summary>
/// Flips the `Linedef`'s vertex attachments and `Sidedef`s. This is a shortcut to using both `flipVertices()` and `flipSidedefs()`.
/// </summary>
public void flip()
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the flip method can not be accessed.");
linedef.FlipVertices();
linedef.FlipSidedefs();
}
/// <summary>
/// Gets a `Vector2D` for testing on one side. The `Vector2D` is on the front when `true` is passed, otherwise on the back.
/// </summary>
/// <param name="front">`true` for front, `false` for back</param>
/// <returns>`Vector2D` that's either on the front of back of the Linedef</returns>
public Vector2DWrapper getSidePoint(bool front)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the getSidePoint method can not be accessed.");
return new Vector2DWrapper(linedef.GetSidePoint(front));
}
/// <summary>
/// Gets a `Vector2D` that's in the center of the `Linedef`.
/// </summary>
/// <returns>`Vector2D` in the center of the `Linedef`</returns>
public Vector2DWrapper getCenterPoint()
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the getCenterPoint method can not be accessed.");
return new Vector2DWrapper(linedef.GetCenterPoint());
}
/// <summary>
/// Automatically sets the blocking and two-sided flags based on the existing `Sidedef`s.
/// </summary>
public void applySidedFlags()
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the applySidedFlags method can not be accessed.");
linedef.ApplySidedFlags();
}
/// <summary>
/// Get a `Vector2D` that's *on* the line, closest to `pos`. `pos` can either be a `Vector2D`, or an array of numbers.
/// ```
/// var v1 = ld.nearestOnLine(new Vector2D(32, 64));
/// var v2 = ld.nearestOnLine([ 32, 64 ]);
/// ```
/// </summary>
/// <param name="pos">Point to check against</param>
/// <returns>`Vector2D` that's on the linedef</returns>
public Vector2DWrapper nearestOnLine(object pos)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the nearestOnLine method can not be accessed.");
try
{
Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
return new Vector2DWrapper(linedef.NearestOnLine(v));
}
catch (CantConvertToVectorException e)
{
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
}
}
/// <summary>
/// Gets the shortest "safe" squared distance from `pos` to the line. If `bounded` is `true` that means that the not the whole line's length will be used, but `lengthInv` less at the start and end.
/// </summary>
/// <param name="pos">Point to check against</param>
/// <param name="bounded">`true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used</param>
/// <returns>Squared distance to the line</returns>
public double safeDistanceToSq(object pos, bool bounded)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the safeDistanceToSq method can not be accessed.");
try
{
Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
return linedef.SafeDistanceToSq(v, bounded);
}
catch (CantConvertToVectorException e)
{
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
}
}
/// <summary>
/// Gets the shortest "safe" distance from `pos` to the line. If `bounded` is `true` that means that the not the whole line's length will be used, but `lengthInv` less at the start and end.
/// </summary>
/// <param name="pos">Point to check against</param>
/// <param name="bounded">`true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used</param>
/// <returns>Distance to the line</returns>
public double safeDistanceTo(object pos, bool bounded)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the safeDistanceTo method can not be accessed.");
try
{
Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
return linedef.SafeDistanceTo(v, bounded);
}
catch (CantConvertToVectorException e)
{
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
}
}
/// <summary>
/// Gets the shortest squared distance from `pos` to the line.
/// </summary>
/// <param name="pos">Point to check against</param>
/// <param name="bounded">`true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used</param>
/// <returns>Squared distance to the line</returns>
public double distanceToSq(object pos, bool bounded)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the distanceToSq method can not be accessed.");
try
{
Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
return linedef.DistanceToSq(v, bounded);
}
catch (CantConvertToVectorException e)
{
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
}
}
/// <summary>
/// Gets the shortest distance from `pos` to the line.
/// </summary>
/// <param name="pos">Point to check against</param>
/// <param name="bounded">`true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used</param>
/// <returns>Distance to the line</returns>
public double distanceTo(object pos, bool bounded)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the distanceTo method can not be accessed.");
try
{
Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
return linedef.DistanceTo(v, bounded);
}
catch (CantConvertToVectorException e)
{
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
}
}
/// <summary>
/// Tests which side of the `Linedef` `pos` is on. Returns &lt; 0 for front (right) side, &gt; for back (left) side, and 0 if `pos` is on the line.
/// </summary>
/// <param name="pos">Point to check against</param>
/// <returns>&lt; 0 for front (right) side, &gt; for back (left) side, and 0 if `pos` is on the line</returns>
public double sideOfLine(object pos)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the sideOfLine method can not be accessed.");
try
{
Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
return linedef.SideOfLine(v);
}
catch (CantConvertToVectorException e)
{
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
}
}
/// <summary>
/// Splits the `Linedef` at the given position. This can either be a `Vector2D`, an array of numbers, or an existing `Vertex`. The result will be two lines, from the start `Vertex` of the `Linedef` to `pos`, and from `pos` to the end `Vertex` of the `Linedef`.
/// </summary>
/// <param name="pos">`Vertex` to split by</param>
/// <returns>The newly created `Linedef`</returns>
public LinedefWrapper split(object pos)
{
if (linedef.IsDisposed)
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the split method can not be accessed.");
if(pos is VertexWrapper)
return new LinedefWrapper(linedef.Split(((VertexWrapper)pos).Vertex));
try
{
Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos);
Vertex nv = General.Map.Map.CreateVertex(v);
nv.SnapToAccuracy();
Linedef nld = linedef.Split(nv);
linedef.UpdateCache();
nld.UpdateCache();
return new LinedefWrapper(nld);
}
catch (CantConvertToVectorException e)
{
throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message);
}
}
/// <summary>
/// Deletes the `Linedef`. Note that this will result in unclosed `Sector`s unless it has the same `Sector`s on both sides.
/// </summary>
public void delete()
{
if (linedef.IsDisposed)
return;
linedef.Dispose();
}
#endregion
#region ================== Interface implementations
/// <summary>
/// Returns an `Array` of the `Linedef`'s tags. UDMF only. Supported game configurations only.
/// </summary>
/// <returns>`Array` of tags</returns>
public int[] getTags()
{
return linedef.Tags.ToArray();
}
/// <summary>
/// Adds a tag to the `Linedef`. UDMF only. Supported game configurations only.
/// </summary>
/// <param name="tag">Tag to add</param>
/// <returns>`true` when the tag was added, `false` when the tag already exists</returns>
public bool addTag(int tag)
{
if (linedef.Tags.Contains(tag))
return false;
// We have to take the detour by creating a new list and assigning that because otherwise the
// BeforePropsChange will not be triggered
List<int> tags = new List<int>(linedef.Tags);
tags.Add(tag);
tags.Remove(0);
linedef.Tags = tags;
return true;
}
/// <summary>
/// Removes a tag from the `Linedef`. UDMF only. Supported game configurations only.
/// </summary>
/// <param name="tag">Tag to remove</param>
/// <returns>`true` when the tag was removed successfully, `false` when the tag did not exist</returns>
public bool removeTag(int tag)
{
if (linedef.Tags.Contains(tag))
{
// If it's the only tag just set it to 0, otherwise remove the tag completely
if (linedef.Tags.Count == 1)
linedef.Tag = 0;
else
{
// We have to take the detour by creating a new list and assigning that because otherwise the
// BeforePropsChange will not be triggered
List<int> tags = new List<int>(linedef.Tags);
tags.Remove(tag);
linedef.Tags = tags;
}
return true;
}
return false;
}
#endregion
}
}