Added, DECORATE support: #region blocks are now used as thing categories ("//$Category" actor block definition overrides these). Additional category-wide special comments are now available (see documentation).

Updated documentation ("DECORATE keys" page).
This commit is contained in:
MaxED 2016-05-13 13:41:09 +00:00
parent b1e5d8b5be
commit 74f749fd68
10 changed files with 268 additions and 47 deletions

View file

@ -25,8 +25,8 @@ keywords
{
//Editor special comments
//These are handled in a different fascion: key is replaced with the value and the caret is placed at [EP] position
$Angled = "$Angled";
$NotAngled = "$NotAngled";
$Angled = "//$Angled";
$NotAngled = "//$NotAngled";
$Category = "//$Category \"[EP]\"";
$Sprite = "//$Sprite \"[EP]\"";
$IgnoreRenderstyle = "//$IgnoreRenderstyle";
@ -59,6 +59,13 @@ keywords
$Color = "//$Color ";
$Obsolete = "//$Obsolete \"[EP]\"";
$GZDB_SKIP = "//$GZDB_SKIP";
//Editor special comments for thing categories ($Sprite and $Color are already defined above)
$Sort = "//$Sort ";
$Arrow = "//$Arrow ";
$Error = "//$Error ";
$FixedSize = "//$FixedSize ";
$FixedRotation = "//$FixedRotation ";
$AbsoluteZ = "//$AbsoluteZ ";
//Preprocessor directives
#Include = "#Include";
#region = "#region";

View file

@ -15,7 +15,17 @@
<h1>DECORATE keys</h1>
</div>
<div id="contents">
<p> Doom Builder 2 includes a DECORATE parser used to obtain relevant information (such as editor number, Radius, Height, Scale, and so on) from custom actors. Additional information can be conveyed with keys in the form of special comments inserted within an actor's declaration block:<br />
<p>GZDoom Builder includes a DECORATE parser used to obtain relevant information (such as editor number, Radius, Height, Scale, and so on) from custom actors.<br />
<br /><br />
<span class="big">Global definitions (GZDB only):</span>
<br />
Additional information can be conveyed with keys in the form of special comments inserted in the global DECORATE block:<br /><br />
<strong>//$GZDB_SKIP</strong><br />
DECORATE parser will stop parsing a file after encountering this comment. This can be used to speedup the parsing process by skipping files, which don't contain placeable actor definitions.<br />
<br /><br />
<span class="big">Actor definitions:</span>
<br />Additional information can be conveyed with keys in the form of special comments inserted within an actor's declaration block:<br />
<br />
<strong>//$Angled</strong> - <span class="red">GZDB only</span>.<br />
<strong>//$NotAngled</strong> - <span class="red">GZDB only</span>.<br />
@ -50,7 +60,7 @@
<strong><a name="argenum" id="argenum"></a>//$ArgNEnum &lt;string or structure&gt;</strong> - <span class="red">GZDB only</span>.<br />
Allows to specify an enum for this argument. This can be either a name of an enum defined in the Game Configuration, or an explicit enum definition. This property can only be used in conjunction with &quot;<strong>//$ArgN</strong>&quot; property.<br />
<br />
<strong>//$Color &lt;color index&gt;</strong> - <span class="red">GZDB only</span>.<br />
<strong><a name="color" id="color"></a>//$Color &lt;color index&gt;</strong> - <span class="red">GZDB only</span>.<br />
Allows to override category color for this actor. Possible values are:
<ul>
<li>&nbsp;&nbsp;0 - <span style="color:#696969">&#9608;</span> Dark Gray;</li>
@ -71,17 +81,12 @@
<li>15 - <span style="color:#F5F5F5">&#9608;</span> White;</li>
<li>16 - <span style="color:#FFB6C1">&#9608;</span> Pink;</li>
<li>17 - <span style="color:#FF8C00">&#9608;</span> Light Orange;</li>
<li>18 - <span style="color:#BDB76B">&#9608;</span> Light Brown (default);</li>
<li>18 - <span style="color:#BDB76B">&#9608;</span> Light Brown (<strong>default</strong>);</li>
<li>19 - <span style="color:#DAA520">&#9608;</span> Orange;</li>
</ul>
<strong>//$Obsolete &lt;reason&gt;</strong> - <span class="red">GZDB only</span>.<br />
Marks the thing as obsolete. It will be detected by &quot;Check obsolete things&quot; <a href="e_mapanalysis.html">Map Analysis Mode</a> check and will be marked in the <a href="w_thingedit.html">Thing Properties Window</a> and the Thing Info panel.<br />
<br /><br />
<h3>Additionaly, you can use the following special comments in the global block:</h3><br />
<strong>//$GZDB_SKIP</strong> - <span class="red">GZDB only</span>.<br />
DECORATE parser will stop parsing a file after encountering this comment. This can be used to speedup the parsing process by skipping files, which don't contain placeable actor definitions.<br />
<br />
<br />
<strong>Note:</strong> It is a good idea to include the Doom Editor Number after the actor definition if you want it to show in Doom Builder, even if it is replacing a standard Doom actor.<br />
<br />
<strong>Example:</strong><br />
@ -136,6 +141,59 @@ actor SuperGiantImpBall : DoomImpBall
Scale 8.0
Damage 30
}
</pre>
<br /><br />
<span class="big">Category definitions (GZDB only):</span><br />
<br />
Thing categories can be defined in DECORATE using <strong>#region</strong> / <strong>#endregion</strong> blocks. Region name is used as thing category title. &quot;<strong>//$Category</strong>&quot; actor block definition overrides these. Additional information can be conveyed with keys in the form of special comments inserted within <strong>#region</strong> / <strong>#endregion</strong> block (these keys replicate Game Configuration <a href="gc_thingsettings.html#catdefs">thing category properties</a> with a slightly different syntax):<br />
<br />
<b class="fat">//$Sorted</b> (integer) [0 .. 1]<br />
When set to 1, items in this category will be sorted by title.<br />
<br />
<b class="fat">//$Color</b> (integer) [0 .. 19]<br />
Sets the color used by things in this category.<br />Uses the same values as &quot;<strong>//$Color</strong>&quot; <a href="#color">thing definition</a>.<br />
<p><b class="fat">//$Arrow</b> (integer) [0 .. 1]<br />
When set to 1, thing angle will be shown as an arrow in Classic and Visual modes.<br />
<br />
<b class="fat">//$Sprite</b> (string)<br />
Sets the sprite graphic to use when rendering this thing or things in this category. This should be fully qualified sprite name without file extension, like &quot;CPOSA2&quot;.<br />
You can also use images from the"Sprites" directory by prefixing an image name without extension with &quot;internal:&quot;, like so: &quot;internal:Actor&quot;.<br />
<br />
<b class="fat">//$Error</b> (integer) [0 .. 2]<br />
Sets the stuck things error checking mode to use on this thing. <br />
Possible values are:
<ul>
<li>0 - Don't check this thing.</li>
<li>1 - Check if the thing is outside of map geometry.</li>
<li>2 - Check if the thing is outside of map geometry or stuck in another thing.</li>
</ul>
Default value is<strong> 1</strong>.<br />
<br />
<b class="fat">//$FixedSize</b> (boolean)<br />
When set to true, thing will be rendered as sizeless in Classic and Visual modes.<br />
<br />
<b class="fat">//$FixedRotation</b> (boolean)<br />
When set to true, thing's angle won't be changed when rotating things using Edit Selection mode.<br />
<br />
<b class="fat">//$AbsoluteZ</b> (boolean)<br />
When set to true, thing's vertical position will be used as an absolute value instead of distance from floor/ceiling in Visual mode.<br />
<br />
<strong>Example:</strong><br />
<pre>
#region Imp Balls
//$Color 4
//$Sprite "BALLS0"
actor SuperGiantImpBall : DoomImpBall
{
//$Title "I'm in the 'Imp Balls' group!"
Scale 8.0
Damage 30
}
#endregion
</pre>
</p>
</div>

