#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. */ #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 { #region ================== Variables private Linedef linedef; private MapElementArgumentsWrapper elementargs; #endregion #region IEquatable 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 /// /// The linedef's index. Read-only. /// 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; } } /// /// The linedef's start `Vertex`. /// 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); } } /// /// The linedef's end `Vertex`. /// 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); } } /// /// The `Linedef`'s front `Sidedef`. Is `null` when there is no front (should not happen). /// 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); } } /// /// The `Linedef`'s back `Sidedef`. Is `null` when there is no back. /// 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); } } /// /// The `Line2D` from the `start` to the `end` `Vertex`. /// 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); } } /// /// If the `Linedef` is selected or not. /// 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; } } /// /// 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). /// 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; } } /// /// The activation flag. Hexen format only. /// 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; } } /// /// `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 /// ``` /// 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 o = eo as IDictionary; foreach (KeyValuePair 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 so = sender as IDictionary; // 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; } } /// /// `Array` of arguments of the `Linedef`. Number of arguments depends on game config (usually 5). Hexen format and UDMF only. /// /// number[] public MapElementArgumentsWrapper args { get { if (linedef.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the args property can not be accessed."); return elementargs; } } /// /// `Linedef` action. /// 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; } } /// /// `Linedef` tag. UDMF only. /// 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; } } /// /// The `Linedef`'s squared length. Read-only. /// 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; } } /// /// The `Linedef`'s length. Read-only. /// 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; } } /// /// 1.0 / length. Read-only. /// 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; } } /// /// The `Linedef`'s angle in degree. Read-only. /// 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; } } /// /// The `Linedef`'s angle in radians. Read-only. /// 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(); } /// /// Copies the properties of this `Linedef` to another `Linedef`. /// /// The `Linedef` to copy the properties to 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); } /// /// Clears all flags. /// public void clearFlags() { if (linedef.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the clearFlags medhot can not be accessed."); linedef.ClearFlags(); } /// /// Flips the `Linedef`'s vertex attachments. /// public void flipVertices() { if (linedef.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the flipVertices method can not be accessed."); linedef.FlipVertices(); } /// /// Flips the `Linedef`'s `Sidedef`s. /// public void flipSidedefs() { if (linedef.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the flipSidedefs method can not be accessed."); linedef.FlipSidedefs(); } /// /// Flips the `Linedef`'s vertex attachments and `Sidedef`s. This is a shortcut to using both `flipVertices()` and `flipSidedefs()`. /// 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(); } /// /// Gets a `Vector2D` for testing on one side. The `Vector2D` is on the front when `true` is passed, otherwise on the back. /// /// `true` for front, `false` for back /// `Vector2D` that's either on the front of back of the Linedef 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)); } /// /// Gets a `Vector2D` that's in the center of the `Linedef`. /// /// `Vector2D` in the center of the `Linedef` 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()); } /// /// Automatically sets the blocking and two-sided flags based on the existing `Sidedef`s. /// public void applySidedFlags() { if (linedef.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Linedef is disposed, the applySidedFlags method can not be accessed."); linedef.ApplySidedFlags(); } /// /// 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 ]); /// ``` /// /// Point to check against /// `Vector2D` that's on the linedef 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); } } /// /// 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. /// /// Point to check against /// `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used /// Squared distance to the line 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); } } /// /// 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. /// /// Point to check against /// `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used /// Distance to the line 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); } } /// /// Gets the shortest squared distance from `pos` to the line. /// /// Point to check against /// `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used /// Squared distance to the line 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); } } /// /// Gets the shortest distance from `pos` to the line. /// /// Point to check against /// `true` if only the finite length of the line should be used, `false` if the infinite length of the line should be used /// Distance to the line 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); } } /// /// Tests which side of the `Linedef` `pos` is on. Returns < 0 for front (right) side, > for back (left) side, and 0 if `pos` is on the line. /// /// Point to check against /// < 0 for front (right) side, > for back (left) side, and 0 if `pos` is on the line 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); } } /// /// 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`. /// /// `Vertex` to split by /// The newly created `Linedef` 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); } } /// /// Deletes the `Linedef`. Note that this will result in unclosed `Sector`s unless it has the same `Sector`s on both sides. /// public void delete() { if (linedef.IsDisposed) return; linedef.Dispose(); } #endregion #region ================== Interface implementations /// /// Returns an `Array` of the `Linedef`'s tags. UDMF only. Supported game configurations only. /// /// `Array` of tags public int[] getTags() { return linedef.Tags.ToArray(); } /// /// Adds a tag to the `Linedef`. UDMF only. Supported game configurations only. /// /// Tag to add /// `true` when the tag was added, `false` when the tag already exists 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 tags = new List(linedef.Tags); tags.Add(tag); tags.Remove(0); linedef.Tags = tags; return true; } /// /// Removes a tag from the `Linedef`. UDMF only. Supported game configurations only. /// /// Tag to remove /// `true` when the tag was removed successfully, `false` when the tag did not exist 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 tags = new List(linedef.Tags); tags.Remove(tag); linedef.Tags = tags; } return true; } return false; } #endregion } }