diff --git a/Source/Plugins/BuilderModes/BuilderModes.csproj b/Source/Plugins/BuilderModes/BuilderModes.csproj
index ba0fad02..e0da7c05 100755
--- a/Source/Plugins/BuilderModes/BuilderModes.csproj
+++ b/Source/Plugins/BuilderModes/BuilderModes.csproj
@@ -145,11 +145,19 @@
+
+
+
+
+
+
+
+
diff --git a/Source/Plugins/BuilderModes/BuilderModesMono.csproj b/Source/Plugins/BuilderModes/BuilderModesMono.csproj
index 22ead15e..1dc66ddc 100644
--- a/Source/Plugins/BuilderModes/BuilderModesMono.csproj
+++ b/Source/Plugins/BuilderModes/BuilderModesMono.csproj
@@ -143,11 +143,19 @@
+
+
+
+
+
+
+
+
diff --git a/Source/Plugins/BuilderModes/FindReplace/BaseFindMapElement.cs b/Source/Plugins/BuilderModes/FindReplace/BaseFindMapElement.cs
new file mode 100644
index 00000000..edcee3a7
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/BaseFindMapElement.cs
@@ -0,0 +1,130 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Rendering;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ internal class BaseFindMapElement : FindReplaceType
+ {
+ #region ================== Methods
+
+ // This is called when a specific object is selected from the list
+ public override void ObjectSelected(FindReplaceObject[] selection)
+ {
+ if (selection.Length == 1)
+ {
+ ZoomToSelection(selection);
+
+ if (selection[0].Object is Linedef)
+ General.Interface.ShowLinedefInfo(selection[0].Linedef);
+ else if (selection[0].Object is Sidedef)
+ General.Interface.ShowLinedefInfo(selection[0].Sidedef.Line);
+ else if (selection[0].Object is Sector)
+ General.Interface.ShowSectorInfo(selection[0].Sector);
+ else if (selection[0].Object is Thing)
+ General.Interface.ShowThingInfo(selection[0].Thing);
+ else if (selection[0].Object is Vertex)
+ General.Interface.ShowVertexInfo(selection[0].Vertex);
+ }
+ else
+ {
+ General.Interface.HideInfo();
+ }
+
+ General.Map.Map.ClearAllSelected();
+ foreach (FindReplaceObject obj in selection)
+ {
+ // Sidedefs can not be selected, so we have to select its Linedef
+ if (obj.Object is Sidedef)
+ obj.Sidedef.Line.Selected = true;
+ else
+ ((SelectableElement)obj.Object).Selected = true;
+ }
+ }
+
+ // Render selection
+ public override void PlotSelection(IRenderer2D renderer, FindReplaceObject[] selection)
+ {
+ foreach (FindReplaceObject o in selection)
+ {
+ if (o.Object is Linedef)
+ renderer.PlotLinedef(o.Linedef, General.Colors.Selection);
+ else if (o.Object is Sidedef)
+ renderer.PlotLinedef(o.Sidedef.Line, General.Colors.Selection);
+ else if (o.Object is Sector)
+ {
+ foreach (Sidedef sd in o.Sector.Sidedefs)
+ renderer.PlotLinedef(sd.Line, General.Colors.Selection);
+ }
+ else if (o.Object is Thing)
+ renderer.RenderThing(o.Thing, General.Colors.Selection, General.Settings.ActiveThingsAlpha);
+ else if(o.Object is Vertex)
+ renderer.PlotVertex(o.Vertex, ColorCollection.SELECTION);
+ }
+ }
+
+ // Edit objects
+ public override void EditObjects(FindReplaceObject[] selection)
+ {
+ HashSet linedefs = new HashSet();
+ HashSet sectors = new HashSet();
+ HashSet things = new HashSet();
+ HashSet vertices = new HashSet();
+
+ foreach (FindReplaceObject o in selection)
+ {
+ if (o.Object is Linedef)
+ {
+ if (!linedefs.Contains(o.Linedef)) linedefs.Add(o.Linedef);
+ }
+ else if (o.Object is Sidedef)
+ {
+ if (!linedefs.Contains(o.Sidedef.Line)) linedefs.Add(o.Sidedef.Line);
+ }
+ else if (o.Object is Sector)
+ {
+ if (!sectors.Contains(o.Sector)) sectors.Add(o.Sector);
+ }
+ else if (o.Object is Thing)
+ {
+ if (!things.Contains(o.Thing)) things.Add(o.Thing);
+ }
+ else if (o.Object is Vertex)
+ if (!vertices.Contains(o.Vertex)) vertices.Add(o.Vertex);
+ }
+
+ if(linedefs.Count > 0)
+ General.Interface.ShowEditLinedefs(linedefs);
+
+ if (sectors.Count > 0)
+ General.Interface.ShowEditSectors(sectors);
+
+ if (things.Count > 0)
+ General.Interface.ShowEditThings(things);
+
+ if (vertices.Count > 0)
+ General.Interface.ShowEditVertices(vertices);
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/FindReplace/BaseFindUDMFField.cs b/Source/Plugins/BuilderModes/FindReplace/BaseFindUDMFField.cs
new file mode 100644
index 00000000..86d60564
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/BaseFindUDMFField.cs
@@ -0,0 +1,125 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ class BaseFindUDMFField : BaseFindMapElement
+ {
+ #region ================== Properties
+
+ public override string UsageHint
+ {
+ get
+ {
+ return "Usage: field [value]" + Environment.NewLine
+ + "Supported wildcards (for both field and value):" + Environment.NewLine
+ + "* - zero or more characters" + Environment.NewLine
+ + "? - one character";
+ }
+ }
+
+ #endregion
+
+ #region ================== Methods
+
+ public override bool CanReplace()
+ {
+ return false;
+ }
+
+ public override bool DetermineVisiblity()
+ {
+ return General.Map.UDMF;
+ }
+
+ ///
+ /// Gets map elements with matching fields
+ ///
+ /// Field name
+ /// Field value
+ /// List of map elements to check
+ ///
+ protected FindReplaceObject[] GetObjects(string input, ICollection list)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ return new FindReplaceObject[] { };
+
+ List objs = new List();
+
+ string key;
+ string value;
+
+ input = input.Trim();
+
+ if (input.IndexOf(' ') == -1)
+ {
+ key = input;
+ value = string.Empty;
+ }
+ else
+ {
+ key = input.Substring(0, input.IndexOf(' '));
+ value = input.Substring(input.IndexOf(' ')).Trim();
+ }
+
+ Regex keyre = new Regex(WildCardToRegular(key));
+ Regex valuere = new Regex(WildCardToRegular(value));
+
+ foreach(MapElement me in list)
+ {
+ foreach(KeyValuePair kvp in me.Fields)
+ {
+ if (keyre.IsMatch(kvp.Key))
+ {
+ bool matching = true;
+ string fieldvalue = kvp.Value.Value.ToString();
+
+ if (!string.IsNullOrEmpty(value))
+ {
+ if (!valuere.IsMatch(fieldvalue))
+ matching = false;
+ }
+
+ if(matching)
+ objs.Add(new FindReplaceObject(me, me.GetType().Name + " " + me.Index.ToString() + ". " + kvp.Key + ": " + fieldvalue));
+ }
+ }
+ }
+
+ return objs.ToArray();
+ }
+
+ ///
+ /// Turns a wildcard string into an regular expression. Taken from https://stackoverflow.com/a/30300521 (by user Dmitry Bychenko)
+ ///
+ /// String with wildcards
+ /// String of regular expression
+ private static string WildCardToRegular(string value)
+ {
+ return "^" + Regex.Escape(value).Replace("\\?", ".").Replace("\\*", ".*") + "$";
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/FindReplace/FindAnyUDMFField.cs b/Source/Plugins/BuilderModes/FindReplace/FindAnyUDMFField.cs
new file mode 100644
index 00000000..69494b90
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/FindAnyUDMFField.cs
@@ -0,0 +1,78 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ [FindReplace("Any UDMF Field", BrowseButton = false)]
+ internal class FindAnyUDMFField : BaseFindUDMFField
+ {
+ #region ================== Methods
+
+ public override bool CanReplace()
+ {
+ return false;
+ }
+
+ public override bool DetermineVisiblity()
+ {
+ return General.Map.UDMF;
+ }
+
+ public override FindReplaceObject[] Find(string value, bool withinselection, bool replace, string replacewith, bool keepselection)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return new FindReplaceObject[] { };
+
+ List list = new List();
+
+ if(withinselection)
+ {
+ list.AddRange(General.Map.Map.GetSelectedSectors(true));
+ list.AddRange(General.Map.Map.GetSelectedLinedefs(true));
+
+ foreach (Linedef ld in General.Map.Map.GetSelectedLinedefs(true))
+ {
+ if (ld.Front != null && !ld.Front.IsDisposed)
+ list.Add(ld.Front);
+
+ if (ld.Back != null && !ld.Back.IsDisposed)
+ list.Add(ld.Back);
+ }
+
+ list.AddRange(General.Map.Map.GetSelectedThings(true));
+ list.AddRange(General.Map.Map.GetSelectedVertices(true));
+ }
+ else
+ {
+ list.AddRange(General.Map.Map.Sectors);
+ list.AddRange(General.Map.Map.Linedefs);
+ list.AddRange(General.Map.Map.Sidedefs);
+ list.AddRange(General.Map.Map.Things);
+ list.AddRange(General.Map.Map.Vertices);
+ }
+
+ return GetObjects(value, list);
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/FindReplace/FindLinedefUDMFFields.cs b/Source/Plugins/BuilderModes/FindReplace/FindLinedefUDMFFields.cs
new file mode 100644
index 00000000..deed5c38
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/FindLinedefUDMFFields.cs
@@ -0,0 +1,52 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ [FindReplace("Linedef UDMF Field", BrowseButton = false)]
+ internal class FindLinedefUDMFField : BaseFindUDMFField
+ {
+ #region ================== Methods
+
+ public override bool CanReplace()
+ {
+ return false;
+ }
+
+ public override bool DetermineVisiblity()
+ {
+ return General.Map.UDMF;
+ }
+
+ public override FindReplaceObject[] Find(string value, bool withinselection, bool replace, string replacewith, bool keepselection)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return new FindReplaceObject[] { };
+
+ ICollection list = withinselection ? (ICollection)General.Map.Map.GetSelectedLinedefs(true) : (ICollection)General.Map.Map.Linedefs;
+
+ return GetObjects(value, list);
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/FindReplace/FindSectorUDMFField.cs b/Source/Plugins/BuilderModes/FindReplace/FindSectorUDMFField.cs
new file mode 100644
index 00000000..f3d0a313
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/FindSectorUDMFField.cs
@@ -0,0 +1,52 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ [FindReplace("Sector UDMF Field", BrowseButton = false)]
+ internal class FindSectorUDMFField : BaseFindUDMFField
+ {
+ #region ================== Methods
+
+ public override bool CanReplace()
+ {
+ return false;
+ }
+
+ public override bool DetermineVisiblity()
+ {
+ return General.Map.UDMF;
+ }
+
+ public override FindReplaceObject[] Find(string value, bool withinselection, bool replace, string replacewith, bool keepselection)
+ {
+ if(string.IsNullOrWhiteSpace(value))
+ return new FindReplaceObject[] { };
+
+ ICollection list = withinselection ? (ICollection)General.Map.Map.GetSelectedSectors(true) : (ICollection)General.Map.Map.Sectors;
+
+ return GetObjects(value, list);
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/FindReplace/FindSidedefUDMFField.cs b/Source/Plugins/BuilderModes/FindReplace/FindSidedefUDMFField.cs
new file mode 100644
index 00000000..c0d61019
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/FindSidedefUDMFField.cs
@@ -0,0 +1,71 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ [FindReplace("Sidedef UDMF Field", BrowseButton = false)]
+ internal class FindSidedefUDMFField : BaseFindUDMFField
+ {
+ #region ================== Methods
+
+ public override bool CanReplace()
+ {
+ return false;
+ }
+
+ public override bool DetermineVisiblity()
+ {
+ return General.Map.UDMF;
+ }
+
+ public override FindReplaceObject[] Find(string value, bool withinselection, bool replace, string replacewith, bool keepselection)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return new FindReplaceObject[] { };
+
+ ICollection list;
+
+ if(withinselection)
+ {
+ // Get all sidedefs of the selected linedefs
+ list = new List();
+
+ foreach(Linedef ld in General.Map.Map.GetSelectedLinedefs(true))
+ {
+ if (ld.Front != null && !ld.Front.IsDisposed)
+ list.Add(ld.Front);
+
+ if (ld.Back != null && !ld.Back.IsDisposed)
+ list.Add(ld.Back);
+ }
+ }
+ else
+ {
+ list = (ICollection)General.Map.Map.Sidedefs;
+ }
+
+ return GetObjects(value, list);
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/FindReplace/FindThingUDMFField.cs b/Source/Plugins/BuilderModes/FindReplace/FindThingUDMFField.cs
new file mode 100644
index 00000000..d9746411
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/FindThingUDMFField.cs
@@ -0,0 +1,52 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ [FindReplace("Thing UDMF Field", BrowseButton = false)]
+ internal class FindThingUDMFField : BaseFindUDMFField
+ {
+ #region ================== Methods
+
+ public override bool CanReplace()
+ {
+ return false;
+ }
+
+ public override bool DetermineVisiblity()
+ {
+ return General.Map.UDMF;
+ }
+
+ public override FindReplaceObject[] Find(string value, bool withinselection, bool replace, string replacewith, bool keepselection)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return new FindReplaceObject[] { };
+
+ ICollection list = withinselection ? (ICollection)General.Map.Map.GetSelectedThings(true) : (ICollection)General.Map.Map.Things;
+
+ return GetObjects(value, list);
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/FindReplace/FindVertexUDMFField.cs b/Source/Plugins/BuilderModes/FindReplace/FindVertexUDMFField.cs
new file mode 100644
index 00000000..4a6e1bbf
--- /dev/null
+++ b/Source/Plugins/BuilderModes/FindReplace/FindVertexUDMFField.cs
@@ -0,0 +1,52 @@
+#region ================== Copyright (c) 2021 Boris Iwanski
+
+/*
+ * Copyright (c) 2021 Boris Iwanski
+ * This program is released under GNU General Public License
+ *
+ * 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.
+ *
+ */
+
+#endregion
+
+#region ================== Namespaces
+
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ [FindReplace("Vertex UDMF Field", BrowseButton = false)]
+ internal class FindVertexUDMFField : BaseFindUDMFField
+ {
+ #region ================== Methods
+
+ public override bool CanReplace()
+ {
+ return false;
+ }
+
+ public override bool DetermineVisiblity()
+ {
+ return General.Map.UDMF;
+ }
+
+ public override FindReplaceObject[] Find(string value, bool withinselection, bool replace, string replacewith, bool keepselection)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ return new FindReplaceObject[] { };
+
+ ICollection list = withinselection ? (ICollection)General.Map.Map.GetSelectedVertices(true) : (ICollection)General.Map.Map.Vertices;
+
+ return GetObjects(value, list);
+ }
+
+ #endregion
+ }
+}