View file

@ -71,7 +71,7 @@ thingrenderstyles
<b class="fat">sorted</b> (integer) [0 .. 1]<br />
When set to 1, items in this category will be sorted by title.<br />
<br />
<span class="big">Thing and Thing Category definitions:</span><br />
<span class="big"><a name="catdefs" id="catdefs"></a>Thing and Thing Category definitions:</span><br />
These settings can be used inside of both thing category and thing definitions.<br />
<br />
<b class="fat">color</b> (integer) [0 .. 19]<br />
@ -96,7 +96,7 @@ thingrenderstyles
<li>15 - <span style="color:#F5F5F5">&#9608;</span> White;</li>
<li>16 - <span style="color:#FFB6C1">&#9608;</span> Pink;</li>
<li>17 - <span style="color:#FF8C00">&#9608;</span> Light Orange;</li>
<li>18 - <span style="color:#BDB76B">&#9608;</span> Light Brown (default);</li>
<li>18 - <span style="color:#BDB76B">&#9608;</span> Light Brown (<strong>default</strong>);</li>
<li>19 - <span style="color:#DAA520">&#9608;</span> Orange;</li>
</ul>
<p><b class="fat">arrow</b> (integer) [0 .. 1]<br />

View file

@ -891,6 +891,7 @@
<Compile Include="Rendering\VisualVertexHandle.cs" />
<Compile Include="Geometry\Line3D.cs" />
<Compile Include="Data\Scripting\ScriptHandlerAttribute.cs" />
<Compile Include="ZDoom\DecorateCategoryInfo.cs" />
<Compile Include="ZDoom\Scripting\DecorateParserSE.cs" />
<Compile Include="ZDoom\GldefsParser.cs" />
<Compile Include="ZDoom\MapinfoParser.cs" />

