diff --git a/Build/Scripting/ZDoom_DECORATE.cfg b/Build/Scripting/ZDoom_DECORATE.cfg index deed3b47..aafc65fc 100644 --- a/Build/Scripting/ZDoom_DECORATE.cfg +++ b/Build/Scripting/ZDoom_DECORATE.cfg @@ -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"; diff --git a/Help/gc_decoratekeys.html b/Help/gc_decoratekeys.html index edb89ec6..b10d4961 100644 --- a/Help/gc_decoratekeys.html +++ b/Help/gc_decoratekeys.html @@ -15,7 +15,17 @@

DECORATE keys

-

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

GZDoom Builder includes a DECORATE parser used to obtain relevant information (such as editor number, Radius, Height, Scale, and so on) from custom actors.
+

+ Global definitions (GZDB only): +
+ Additional information can be conveyed with keys in the form of special comments inserted in the global DECORATE block:

+ //$GZDB_SKIP
+ 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.
+

+ Actor definitions: +
Additional information can be conveyed with keys in the form of special comments inserted within an actor's declaration block:
+
//$Angled - GZDB only.
//$NotAngled - GZDB only.
@@ -50,7 +60,7 @@ //$ArgNEnum <string or structure> - GZDB only.
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 "//$ArgN" property.

- //$Color <color index> - GZDB only.
+ //$Color <color index> - GZDB only.
Allows to override category color for this actor. Possible values are:

//$Obsolete <reason> - GZDB only.
Marks the thing as obsolete. It will be detected by "Check obsolete things" Map Analysis Mode check and will be marked in the Thing Properties Window and the Thing Info panel.
-

- -

Additionaly, you can use the following special comments in the global block:


- //$GZDB_SKIP - GZDB only.
- 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.
-
+
Note: 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.

Example:
@@ -136,6 +141,59 @@ actor SuperGiantImpBall : DoomImpBall Scale 8.0 Damage 30 } + +

+Category definitions (GZDB only):
+
+Thing categories can be defined in DECORATE using #region / #endregion blocks. Region name is used as thing category title. "//$Category" actor block definition overrides these. Additional information can be conveyed with keys in the form of special comments inserted within #region / #endregion block (these keys replicate Game Configuration thing category properties with a slightly different syntax):
+
+ //$Sorted (integer) [0 .. 1]
+ When set to 1, items in this category will be sorted by title.
+
+ //$Color (integer) [0 .. 19]
+ Sets the color used by things in this category.
Uses the same values as "//$Color" thing definition.
+ +

//$Arrow (integer) [0 .. 1]
+ When set to 1, thing angle will be shown as an arrow in Classic and Visual modes.
+
+ //$Sprite (string)
+ 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 "CPOSA2".
+ You can also use images from the"Sprites" directory by prefixing an image name without extension with "internal:", like so: "internal:Actor".
+
+ //$Error (integer) [0 .. 2]
+ Sets the stuck things error checking mode to use on this thing.
+ Possible values are: + +

+ Default value is 1.
+
+ //$FixedSize (boolean)
+ When set to true, thing will be rendered as sizeless in Classic and Visual modes.
+
+ //$FixedRotation (boolean)
+ When set to true, thing's angle won't be changed when rotating things using Edit Selection mode.
+
+ //$AbsoluteZ (boolean)
+ When set to true, thing's vertical position will be used as an absolute value instead of distance from floor/ceiling in Visual mode.
+
+ Example:
+
+#region Imp Balls
+//$Color 4
+//$Sprite "BALLS0"
+
+  actor SuperGiantImpBall : DoomImpBall
+  {
+    //$Title "I'm in the 'Imp Balls' group!"
+    Scale 8.0
+    Damage 30
+  }
+
+#endregion  
 

diff --git a/Help/gc_thingsettings.html b/Help/gc_thingsettings.html index 6adc5a89..3df49c81 100644 --- a/Help/gc_thingsettings.html +++ b/Help/gc_thingsettings.html @@ -71,7 +71,7 @@ thingrenderstyles sorted (integer) [0 .. 1]
When set to 1, items in this category will be sorted by title.

- Thing and Thing Category definitions:
+ Thing and Thing Category definitions:
These settings can be used inside of both thing category and thing definitions.

color (integer) [0 .. 19]
@@ -96,7 +96,7 @@ thingrenderstyles
  • 15 - White;
  • 16 - Pink;
  • 17 - Light Orange;
  • -
  • 18 - Light Brown (default);
  • +
  • 18 - Light Brown (default);
  • 19 - Orange;
  • arrow (integer) [0 .. 1]
    diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index c5e7da93..8e9c1137 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -891,6 +891,7 @@ + diff --git a/Source/Core/Config/ThingCategory.cs b/Source/Core/Config/ThingCategory.cs index a2a08555..1e221473 100644 --- a/Source/Core/Config/ThingCategory.cs +++ b/Source/Core/Config/ThingCategory.cs @@ -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); diff --git a/Source/Core/Data/DataManager.cs b/Source/Core/Data/DataManager.cs index 3395843b..52e98221 100644 --- a/Source/Core/Data/DataManager.cs +++ b/Source/Core/Data/DataManager.cs @@ -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 spawnnumsoverride, Dictionary 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 categories, string[] catnames) + private static ThingCategory GetThingCategory(ThingCategory parent, List 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(actor.CategoryInfo.Category); + catinfo.Properties = new Dictionary>(actor.CategoryInfo.Properties); + } + else + { + catinfo.Category = new List { "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) diff --git a/Source/Core/ZDoom/ActorStructure.cs b/Source/Core/ZDoom/ActorStructure.cs index 28cd1b1c..903e992a 100644 --- a/Source/Core/ZDoom/ActorStructure.cs +++ b/Source/Core/ZDoom/ActorStructure.cs @@ -54,6 +54,9 @@ namespace CodeImp.DoomBuilder.ZDoom // Properties private Dictionary> props; private readonly Dictionary uservars; //mxd + + //mxd. Categories + private DecorateCategoryInfo catinfo; // States private Dictionary 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 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(StringComparer.OrdinalIgnoreCase); props = new Dictionary>(StringComparer.OrdinalIgnoreCase); states = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/Source/Core/ZDoom/DecorateCategoryInfo.cs b/Source/Core/ZDoom/DecorateCategoryInfo.cs new file mode 100644 index 00000000..f0307e33 --- /dev/null +++ b/Source/Core/ZDoom/DecorateCategoryInfo.cs @@ -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 Category; + public Dictionary> Properties; + + #endregion + + #region ================== Constructor + + public DecorateCategoryInfo() + { + Category = new List(1); + Properties = new Dictionary>(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 + } +} diff --git a/Source/Core/ZDoom/DecorateParser.cs b/Source/Core/ZDoom/DecorateParser.cs index d80dbf58..c5e3a9a9 100644 --- a/Source/Core/ZDoom/DecorateParser.cs +++ b/Source/Core/ZDoom/DecorateParser.cs @@ -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(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 regions = new List(); //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>(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 { (SkipWhitespace(false) ? ReadLine() : "") }; + } + else + { + // Skip the whole line, then carry on + ReadLine(); + } break; } diff --git a/Source/Core/ZDoom/ZDTextParser.cs b/Source/Core/ZDoom/ZDTextParser.cs index 9aff38ce..129a91ee 100644 --- a/Source/Core/ZDoom/ZDTextParser.cs +++ b/Source/Core/ZDoom/ZDTextParser.cs @@ -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(StringComparer.OrdinalIgnoreCase); //mxd untrackedtextresources = new HashSet(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")