diff --git a/Build/Configurations/Includes/Hexen_things.cfg b/Build/Configurations/Includes/Hexen_things.cfg
index 2135c09..ef47836 100644
--- a/Build/Configurations/Includes/Hexen_things.cfg
+++ b/Build/Configurations/Includes/Hexen_things.cfg
@@ -1518,6 +1518,7 @@ other
title = "Polyobject Anchor";
sprite = "internal:anchor";
fixedrotation = true;
+ error = 0; // Can be outside of map geometry
}
3001
{
diff --git a/Build/Configurations/Includes/ZDoom_things.cfg b/Build/Configurations/Includes/ZDoom_things.cfg
index a8cdb86..7871031 100644
--- a/Build/Configurations/Includes/ZDoom_things.cfg
+++ b/Build/Configurations/Includes/ZDoom_things.cfg
@@ -1091,6 +1091,7 @@ zdoom
sprite = "internal:anchor";
class = "$PolyAnchor";
fixedrotation = true;
+ error = 0; // Can be outside of map geometry
}
9301
diff --git a/Build/Scripting/ZDoom_DECORATE.cfg b/Build/Scripting/ZDoom_DECORATE.cfg
index 194679d..b4bd611 100644
--- a/Build/Scripting/ZDoom_DECORATE.cfg
+++ b/Build/Scripting/ZDoom_DECORATE.cfg
@@ -238,7 +238,7 @@ keywords
A_SetRipMin = "A_SetRipMin(int min)";
A_SetRipMax = "A_SetRipMax(int max)";
A_SetRoll = "A_SetRoll(float roll[, int flags = 0[, int pointer = AAPTR_DEFAULT]])";
- A_SetScale = "A_SetScale(float scaleX[, float scaleY = scaleX[, int pointer = AAPTR_DEFAULT]])";
+ A_SetScale = "A_SetScale(float scaleX[, float scaleY = scaleX[, int pointer = AAPTR_DEFAULT[, bool usezero = false]]])";
A_SetShadow = "A_SetShadow";
A_SetShootable = "A_SetShootable";
A_SetSolid = "A_SetSolid";
diff --git a/Source/Plugins/BuilderModes/BuilderModes.csproj b/Source/Plugins/BuilderModes/BuilderModes.csproj
index 84dad06..c76d445 100644
--- a/Source/Plugins/BuilderModes/BuilderModes.csproj
+++ b/Source/Plugins/BuilderModes/BuilderModes.csproj
@@ -347,6 +347,7 @@
+
@@ -355,6 +356,8 @@
+
+
diff --git a/Source/Plugins/BuilderModes/ErrorChecks/CheckPolyobjects.cs b/Source/Plugins/BuilderModes/ErrorChecks/CheckPolyobjects.cs
new file mode 100644
index 0000000..3d1c2c7
--- /dev/null
+++ b/Source/Plugins/BuilderModes/ErrorChecks/CheckPolyobjects.cs
@@ -0,0 +1,167 @@
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Map;
+using System.Threading;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ [ErrorChecker("Check Polyobjects", true, 100)]
+ public class CheckPolyobjects : ErrorChecker
+ {
+ #region ================== Constants
+
+ private const int PROGRESS_STEP = 1000;
+
+ #endregion
+
+ #region ================== Constructor / Destructor
+
+ public CheckPolyobjects()
+ {
+ // Total progress is somewhat done when all linedefs and things are checked
+ SetTotalProgress((General.Map.Map.Linedefs.Count + General.Map.Map.Things.Count) / PROGRESS_STEP);
+ }
+
+ #endregion
+
+ #region ================== Methods
+
+ // This runs the check
+ public override void Run()
+ {
+ int progress = 0;
+ int stepprogress = 0;
+ const string Polyobj_StartLine = "Polyobj_StartLine";
+
+ // >
+ Dictionary>> polyobjlines = new Dictionary>>();
+
+ // All polyobject-related actions
+ HashSet allactions = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ Polyobj_StartLine, "Polyobj_RotateLeft",
+ "Polyobj_RotateRight", "Polyobj_Move",
+ "Polyobj_MoveTimes8", "Polyobj_DoorSwing",
+ "Polyobj_DoorSlide", "Polyobj_OR_MoveToSpot",
+ "Polyobj_MoveToSpot", "Polyobj_Stop",
+ "Polyobj_MoveTo", "Polyobj_OR_MoveTo",
+ "Polyobj_OR_RotateLeft", "Polyobj_OR_RotateRight",
+ "Polyobj_OR_Move", "Polyobj_OR_MoveTimes8"
+ };
+
+ Dictionary> anchors = new Dictionary>();
+ Dictionary> startspots = new Dictionary>();
+
+ // Collect Linedefs...
+ foreach(Linedef l in General.Map.Map.Linedefs)
+ {
+ if(l.Action > 0 && General.Map.Config.LinedefActions.ContainsKey(l.Action) && allactions.Contains(General.Map.Config.LinedefActions[l.Action].Id))
+ {
+ string id = General.Map.Config.LinedefActions[l.Action].Id;
+
+ if(!polyobjlines.ContainsKey(id))
+ polyobjlines.Add(id, new Dictionary>());
+
+ // Polyobj number is always the first arg
+ if(!polyobjlines[id].ContainsKey(l.Args[0]))
+ polyobjlines[id].Add(l.Args[0], new List());
+
+ polyobjlines[id][l.Args[0]].Add(l);
+ }
+
+ UpdateProgress(ref progress, ref stepprogress);
+ }
+
+ // Collect Things...
+ foreach(Thing t in General.Map.Map.Things)
+ {
+ ThingTypeInfo info = General.Map.Data.GetThingInfoEx(t.Type);
+ if(info == null) continue;
+ switch(info.ClassName.ToLowerInvariant())
+ {
+ case "$polyanchor":
+ if(!anchors.ContainsKey(t.AngleDoom)) anchors.Add(t.AngleDoom, new List());
+ anchors[t.AngleDoom].Add(t);
+ break;
+
+ case "$polyspawn":
+ case "$polyspawncrush":
+ case "$polyspawnhurt":
+ if(!startspots.ContainsKey(t.AngleDoom)) startspots.Add(t.AngleDoom, new List());
+ startspots[t.AngleDoom].Add(t);
+ break;
+ }
+
+ UpdateProgress(ref progress, ref stepprogress);
+ }
+
+ // Check Linedefs. These can connect 1 - multiple (except Polyobj_StartLine)
+ // Polyobject number is always arg0.
+ foreach(KeyValuePair>> group in polyobjlines)
+ {
+ foreach(KeyValuePair> linesbytype in group.Value)
+ {
+ if(!startspots.ContainsKey(linesbytype.Key))
+ SubmitResult(new ResultInvalidPolyobjectLines(linesbytype.Value, "\"" + group.Key + "\" action targets non-existing Polyobject Start Spot (" + linesbytype.Key + ")"));
+ }
+ }
+
+ // Check Linedefs with Polyobj_StartLine action. These must connect 1 - 1.
+ // Polyobject number is arg0, Mirror polyobject number is arg1
+ foreach(KeyValuePair> linesbytype in polyobjlines[Polyobj_StartLine])
+ {
+ // Should be only one Polyobj_StartLine per Polyobject number
+ if(linesbytype.Value.Count > 1)
+ SubmitResult(new ResultInvalidPolyobjectLines(linesbytype.Value, "Several \"" + Polyobj_StartLine + "\" actions have the same Polyobject Number assigned (" + linesbytype.Key + "). They won't function correctly ingame."));
+
+ // Check if Mirror Polyobject Number exists
+ foreach(Linedef linedef in linesbytype.Value)
+ {
+ if(!startspots.ContainsKey(linedef.Args[1]))
+ SubmitResult(new ResultInvalidPolyobjectLines(new List { linedef }, "\"" + Polyobj_StartLine + "\" action have non-existing Mirror Polyobject Number assigned (" + linedef.Args[1] + "). It won't function correctly ingame."));
+ }
+ }
+
+ // Check Polyobject Anchors. These must connect 1 - 1.
+ foreach(KeyValuePair> group in anchors)
+ {
+ if(!startspots.ContainsKey(group.Key))
+ SubmitResult(new ResultInvalidPolyobjectThings(group.Value, "Polyobject " + (group.Value.Count > 1 ? "Anchors target" : "Anchor targets") + " non-existing Polyobject Start Spot (" + group.Key + ")"));
+
+ if(group.Value.Count > 1)
+ SubmitResult(new ResultInvalidPolyobjectThings(group.Value, "Several Polyobject Anchors target the same Polyobject Start Spot (" + group.Key + "). They won't function correctly ingame."));
+ }
+
+ // Check Polyobject Start Spots. These must connect 1 - 1.
+ foreach(KeyValuePair> group in startspots)
+ {
+ if(!anchors.ContainsKey(group.Key))
+ SubmitResult(new ResultInvalidPolyobjectThings(group.Value, "Polyobject Start " + (group.Value.Count > 1 ? "Spots are not targeted" : "Spot " + group.Key + " is not targeted") + " by any Polyobject Anchor"));
+
+ if(group.Value.Count > 1)
+ SubmitResult(new ResultInvalidPolyobjectThings(group.Value, "Several Polyobject Start Spots have the same Polyobject number (" + group.Key + "). They won't function correctly ingame."));
+ }
+ }
+
+ private void UpdateProgress(ref int progress, ref int stepprogress)
+ {
+ // Handle thread interruption
+ try { Thread.Sleep(0); }
+ catch(ThreadInterruptedException) { return; }
+
+ // We are making progress!
+ if((++progress / PROGRESS_STEP) > stepprogress)
+ {
+ stepprogress = (progress / PROGRESS_STEP);
+ AddProgress(1);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/ErrorChecks/ResultInvalidPolyobjectLines.cs b/Source/Plugins/BuilderModes/ErrorChecks/ResultInvalidPolyobjectLines.cs
new file mode 100644
index 0000000..8759bc5
--- /dev/null
+++ b/Source/Plugins/BuilderModes/ErrorChecks/ResultInvalidPolyobjectLines.cs
@@ -0,0 +1,94 @@
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Rendering;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ public class ResultInvalidPolyobjectLines : ErrorResult
+ {
+ #region ================== Variables
+
+ private readonly List lines;
+ private readonly string linesinfo;
+
+ #endregion
+
+ #region ================== Properties
+
+ public override int Buttons { get { return 0; } }
+
+ #endregion
+
+ #region ================== Constructor / Destructor
+
+ public ResultInvalidPolyobjectLines(List lines, string details)
+ {
+ // Initialize
+ this.lines = lines;
+ this.hidden = true;
+ foreach(Linedef l in lines)
+ {
+ this.viewobjects.Add(l);
+ this.hidden &= l.IgnoredErrorChecks.Contains(this.GetType());
+ }
+
+ if(lines.Count == 1)
+ {
+ linesinfo = "Incorrect Polyobject setup for linedef " + lines[0].Index;
+ }
+ else
+ {
+ linesinfo = "Incorrect Polyobject setup for linedefs " + lines[0].Index;
+ for(int i = 1; i < lines.Count - 1; i++) linesinfo += ", " + lines[i].Index;
+ linesinfo += " and " + lines[lines.Count - 1].Index;
+ }
+
+ this.description = linesinfo + ": " + details;
+ }
+
+ #endregion
+
+ #region ================== Methods
+
+ // This sets if this result is displayed in ErrorCheckForm (mxd)
+ internal override void Hide(bool hide)
+ {
+ hidden = hide;
+ Type t = this.GetType();
+ if(hide)
+ {
+ foreach(Linedef l in lines)
+ l.IgnoredErrorChecks.Add(t);
+ }
+ else
+ {
+ foreach(Linedef l in lines)
+ if(l.IgnoredErrorChecks.Contains(t)) l.IgnoredErrorChecks.Remove(t);
+ }
+ }
+
+ // This must return the string that is displayed in the listbox
+ public override string ToString()
+ {
+ return linesinfo;
+ }
+
+ // Rendering
+ public override void PlotSelection(IRenderer2D renderer)
+ {
+ foreach(Linedef l in lines)
+ {
+ renderer.PlotLinedef(l, General.Colors.Selection);
+ renderer.PlotVertex(l.Start, ColorCollection.VERTICES);
+ renderer.PlotVertex(l.End, ColorCollection.VERTICES);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Plugins/BuilderModes/ErrorChecks/ResultInvalidPolyobjectThings.cs b/Source/Plugins/BuilderModes/ErrorChecks/ResultInvalidPolyobjectThings.cs
new file mode 100644
index 0000000..fd7bb85
--- /dev/null
+++ b/Source/Plugins/BuilderModes/ErrorChecks/ResultInvalidPolyobjectThings.cs
@@ -0,0 +1,89 @@
+#region ================== Namespaces
+
+using System;
+using System.Collections.Generic;
+using CodeImp.DoomBuilder.Map;
+using CodeImp.DoomBuilder.Rendering;
+
+#endregion
+
+namespace CodeImp.DoomBuilder.BuilderModes
+{
+ public class ResultInvalidPolyobjectThings : ErrorResult
+ {
+ #region ================== Variables
+
+ private readonly List things;
+ private readonly string thingsinfo;
+
+ #endregion
+
+ #region ================== Properties
+
+ public override int Buttons { get { return 0; } }
+
+ #endregion
+
+ #region ================== Constructor / Destructor
+
+ public ResultInvalidPolyobjectThings(List things, string details)
+ {
+ // Initialize
+ this.things = things;
+ this.hidden = true;
+ foreach(Thing t in things)
+ {
+ this.viewobjects.Add(t);
+ this.hidden &= t.IgnoredErrorChecks.Contains(this.GetType());
+ }
+
+ if(things.Count == 1)
+ {
+ thingsinfo = "Incorrect Polyobject setup for thing " + things[0].Index;
+ }
+ else
+ {
+ thingsinfo = "Incorrect Polyobject setup for things " + things[0].Index;
+ for(int i = 1; i < things.Count - 1; i++) thingsinfo += ", " + things[i].Index;
+ thingsinfo += " and " + things[things.Count - 1].Index;
+ }
+
+ this.description = thingsinfo + ": " + details;
+ }
+
+ #endregion
+
+ #region ================== Methods
+
+ // This sets if this result is displayed in ErrorCheckForm (mxd)
+ internal override void Hide(bool hide)
+ {
+ hidden = hide;
+ Type t = this.GetType();
+ if(hide)
+ {
+ foreach(Thing thing in things) thing.IgnoredErrorChecks.Add(t);
+ }
+ else
+ {
+ foreach(Thing thing in things)
+ if(thing.IgnoredErrorChecks.Contains(t)) thing.IgnoredErrorChecks.Remove(t);
+ }
+ }
+
+ // This must return the string that is displayed in the listbox
+ public override string ToString()
+ {
+ return thingsinfo;
+ }
+
+ // Rendering
+ public override void RenderOverlaySelection(IRenderer2D renderer)
+ {
+ foreach(Thing thing in things)
+ renderer.RenderThing(thing, General.Colors.Selection, Presentation.THINGS_ALPHA);
+ }
+
+ #endregion
+ }
+}