View file

@ -21,6 +21,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.ZDoom;
#endregion
@ -96,7 +97,7 @@ namespace CodeImp.DoomBuilder.Config
#region ================== Constructor / Disposer
// Constructor
internal ThingCategory(ThingCategory parent, string name, string title)
internal ThingCategory(ThingCategory parent, string name, string title, DecorateCategoryInfo catinfo)
{
// Initialize
this.name = name;
@ -142,6 +143,19 @@ namespace CodeImp.DoomBuilder.Config
this.absolutez = false;
this.spritescale = 1.0f;
}
//mxd. Apply DecorateCategoryInfo overrides...
if(catinfo != null && catinfo.Properties.Count > 0)
{
this.sprite = catinfo.GetPropertyValueString("$sprite", 0, this.sprite);
this.sorted = (catinfo.GetPropertyValueInt("$sort", 0, (this.sorted ? 1 : 0)) != 0);
this.color = catinfo.GetPropertyValueInt("$color", 0, this.color);
this.arrow = catinfo.GetPropertyValueInt("$arrow", 0, this.arrow);
this.errorcheck = catinfo.GetPropertyValueInt("$error", 0, this.errorcheck);
this.fixedsize = catinfo.GetPropertyValueBool("$fixedsize", 0, this.fixedsize);
this.fixedrotation = catinfo.GetPropertyValueBool("$fixedrotation", 0, this.fixedrotation);
this.absolutez = catinfo.GetPropertyValueBool("$absolutez", 0, this.absolutez);
}
// We have no destructor
GC.SuppressFinalize(this);

View file

