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 + } +}