UltimateZoneBuilder/Source/Core/GZBuilder/GZDoom/GldefsParser.cs
MaxED 16b97e1c83 Added GLDEFS and (Z)MAPINFO parsers.
Dynamic lights defined in GLDEFS are now rendered in Visual modes (currently only one light per Thing is supported).
Several values from (Z)MAPINFO are now used in Visual modes.
Added "Reload GLDEFS", "Reload (Z)MAPINFO" and "Reload MODELDEF" menu options (in "Tools" section).
MODELDEF parser now checks which model(s) is/are used in first frame.
Dynamic lights are now created with more meaningful default parameters.
Added errors and warnings indicator to main window.
2012-06-01 10:17:47 +00:00

430 lines
No EOL
27 KiB
C#

using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using CodeImp.DoomBuilder.ZDoom;
using CodeImp.DoomBuilder.GZBuilder.Data;
using SlimDX;
using SlimDX.Direct3D9;
namespace CodeImp.DoomBuilder.GZBuilder.GZDoom {
public class GldefsParser : ZDTextParser {
public delegate void IncludeDelegate(GldefsParser parser, string includefile);
public IncludeDelegate OnInclude;
private Dictionary<string, GZDoomLight> lightsByName; //LightName, light definition
private Dictionary<string, string> objects; //ClassName, LightName
public Dictionary<string, GZDoomLight> LightsByName { get { return lightsByName; } }
public Dictionary<string, string> Objects { get { return objects; } }
private List<string> parsedLumps;
private struct GldefsLightType {
public const string POINT = "pointlight";
public const string PULSE = "pulselight";
public const string FLICKER = "flickerlight";
public const string FLICKER2 = "flickerlight2";
public const string SECTOR = "sectorlight";
public static Dictionary<string, int> GLDEFS_TO_GZDOOM_LIGHT_TYPE = new Dictionary<string, int>() { { POINT, 0 }, { PULSE, 1 }, { FLICKER, 2 }, { FLICKER2, 4 }, { SECTOR, 3 } };
}
public GldefsParser() {
parsedLumps = new List<string>();
lightsByName = new Dictionary<string, GZDoomLight>(); //LightName, Light params
objects = new Dictionary<string, string>(); //ClassName, LightName
}
public override bool Parse(Stream stream, string sourcefilename) {
base.Parse(stream, sourcefilename);
if (parsedLumps.IndexOf(sourcefilename) != -1) {
GZBuilder.GZGeneral.LogAndTraceWarning("Error: already parsed '" + sourcefilename + "'. Check your #include directives!");
return false;
}
parsedLumps.Add(sourcefilename);
// Keep local data
Stream localstream = datastream;
string localsourcename = sourcename;
BinaryReader localreader = datareader;
// Continue until at the end of the stream
while (SkipWhitespace(true)) {
string token = ReadToken();
if (!string.IsNullOrEmpty(token)) {
token = StripTokenQuotes(token.ToLowerInvariant()); //Quotes can be anywhere! ANYWHERE!!! And GZDoom will still parse data correctly
//got light structure
if (token == GldefsLightType.POINT || token == GldefsLightType.PULSE || token == GldefsLightType.FLICKER || token == GldefsLightType.FLICKER2 || token == GldefsLightType.SECTOR) {
bool gotErrors = false;
string lightType = token;
GZDoomLight light = new GZDoomLight();
light.Type = GldefsLightType.GLDEFS_TO_GZDOOM_LIGHT_TYPE[lightType];
//find classname
SkipWhitespace(true);
string lightName = StripTokenQuotes(ReadToken()).ToLowerInvariant();
if (!string.IsNullOrEmpty(lightName)) {
//now find opening brace
SkipWhitespace(true);
token = ReadToken();
if (token != "{") {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected '{', but got " + token);
continue; //something wrong with gldefs declaration, continue to next one
}
//read gldefs light structure
while (SkipWhitespace(true)) {
token = ReadToken();
if (!string.IsNullOrEmpty(token)) {
token = token.ToLowerInvariant();
//color
if (token == "color") {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Red)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Red Color value, but got '" + token + "'");
gotErrors = true;
break;
}
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Green)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Green Color value, but got '" + token + "'");
gotErrors = true;
break;
}
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Blue)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Blue Color value, but got '" + token + "'");
gotErrors = true;
break;
}
//size
} else if (token == "size") {
if (lightType != GldefsLightType.SECTOR) {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.PrimaryRadius)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Size value, but got '" + token + "'");
gotErrors = true;
break;
}
light.PrimaryRadius *= 2;
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": '" + token + "' is not valid property for " + lightType);
gotErrors = true;
break;
}
//offset
} else if (token == "offset") {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!ReadSignedFloat(token, ref light.Offset.X)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Offset X value, but got '" + token + "'");
gotErrors = true;
break;
}
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!ReadSignedFloat(token, ref light.Offset.Z)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Offset X value, but got '" + token + "'");
gotErrors = true;
break;
}
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!ReadSignedFloat(token, ref light.Offset.Y)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Offset X value, but got '" + token + "'");
gotErrors = true;
break;
}
//subtractive
} else if (token == "subtractive") {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
int i;
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Subtractive value, but got '" + token + "'");
gotErrors = true;
break;
}
light.Subtractive = i == 1;
//dontlightself
} else if (token == "dontlightself") {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
int i;
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Dontlightself value, but got '" + token + "'");
gotErrors = true;
break;
}
light.DontLightSelf = (i == 1);
//interval
} else if (token == "interval") {
if (lightType == GldefsLightType.PULSE || lightType == GldefsLightType.FLICKER2) {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
float interval;
if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out interval)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Interval value, but got '" + token + "'");
gotErrors = true;
break;
}
//I wrote logic for dynamic lights animation first, so here I modify gldefs settings to fit in existing logic
if (lightType == GldefsLightType.PULSE) {
light.Interval = (int)(interval * 35); //measured in tics (35 per second) in PointLightPulse, measured in seconds in gldefs' PulseLight
} else { //FLICKER2. Seems like PointLightFlickerRandom to me
light.Interval = (int)(interval * 350); //0.1 is one second for FlickerLight2
}
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": '"+token+"' is not valid property for " + lightType);
gotErrors = true;
break;
}
//secondarysize
} else if (token == "secondarysize") {
if (lightType == GldefsLightType.PULSE || lightType == GldefsLightType.FLICKER || lightType == GldefsLightType.FLICKER2) {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.SecondaryRadius)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected SecondarySize value, but got '" + token + "'");
gotErrors = true;
break;
}
light.SecondaryRadius *= 2;
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": '" + token + "' is not valid property for " + lightType);
gotErrors = true;
break;
}
//chance
} else if (token == "chance") {
if (lightType == GldefsLightType.FLICKER) {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
float chance;
if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out chance)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Chance value, but got '" + token + "'");
gotErrors = true;
break;
}
//transforming from 0.0 .. 1.0 to 0 .. 359 to fit in existing logic
light.Interval = (int)(chance * 359.0f);
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": '" + token + "' is not valid property for " + lightType);
gotErrors = true;
break;
}
//scale
} else if (token == "scale") {
if (lightType == GldefsLightType.SECTOR) {
SkipWhitespace(true);
token = StripTokenQuotes(ReadToken());
float scale;
if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out scale)) {
// Not numeric!
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected Scale value, but got '" + token + "'");
gotErrors = true;
break;
}
if (scale < 0.0f || scale > 1.0f) {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": scale must be in 0.0 - 1.0 range, but is " + scale);
gotErrors = true;
break;
}
//sector light doesn't have animation, so we will store it's size in Interval
//transforming from 0.0 .. 1.0 to 0 .. 10 to preserve value.
light.Interval = (int)(scale * 10.0f);
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": '" + token + "' is not valid property for " + lightType);
gotErrors = true;
break;
}
//end of structure
} else if (token == "}") {
if (!gotErrors) {
//general checks
if (light.Color.Red == 0.0f && light.Color.Green == 0.0f && light.Color.Blue == 0.0f) {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": light Color is " + light.Color.Red + "," + light.Color.Green + "," + light.Color.Blue + ". It won't be shown in GZDoom!");
gotErrors = true;
}
//light-type specific checks
if (light.Type == (int)GZDoomLightType.NORMAL) {
if (light.PrimaryRadius == 0) {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": light Size is 0. It won't be shown in GZDoom!");
gotErrors = true;
}
}
if (light.Type == (int)GZDoomLightType.FLICKER || light.Type == (int)GZDoomLightType.PULSE || light.Type == (int)GZDoomLightType.RANDOM) {
if (light.PrimaryRadius == 0 && light.SecondaryRadius == 0) {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": 'Size' and 'SecondarySize' are 0. This light won't be shown in GZDoom!");
gotErrors = true;
}
}
//offset it slightly to avoid shading glitches
if (light.Offset.Y == 0)
light.Offset.Y = 0.1f;
if (!gotErrors) {
if (lightsByName.ContainsKey(lightName)) {
lightsByName[lightName] = light;
} else {
lightsByName.Add(lightName, light);
}
}
}
break; //break out of this parsing loop
}
}
}
}
} else if (token == "object") {
SkipWhitespace(true);
//read object class
string objectClass = StripTokenQuotes(ReadToken()).ToLowerInvariant();
if (!string.IsNullOrEmpty(objectClass)) {
//now find opening brace
SkipWhitespace(true);
token = ReadToken();
if (token != "{") {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": expected '{', but got " + token);
continue;
}
int bracesCount = 1;
bool foundLight = false;
//read frames structure
while (SkipWhitespace(true)) {
token = ReadToken();
if (!string.IsNullOrEmpty(token)) {
token = StripTokenQuotes(token).ToLowerInvariant();
if (!foundLight && token == "light") { //just use first light from first frame and be done with it
SkipWhitespace(true);
token = ReadToken().ToLowerInvariant(); //should be light name
if (!string.IsNullOrEmpty(token)) {
if (lightsByName.ContainsKey(token)) {
if (objects.ContainsKey(objectClass))
objects[objectClass] = token;
else
objects.Add(objectClass, token);
foundLight = true;
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": light declaration not found for light '" + token + "'");
}
}
} else if (token == "{") { //continue in this loop until object structure ends
bracesCount++;
} else if (token == "}") {
if (--bracesCount <= 0)
break; //This was Cave Johnson. And we are done here.
}
}
}
}
} else if (token == "#include") {
SkipWhitespace(true);
string includeLump = StripTokenQuotes(ReadToken()).ToLowerInvariant();
if (!string.IsNullOrEmpty(includeLump)) {
// Callback to parse this file
if (OnInclude != null)
OnInclude(this, includeLump.Replace("/", "\\"));
// Set our buffers back to continue parsing
datastream = localstream;
datareader = localreader;
sourcename = localsourcename;
} else {
GZBuilder.GZGeneral.LogAndTraceWarning("Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber() + ": got #include directive with missing or incorrect path: '" + includeLump + "'");
}
} else {
// Unknown structure!
string token2;
do {
if (!SkipWhitespace(true)) break;
token2 = ReadToken();
if (token2 == null) break;
}
while (token2 != "{");
int scopelevel = 1;
do {
if (!SkipWhitespace(true)) break;
token2 = ReadToken();
if (token2 == null) break;
if (token2 == "{") scopelevel++;
if (token2 == "}") scopelevel--;
}
while (scopelevel > 0);
}
}
}
if (objects.Count > 0)
return true;
return false;
}
}
}