@ -64,6 +64,7 @@ namespace CodeImp.DoomBuilder.Data
public const string INTERNAL_PREFIX = "internal:";
public const int CLASIC_IMAGE_NAME_LENGTH = 8; //mxd
private const int MAX_SKYTEXTURE_SIZE = 2048; //mxd
internal static readonly char[] CATEGORY_SPLITTER = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; //mxd
private long UNKNOWN_THING; //mxd
private long MISSING_THING; //mxd
@ -1735,7 +1736,6 @@ namespace CodeImp.DoomBuilder.Data
private int LoadDecorateThings(Dictionary<int, string> spawnnumsoverride, Dictionary<int, string> doomednumsoverride)
{
int counter = 0;
char[] catsplitter = { Path.AltDirectorySeparatorChar }; //mxd
// Create new parser
decorate = new DecorateParser { OnInclude = LoadDecorateFromLocation };
@ -1792,13 +1792,6 @@ namespace CodeImp.DoomBuilder.Data
// Check if we want to add this actor
if(actor.DoomEdNum > 0)
{
string catname = ZDTextParser.StripQuotes(actor.GetPropertyAllValues("$category"));
string[] catnames; //mxd
if(string.IsNullOrEmpty(catname.Trim()))
catnames = new[] { "decorate" };
else
catnames = catname.Split(catsplitter, StringSplitOptions.RemoveEmptyEntries); //mxd
// Check if we can find this thing in our existing collection
if(thingtypes.ContainsKey(actor.DoomEdNum))
{
@ -1808,7 +1801,7 @@ namespace CodeImp.DoomBuilder.Data
else
{
// Find the category to put the actor in
ThingCategory cat = GetThingCategory(null, thingcategories, catnames); //mxd
ThingCategory cat = GetThingCategory(null, thingcategories, GetCategoryInfo(actor)); //mxd
// Add new thing
ThingTypeInfo t = new ThingTypeInfo(cat, actor);
@ -1864,14 +1857,7 @@ namespace CodeImp.DoomBuilder.Data
if(actor != null)
{
// Find the category to put the actor in
string catname = ZDTextParser.StripQuotes(actor.GetPropertyAllValues("$category"));
string[] catnames; //mxd
if(string.IsNullOrEmpty(catname.Trim()))
catnames = new[] { "decorate" };
else
catnames = catname.Split(catsplitter, StringSplitOptions.RemoveEmptyEntries); //mxd
ThingCategory cat = GetThingCategory(null, thingcategories, catnames); //mxd
ThingCategory cat = GetThingCategory(null, thingcategories, GetCategoryInfo(actor)); //mxd
// Add a new ThingTypeInfo, replacing already existing one if necessary
ThingTypeInfo info = new ThingTypeInfo(cat, actor, group.Key);
@ -1965,17 +1951,21 @@ namespace CodeImp.DoomBuilder.Data
}
//mxd
private static ThingCategory GetThingCategory(ThingCategory parent, List<ThingCategory> categories, string[] catnames)
private static ThingCategory GetThingCategory(ThingCategory parent, List<ThingCategory> categories, DecorateCategoryInfo catinfo)
{
// Find the category to put the actor in
ThingCategory cat = null;
string catname = catnames[0].ToLowerInvariant().Trim();
string catname = (catinfo.Category.Count > 0 ? catinfo.Category[0].Trim().ToLowerInvariant() : string.Empty); //catnames[0].ToLowerInvariant().Trim();
if(string.IsNullOrEmpty(catname)) catname = "decorate";
// First search by Title...
foreach(ThingCategory c in categories)
{
if(c.Title.ToLowerInvariant() == catname) cat = c;
if(string.Equals(c.Title, catname, StringComparison.OrdinalIgnoreCase))
{
cat = c;
break;
}
}
// Make full name
@ -1986,29 +1976,58 @@ namespace CodeImp.DoomBuilder.Data
{
foreach(ThingCategory c in categories)
{
if(c.Name.ToLowerInvariant() == catname) cat = c;
if(string.Equals(c.Name, catname, StringComparison.OrdinalIgnoreCase))
{
cat = c;
break;
}
}
}
// Make the category if needed
if(cat == null)
{
string cattitle = catnames[0].Trim();
string cattitle = (catinfo.Category.Count > 0 ? catinfo.Category[0].Trim() : string.Empty);
if(string.IsNullOrEmpty(cattitle)) cattitle = "Decorate";
cat = new ThingCategory(parent, catname, cattitle);
cat = new ThingCategory(parent, catname, cattitle, catinfo);
categories.Add(cat); // ^.^
}
// Still have subcategories?
if(catnames.Length > 1)
if(catinfo.Category.Count > 1)
{
string[] remainingnames = new string[catnames.Length - 1];
Array.Copy(catnames, 1, remainingnames, 0, remainingnames.Length);
return GetThingCategory(cat, cat.Children, remainingnames);
catinfo.Category.RemoveAt(0);
return GetThingCategory(cat, cat.Children, catinfo);
}
return cat;
}
//mxd
private static DecorateCategoryInfo GetCategoryInfo(ActorStructure actor)
{
string catname = ZDTextParser.StripQuotes(actor.GetPropertyAllValues("$category")).Trim();
DecorateCategoryInfo catinfo = new DecorateCategoryInfo();
if(string.IsNullOrEmpty(catname))
{
if(actor.CategoryInfo != null)
{
catinfo.Category = new List<string>(actor.CategoryInfo.Category);
catinfo.Properties = new Dictionary<string, List<string>>(actor.CategoryInfo.Properties);
}
else
{
catinfo.Category = new List<string> { "Decorate" };
}
}
else
{
catinfo.Category = catname.Split(CATEGORY_SPLITTER, StringSplitOptions.RemoveEmptyEntries).ToList(); //mxd
}
return catinfo;
}
// This loads Decorate data from a specific file or lump name
private void LoadDecorateFromLocation(DecorateParser parser, string location)

View file

@ -54,6 +54,9 @@ namespace CodeImp.DoomBuilder.ZDoom
// Properties
private Dictionary<string, List<string>> props;
private readonly Dictionary<string, UniversalType> uservars; //mxd
//mxd. Categories
private DecorateCategoryInfo catinfo;
// States
private Dictionary<string, StateStructure> states;
@ -70,15 +73,17 @@ namespace CodeImp.DoomBuilder.ZDoom
public ActorStructure BaseClass { get { return baseclass; } }
internal int DoomEdNum { get { return doomednum; } set { doomednum = value; } }
public Dictionary<string, UniversalType> UserVars { get { return uservars; } } //mxd
internal DecorateCategoryInfo CategoryInfo { get { return catinfo; } } //mxd
#endregion
#region ================== Constructor / Disposer
// Constructor
internal ActorStructure(DecorateParser parser)
internal ActorStructure(DecorateParser parser, DecorateCategoryInfo catinfo)
{
// Initialize
this.catinfo = catinfo; //mxd
flags = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
props = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
states = new Dictionary<string, StateStructure>(StringComparer.OrdinalIgnoreCase);

View file

@ -0,0 +1,72 @@
#region ================== Namespaces
using System;
using System.Collections.Generic;
using System.Globalization;
#endregion
namespace CodeImp.DoomBuilder.ZDoom
{
internal class DecorateCategoryInfo
{
#region ================== Properties
public List<string> Category;
public Dictionary<string, List<string>> Properties;
#endregion
#region ================== Constructor
public DecorateCategoryInfo()
{
Category = new List<string>(1);
Properties = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
}
#endregion
#region ================== Methods
public string GetPropertyValueString(string propname, int valueindex, string defaultvalue) { return GetPropertyValueString(propname, valueindex, defaultvalue, true); }
public string GetPropertyValueString(string propname, int valueindex, string defaultvalue, bool stripquotes)
{
if(Properties.ContainsKey(propname) && (Properties[propname].Count > valueindex))
return (stripquotes ? ZDTextParser.StripQuotes(Properties[propname][valueindex]) : Properties[propname][valueindex]);
return defaultvalue;
}
public bool GetPropertyValueBool(string propname, int valueindex, bool defaultvalue)
{
string str = GetPropertyValueString(propname, valueindex, string.Empty, false).ToLowerInvariant();
return (string.IsNullOrEmpty(str) ? defaultvalue : str == "true");
}
public int GetPropertyValueInt(string propname, int valueindex, int defaultvalue)
{
string str = GetPropertyValueString(propname, valueindex, string.Empty, false);
// It can be negative...
if(str == "-" && Properties.Count > valueindex + 1)
str += GetPropertyValueString(propname, valueindex + 1, String.Empty, false);
int intvalue;
return (int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out intvalue) ? intvalue : defaultvalue);
}
public float GetPropertyValueFloat(string propname, int valueindex, float defaultvalue)
{
string str = GetPropertyValueString(propname, valueindex, string.Empty, false);
// It can be negative...
if(str == "-" && Properties.Count > valueindex + 1)
str += GetPropertyValueString(propname, valueindex + 1, string.Empty, false);
float fvalue;
return (float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out fvalue) ? fvalue : defaultvalue);
}
#endregion
}
}

View file

@ -100,6 +100,7 @@ namespace CodeImp.DoomBuilder.ZDoom
// Syntax
whitespace = "\n \t\r\u00A0"; //mxd. non-breaking space is also space :)
specialtokens = ":{}[]()+-\n;,";
skipregions = false; //mxd
// Initialize
actors = new Dictionary<string, ActorStructure>(StringComparer.OrdinalIgnoreCase);
@ -141,6 +142,9 @@ namespace CodeImp.DoomBuilder.ZDoom
// Cannot process?
if(!base.Parse(data, clearerrors)) return false;
//mxd. Region-as-category stuff...
List<DecorateCategoryInfo> regions = new List<DecorateCategoryInfo>(); //mxd
// Keep local data
Stream localstream = datastream;
@ -163,7 +167,7 @@ namespace CodeImp.DoomBuilder.ZDoom
case "actor":
{
// Read actor structure
ActorStructure actor = new ActorStructure(this);
ActorStructure actor = new ActorStructure(this, (regions.Count > 0 ? regions[regions.Count - 1] : null));
if(this.HasError) return false;
// Add the actor
@ -290,13 +294,52 @@ namespace CodeImp.DoomBuilder.ZDoom
}
break;
//mxd. Region-as-category handling
case "#region":
SkipWhitespace(false);
string cattitle = ReadLine();
if(!string.IsNullOrEmpty(cattitle))
{
// Make new category info
string[] parts = cattitle.Split(DataManager.CATEGORY_SPLITTER, StringSplitOptions.RemoveEmptyEntries);
DecorateCategoryInfo info = new DecorateCategoryInfo();
if(regions.Count > 0)
{
// Preserve nesting
info.Category.AddRange(regions[regions.Count - 1].Category);
info.Properties = new Dictionary<string, List<string>>(regions[regions.Count - 1].Properties);
}
info.Category.AddRange(parts);
// Add to collection
regions.Add(info);
}
break;
//mxd. Region-as-category handling
case "#endregion":
if(regions.Count > 0)
regions.RemoveAt(regions.Count - 1);
else
LogWarning("Unexpected #endregion token");
break;
default:
{
//mxd. In some special cases (like the whole actor commented using "//") our special comments will be detected here...
if(objdeclaration.StartsWith("$"))
{
// So skip the whole line, then carry on
ReadLine();
if(regions.Count > 0)
{
// Store region property
regions[regions.Count - 1].Properties[objdeclaration] = new List<string> { (SkipWhitespace(false) ? ReadLine() : "") };
}
else
{
// Skip the whole line, then carry on
ReadLine();
}
break;
}

View file

@ -46,6 +46,7 @@ namespace CodeImp.DoomBuilder.ZDoom
// Parsing
protected string whitespace = "\n \t\r\u00A0\0"; //mxd. non-breaking space is also space :)
protected string specialtokens = ":{}+-\n;";
protected bool skipregions; //mxd
// Input data stream
protected Stream datastream;
@ -90,6 +91,7 @@ namespace CodeImp.DoomBuilder.ZDoom
errordesc = null;
textresources = new Dictionary<string, TextResource>(StringComparer.OrdinalIgnoreCase); //mxd
untrackedtextresources = new HashSet<string>(StringComparer.OrdinalIgnoreCase); //mxd
skipregions = true; //mxd
}
#endregion
@ -276,7 +278,7 @@ namespace CodeImp.DoomBuilder.ZDoom
}
}
//mxd. Region/endregion handling
else if(c == '#')
else if(skipregions && c == '#')
{
string s = ReadToken(false).ToLowerInvariant();
if(s == "region" || s == "endregion")