UltimateZoneBuilder/Source/Plugins/BuilderModes/ErrorChecks/CheckPolyobjects.cs
MaxED ecb650259b Added "safeboundary" game configuration property. The value determines the maximum map bounding box size considered to be safe by "Check map size" error check.
Changed, Map Analysis mode: some checks are now available only under certain conditions (for example, "Check polyobjects" is now available only when the map is in Hexen or UDMF map format).
Updated documentation ("Game Configuration - Basic Settings" page).
2016-05-18 23:31:12 +00:00

206 lines
8.2 KiB
C#

#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 ================== Properties
// Only possible in Hexen/UDMF map formats
public override bool SkipCheck { get { return (!General.Map.UDMF && !General.Map.HEXEN); } }
#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";
const string Polyobj_ExplicitLine = "Polyobj_ExplicitLine";
// <Polyobj_Action, <Polyobj_number, Lines using this number>>
Dictionary<string, Dictionary<int, List<Linedef>>> polyobjlines = new Dictionary<string, Dictionary<int, List<Linedef>>>();
// All polyobject-related actions
HashSet<string> allactions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
Polyobj_StartLine, Polyobj_ExplicitLine,
"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<int, List<Thing>> anchors = new Dictionary<int, List<Thing>>();
Dictionary<int, List<Thing>> startspots = new Dictionary<int, List<Thing>>();
// 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<int, List<Linedef>>());
// Polyobj number is always the first arg
if(!polyobjlines[id].ContainsKey(l.Args[0]))
polyobjlines[id].Add(l.Args[0], new List<Linedef>());
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<Thing>());
anchors[t.AngleDoom].Add(t);
break;
case "$polyspawn":
case "$polyspawncrush":
case "$polyspawnhurt":
if(!startspots.ContainsKey(t.AngleDoom)) startspots.Add(t.AngleDoom, new List<Thing>());
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<string, Dictionary<int, List<Linedef>>> group in polyobjlines)
{
foreach(KeyValuePair<int, List<Linedef>> 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
if(polyobjlines.ContainsKey(Polyobj_StartLine))
{
foreach(KeyValuePair<int, List<Linedef>> 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)
{
// The value of 0 can mean either "No mirror polyobj" or "Polyobj 0" here...
if(linedef.Args[1] > 0)
{
if(!startspots.ContainsKey(linedef.Args[1]))
SubmitResult(new ResultInvalidPolyobjectLines(new List<Linedef> { linedef }, "\"" + Polyobj_StartLine + "\" action have non-existing Mirror Polyobject Number assigned (" + linedef.Args[1] + "). It won't function correctly ingame."));
if(linedef.Args[1] == linedef.Args[0])
SubmitResult(new ResultInvalidPolyobjectLines(new List<Linedef> { linedef }, "\"" + Polyobj_StartLine + "\" action have the same Polyobject and Mirror Polyobject numbers assigned (" + linedef.Args[1] + "). It won't function correctly ingame."));
}
}
}
}
// Check Linedefs with Polyobj_ExplicitLine action. These can connect 1 - multiple.
// Polyobject number is arg0, Mirror polyobject number is arg2
if(polyobjlines.ContainsKey(Polyobj_ExplicitLine))
{
foreach(KeyValuePair<int, List<Linedef>> linesbytype in polyobjlines[Polyobj_ExplicitLine])
{
// Check if Mirror Polyobject Number exists
foreach(Linedef linedef in linesbytype.Value)
{
// The value of 0 can mean either "No mirror polyobj" or "Polyobj 0" here...
if(linedef.Args[2] > 0)
{
if(!startspots.ContainsKey(linedef.Args[2]))
SubmitResult(new ResultInvalidPolyobjectLines(new List<Linedef> { linedef }, "\"" + Polyobj_StartLine + "\" action have non-existing Mirror Polyobject Number assigned (" + linedef.Args[2] + "). It won't function correctly ingame."));
if(linedef.Args[2] == linedef.Args[0])
SubmitResult(new ResultInvalidPolyobjectLines(new List<Linedef> { linedef }, "\"" + Polyobj_StartLine + "\" action have the same Polyobject and Mirror Polyobject numbers assigned (" + linedef.Args[2] + "). It won't function correctly ingame."));
}
}
}
}
// Check Polyobject Anchors. These must connect 1 - 1.
foreach(KeyValuePair<int, List<Thing>> 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<int, List<Thing>> 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
}
}