#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 { internal class ThingWrapper : MapElementWrapper, IEquatable { #region ================== Variables private Thing thing; private MapElementArgumentsWrapper elementargs; #endregion #region IEquatable members public bool Equals(ThingWrapper other) { return thing == other.thing; } public override bool Equals(object obj) { return Equals((ThingWrapper)obj); } public override int GetHashCode() { return thing.GetHashCode(); } #endregion #region ================== Properties /// /// Index of the `Thing`. Read-only. /// public int index { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the index property can not be accessed."); return thing.Index; } } /// /// Type of the `Thing`. /// public int type { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the type property can not be accessed."); return thing.Type; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the type property can not be accessed."); thing.Type = value; thing.UpdateConfiguration(); } } /// /// Angle of the `Thing` in degrees, see https://doomwiki.org/wiki/Angle. /// public int angle { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the angle property can not be accessed."); return thing.AngleDoom; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the angle property can not be accessed."); thing.Rotate(value); } } /// /// Angle of the `Thing` in radians. /// public double angleRad { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the angleRad property can not be accessed."); return thing.Angle; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the angleRad property can not be accessed."); thing.Rotate(value); } } /// /// `Array` of arguments of the `Thing`. Number of arguments depends on game config (usually 5). Hexen format and UDMF only. /// /// number[] public MapElementArgumentsWrapper args { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the args property can not be accessed."); return elementargs; } } /// /// `Thing` action. Hexen and UDMF only. /// public int action { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the action property can not be accessed."); return thing.Action; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the angle property can not be accessed."); thing.Action = value; } } /// /// `Thing` tag. UDMF only. /// public int tag { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the tag property can not be accessed."); return thing.Tag; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the tag property can not be accessed."); thing.Tag = value; } } /// /// If the `Thing` is selected or not. /// public bool selected { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the selected property can not be accessed."); return thing.Selected; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the selected property can not be accessed."); thing.Selected = value; } } /// /// If the `Thing` 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 (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the marked property can not be accessed."); return thing.Marked; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the marked property can not be accessed."); thing.Marked = value; } } /// /// `Thing` 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: /// ``` /// t.flags["8"] = true; // Set the ambush flag /// ``` /// UDMF: /// ``` /// t.flags['ambush'] = true; // Set the ambush flag /// t.flags.ambush = true; // Also works /// ``` /// public ExpandoObject flags { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the flags property can not be accessed."); dynamic eo = new ExpandoObject(); IDictionary o = eo as IDictionary; foreach (KeyValuePair kvp in thing.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.ThingFlags.Keys.Contains(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."); thing.SetFlag(pcea.PropertyName, (bool)so[pcea.PropertyName]); }); return eo; } } /// /// Position of the `Thing`. It's an object with `x`, `y`, and `z` properties. The latter is only relevant in Hexen format and UDMF. /// The `x`, `y`, and `z` accept numbers: /// ``` /// t.position.x = 32; /// t.position.y = 64; /// ``` /// It's also possible to set all fields immediately by assigning either a `Vector2D`, `Vector3D`, or an array of numbers: /// ``` /// t.position = new UDB.Vector2D(32, 64); /// t.position = new UDB.Vector3D(32, 64, 128); /// t.position = [ 32, 64 ]; /// t.position = [ 32, 64, 128 ]; /// ``` /// public object position { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Vertex is disposed, the position property can not be accessed."); return new Vector3DWrapper(thing.Position, thing); } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Vertex is disposed, the position property can not be accessed."); try { Vector3D v = BuilderPlug.Me.GetVector3DFromObject(value); thing.Move(v); } catch (CantConvertToVectorException e) { throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message); } } } /// /// Pitch of the `Thing` in degrees. Only valid for supporting game configurations. /// public int pitch { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the pitch property can not be accessed."); return thing.Pitch; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the pitch property can not be accessed."); thing.SetPitch(value); } } /// /// Roll of the `Thing` in degrees. Only valid for supporting game configurations. /// public int roll { get { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the roll property can not be accessed."); return thing.Roll; } set { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the roll property can not be accessed."); thing.SetRoll(value); } } #endregion #region ================== Constructors internal ThingWrapper(Thing thing) : base(thing) { this.thing = thing; elementargs = new MapElementArgumentsWrapper(thing); } #endregion #region ================== Update internal override void AfterFieldsUpdate() { } #endregion #region ================== Methods public override string ToString() { // The Thing class doesn't have a proper ToString method, so we have to output something sensible ourself return "Thing " + thing.Index.ToString(); } /// /// Copies the properties from this `Thing` to another. /// /// The `Thing` to copy the properties to public void copyPropertiesTo(ThingWrapper t) { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the copyPropertiesTo method can not be accessed."); thing.CopyPropertiesTo(t.thing); } /// /// Clears all flags. /// public void clearFlags() { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the cleaFlags method can not be accessed."); thing.ClearFlags(); } /// /// Snaps the `Thing`'s position to the grid. /// public void snapToGrid() { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the snapToGrid method can not be accessed."); thing.SnapToGrid(); } /// /// Snaps the `Thing`'s position to the map format's accuracy. /// public void snapToAccuracy() { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the snapToAccuracy method can not be accessed."); thing.SnapToAccuracy(); } /// /// Gets the squared distance between this `Thing` and the given point. /// The point can be either a `Vector2D` or an array of numbers. /// ``` /// t.distanceToSq(new UDB.Vector2D(32, 64)); /// t.distanceToSq([ 32, 64 ]); /// ``` /// /// Point to calculate the squared distance to. /// Distance to `pos` public double distanceToSq(object pos) { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the distanceToSq method can not be accessed."); try { Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos); return thing.DistanceToSq(v); } catch (CantConvertToVectorException e) { throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message); } } /// /// Gets the distance between this `Thing` and the given point. The point can be either a `Vector2D` or an array of numbers. /// ``` /// t.distanceToSq(new UDB.Vector2D(32, 64)); /// t.distanceToSq([ 32, 64 ]); /// ``` /// /// Point to calculate the distance to. /// Distance to `pos` public double distanceTo(object pos) { if (thing.IsDisposed) throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException("Thing is disposed, the distanceTo method can not be accessed."); try { Vector2D v = BuilderPlug.Me.GetVector3DFromObject(pos); return thing.DistanceTo(v); } catch (CantConvertToVectorException e) { throw BuilderPlug.Me.ScriptRunner.CreateRuntimeException(e.Message); } } /// /// Deletes the `Thing`. /// public void delete() { if (!thing.IsDisposed) { thing.Dispose(); General.Map.ThingsFilter.Update(); } } /// /// Determines and returns the `Sector` the `Thing` is in. /// /// The `Sector` the `Thing` is in public SectorWrapper getSector() { thing.DetermineSector(); return new SectorWrapper(thing.Sector); } #endregion #region ================== Management /// /// Adds fields to the dictionary that are handled directly by UDB, but changing them is emulated through the UDMF fields. /// /// UniFields of the map element internal override void AddManagedFields(IDictionary fields) { if (thing.ScaleX != 1.0) fields.Add("scalex", thing.ScaleX); if (thing.ScaleY != 1.0) fields.Add("scaley", thing.ScaleY); } /// /// Processed a managed UDMF field, setting the managed value to what the user set in the UDMF field. /// /// UniFields of the map element /// field property name /// field value /// true if the field needed to be processed, false if it didn't internal override bool ProcessManagedField(UniFields fields, string pname, object newvalue) { switch(pname) { case "scalex": if (newvalue == null) thing.SetScale(1.0, thing.ScaleY); else thing.SetScale((double)newvalue, thing.ScaleY); return true; case "scaley": if(newvalue == null) thing.SetScale(thing.ScaleX, 1.0); else thing.SetScale(thing.ScaleX, (double)newvalue); return true; } return false; } #endregion } }