mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 14:12:16 +00:00
835 lines
No EOL
24 KiB
C#
Executable file
835 lines
No EOL
24 KiB
C#
Executable file
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using CodeImp.DoomBuilder.Config;
|
|
using CodeImp.DoomBuilder.Data;
|
|
using CodeImp.DoomBuilder.GZBuilder.Data;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
using CodeImp.DoomBuilder.IO;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.ZDoom
|
|
{
|
|
internal sealed class GldefsParser : ZDTextParser
|
|
{
|
|
#region ================== Constants
|
|
|
|
private const int DEFAULT_GLOW_HEIGHT = 64;
|
|
|
|
#endregion
|
|
|
|
#region ================== Structs
|
|
|
|
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 readonly Dictionary<string, DynamicLightType> GLDEFS_TO_GZDOOM_LIGHT_TYPE = new Dictionary<string, DynamicLightType>(StringComparer.Ordinal) { { POINT, DynamicLightType.NORMAL }, { PULSE, DynamicLightType.PULSE }, { FLICKER, DynamicLightType.FLICKER }, { FLICKER2, DynamicLightType.RANDOM }, { SECTOR, DynamicLightType.SECTOR } };
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Delegates
|
|
|
|
public delegate void IncludeDelegate(GldefsParser parser, string includefile, bool clearerrors);
|
|
public IncludeDelegate OnInclude;
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
private readonly Dictionary<string, DynamicLightData> lightsbyname; //LightName, light definition
|
|
private readonly Dictionary<string, string> objects; //ClassName, LightName
|
|
private readonly Dictionary<long, GlowingFlatData> glowingflats;
|
|
private readonly Dictionary<string, SkyboxInfo> skyboxes;
|
|
private readonly HashSet<string> parsedlumps;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
internal override ScriptType ScriptType { get { return ScriptType.GLDEFS; } }
|
|
|
|
internal Dictionary<string, DynamicLightData> LightsByName { get { return lightsbyname; } }
|
|
internal Dictionary<string, string> Objects { get { return objects; } }
|
|
internal Dictionary<long, GlowingFlatData> GlowingFlats { get { return glowingflats; } }
|
|
internal Dictionary<string, SkyboxInfo> Skyboxes { get { return skyboxes; } }
|
|
|
|
#endregion
|
|
|
|
#region ================== Constructor
|
|
|
|
public GldefsParser()
|
|
{
|
|
// Syntax
|
|
whitespace = "\n \t\r\u00A0";
|
|
specialtokens = ",{}\n";
|
|
|
|
parsedlumps = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
lightsbyname = new Dictionary<string, DynamicLightData>(StringComparer.OrdinalIgnoreCase); //LightName, Light params
|
|
objects = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); //ClassName, LightName
|
|
glowingflats = new Dictionary<long, GlowingFlatData>(); // Texture name hash, Glowing Flat Data
|
|
skyboxes = new Dictionary<string, SkyboxInfo>(StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Parsing
|
|
|
|
public override bool Parse(TextResourceData data, bool clearerrors)
|
|
{
|
|
//mxd. Already parsed?
|
|
if(!base.AddTextResource(data))
|
|
{
|
|
if(clearerrors) ClearError();
|
|
return true;
|
|
}
|
|
|
|
// Cannot process?
|
|
if(!base.Parse(data, clearerrors)) return false;
|
|
|
|
// Keep local data
|
|
Stream localstream = datastream;
|
|
string localsourcename = sourcename;
|
|
int localsourcelumpindex = sourcelumpindex;
|
|
BinaryReader localreader = datareader;
|
|
DataLocation locallocation = datalocation;
|
|
string localtextresourcepath = textresourcepath;
|
|
|
|
// Continue until at the end of the stream
|
|
while(SkipWhitespace(true))
|
|
{
|
|
string token = ReadToken().ToLowerInvariant();
|
|
if(string.IsNullOrEmpty(token)) continue;
|
|
|
|
switch(token)
|
|
{
|
|
case GldefsLightType.POINT: case GldefsLightType.PULSE: case GldefsLightType.SECTOR:
|
|
case GldefsLightType.FLICKER: case GldefsLightType.FLICKER2:
|
|
if(!ParseLight(token)) return false;
|
|
break;
|
|
|
|
case "object":
|
|
if(!ParseObject()) return false;
|
|
break;
|
|
|
|
case "glow":
|
|
if(!ParseGlowingFlats()) return false;
|
|
break;
|
|
|
|
case "skybox":
|
|
if(!ParseSkybox()) return false;
|
|
break;
|
|
|
|
case "#include":
|
|
if(!ParseInclude(clearerrors)) return false;
|
|
|
|
// Set our buffers back to continue parsing
|
|
datastream = localstream;
|
|
datareader = localreader;
|
|
sourcename = localsourcename;
|
|
sourcelumpindex = localsourcelumpindex;
|
|
datalocation = locallocation;
|
|
textresourcepath = localtextresourcepath;
|
|
break;
|
|
|
|
case "$gzdb_skip": return !this.HasError;
|
|
|
|
default:
|
|
// Unknown structure!
|
|
SkipStructure();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// All done
|
|
return !this.HasError;
|
|
}
|
|
|
|
private bool ParseLight(string lighttype)
|
|
{
|
|
DynamicLightData light = new DynamicLightData { Type = GldefsLightType.GLDEFS_TO_GZDOOM_LIGHT_TYPE[lighttype] };
|
|
|
|
// Find classname
|
|
SkipWhitespace(true);
|
|
string lightname = StripQuotes(ReadToken());
|
|
|
|
if(string.IsNullOrEmpty(lightname))
|
|
{
|
|
ReportError("Expected " + lighttype + " name");
|
|
return false;
|
|
}
|
|
|
|
// Now find opening brace
|
|
if(!NextTokenIs("{", false))
|
|
{
|
|
ReportError("Expected opening brace");
|
|
return false;
|
|
}
|
|
|
|
// Read gldefs light structure
|
|
while(SkipWhitespace(true))
|
|
{
|
|
string token = ReadToken().ToLowerInvariant();
|
|
if(string.IsNullOrEmpty(token)) continue;
|
|
|
|
switch(token)
|
|
{
|
|
case "color":
|
|
SkipWhitespace(true);
|
|
token = ReadToken();
|
|
if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Red))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Red color value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
SkipWhitespace(true);
|
|
token = ReadToken();
|
|
if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Green))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Green color value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
SkipWhitespace(true);
|
|
token = ReadToken();
|
|
if(!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out light.Color.Blue))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Blue color value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
// Check the value
|
|
if(light.Color.Red == 0.0f && light.Color.Green == 0.0f && light.Color.Blue == 0.0f)
|
|
{
|
|
LogWarning("\"" + lightname + "\" light Color value is "
|
|
+ light.Color.Red + "," + light.Color.Green + "," + light.Color.Blue
|
|
+ ". It won't be shown in GZDoom");
|
|
}
|
|
else if(light.Color.Red > 1.0f || light.Color.Green > 1.0f || light.Color.Blue > 1.0f ||
|
|
light.Color.Red < 0.0f || light.Color.Green < 0.0f || light.Color.Blue < 0.0f)
|
|
{
|
|
// Clamp values
|
|
light.Color.Red = General.Clamp(light.Color.Red, 0.0f, 1.0f);
|
|
light.Color.Green = General.Clamp(light.Color.Green, 0.0f, 1.0f);
|
|
light.Color.Blue = General.Clamp(light.Color.Blue, 0.0f, 1.0f);
|
|
|
|
// Notify user
|
|
LogWarning("\"" + lightname + "\" light Color value was clamped. Color values must be in [0.0 .. 1.0] range");
|
|
}
|
|
break;
|
|
|
|
case "size":
|
|
if(lighttype != GldefsLightType.SECTOR)
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out light.PrimaryRadius))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Size value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
if(light.PrimaryRadius < 0)
|
|
{
|
|
ReportError("Size value should be positive, but is \"" + light.PrimaryRadius + "\"");
|
|
return false;
|
|
}
|
|
|
|
light.PrimaryRadius *= 2;
|
|
|
|
}
|
|
else
|
|
{
|
|
ReportError("\"" + token + "\" is not valid property for " + lighttype);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case "offset":
|
|
SkipWhitespace(true);
|
|
token = ReadToken();
|
|
if(!ReadSignedFloat(token, ref light.Offset.X))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Offset X value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
SkipWhitespace(true);
|
|
token = ReadToken();
|
|
if(!ReadSignedFloat(token, ref light.Offset.Z))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Offset Y value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
SkipWhitespace(true);
|
|
token = ReadToken();
|
|
if(!ReadSignedFloat(token, ref light.Offset.Y))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Offset Z value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case "subtractive":
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
int i;
|
|
if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
|
|
{
|
|
// Not numeric!
|
|
ReportError("expected Subtractive value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
light.Style = (i == 1) ? DynamicLightRenderStyle.NEGATIVE : DynamicLightRenderStyle.NORMAL;
|
|
}
|
|
break;
|
|
|
|
case "attenuate":
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
int i;
|
|
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
|
|
{
|
|
// Not numeric!
|
|
ReportError("expected Attenuate value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
light.Style = (i == 1) ? DynamicLightRenderStyle.ATTENUATED : DynamicLightRenderStyle.NORMAL;
|
|
}
|
|
break;
|
|
|
|
case "dontlightself":
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
int i;
|
|
if(!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected DontLightSelf value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
light.DontLightSelf = (i == 1);
|
|
}
|
|
break;
|
|
|
|
case "interval":
|
|
if(lighttype == GldefsLightType.PULSE || lighttype == GldefsLightType.FLICKER2)
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
float interval = 0f;
|
|
if(!ReadSignedFloat(token, ref interval))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Interval value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
if(interval <= 0)
|
|
{
|
|
ReportError("Interval value should be greater than zero, but is \"" + interval + "\"");
|
|
return false;
|
|
}
|
|
|
|
//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
|
|
{
|
|
ReportError("\"" + token + "\" is not valid property for " + lighttype);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case "secondarysize":
|
|
if(lighttype == GldefsLightType.PULSE || lighttype == GldefsLightType.FLICKER || lighttype == GldefsLightType.FLICKER2)
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
if(!ReadSignedInt(token, ref light.SecondaryRadius))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected SecondarySize value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
if(light.SecondaryRadius < 0)
|
|
{
|
|
ReportError("SecondarySize value should be positive, but is \"" + light.SecondaryRadius + "\"");
|
|
return false;
|
|
}
|
|
|
|
light.SecondaryRadius *= 2;
|
|
}
|
|
else
|
|
{
|
|
ReportError("\"" + token + "\" is not valid property for " + lighttype);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case "chance":
|
|
if(lighttype == GldefsLightType.FLICKER)
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
float chance = 0f;
|
|
if(!ReadSignedFloat(token, ref chance))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Chance value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
// Check range
|
|
if(chance > 1.0f || chance < 0.0f)
|
|
{
|
|
ReportError("Chance must be in [0.0 .. 1.0] range, but is " + chance);
|
|
return false;
|
|
}
|
|
|
|
// Transforming from 0.0 .. 1.0 to 0 .. 359 to fit in existing logic
|
|
light.Interval = (int)(chance * 359.0f);
|
|
}
|
|
else
|
|
{
|
|
ReportError("\"" + token + "\" is not valid property for " + lighttype);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case "scale":
|
|
if(lighttype == GldefsLightType.SECTOR)
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
token = ReadToken();
|
|
float scale = 0f;
|
|
if(!ReadSignedFloat(token, ref scale))
|
|
{
|
|
// Not numeric!
|
|
ReportError("Expected Scale value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
// Check range
|
|
if(scale > 1.0f || scale < 0.0f)
|
|
{
|
|
ReportError("Scale must be in [0.0 .. 1.0] range, but is " + scale);
|
|
return false;
|
|
}
|
|
|
|
//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
|
|
{
|
|
ReportError("\"" + token + "\" is not valid property for " + lighttype);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case "}":
|
|
{
|
|
bool skip = (light.Color.Red == 0.0f && light.Color.Green == 0.0f && light.Color.Blue == 0.0f);
|
|
|
|
// Light-type specific checks
|
|
if(light.Type == DynamicLightType.NORMAL && light.PrimaryRadius == 0)
|
|
{
|
|
LogWarning("\"" + lightname + "\" light Size is 0. It won't be shown in GZDoom");
|
|
skip = true;
|
|
}
|
|
|
|
if(light.Type == DynamicLightType.FLICKER || light.Type == DynamicLightType.PULSE || light.Type == DynamicLightType.RANDOM)
|
|
{
|
|
if(light.PrimaryRadius == 0 && light.SecondaryRadius == 0)
|
|
{
|
|
LogWarning("\"" + lightname + "\" light Size and SecondarySize are 0. It won't be shown in GZDoom");
|
|
skip = true;
|
|
}
|
|
}
|
|
|
|
// Add to the collection?
|
|
if(!skip) lightsbyname[lightname] = light;
|
|
|
|
// Break out of this parsing loop
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// All done here
|
|
return true;
|
|
}
|
|
|
|
private bool ParseObject()
|
|
{
|
|
SkipWhitespace(true);
|
|
|
|
// Read object class
|
|
string objectclass = StripQuotes(ReadToken());
|
|
|
|
if(string.IsNullOrEmpty(objectclass))
|
|
{
|
|
ReportError("Expected object class");
|
|
return false;
|
|
}
|
|
|
|
// Check if actor exists
|
|
if(General.Map.Data.GetZDoomActor(objectclass) == null)
|
|
LogWarning("DECORATE class \"" + objectclass + "\" does not exist");
|
|
|
|
// Now find opening brace
|
|
if(!NextTokenIs("{", false))
|
|
{
|
|
ReportError("Expected opening brace");
|
|
return false;
|
|
}
|
|
|
|
int bracescount = 1;
|
|
bool foundlight = false;
|
|
bool foundframe = false;
|
|
|
|
// Read frames structure
|
|
while(SkipWhitespace(true))
|
|
{
|
|
string token = ReadToken().ToLowerInvariant();
|
|
if(string.IsNullOrEmpty(token)) continue;
|
|
|
|
if(!foundlight && !foundframe && token == "frame")
|
|
{
|
|
SkipWhitespace(true);
|
|
token = ReadToken().ToLowerInvariant(); // Should be frame name
|
|
|
|
// Use this frame if it's 4 characters long or it's the first frame
|
|
foundframe = (token.Length == 4 || (token.Length > 4 && token[4] == 'a'));
|
|
}
|
|
else if(!foundlight && foundframe && token == "light") // Just use first light and be done with it
|
|
{
|
|
SkipWhitespace(true);
|
|
token = StripQuotes(ReadToken()); // Should be light name
|
|
|
|
if(!string.IsNullOrEmpty(token))
|
|
{
|
|
objects[objectclass] = token;
|
|
foundlight = true;
|
|
}
|
|
}
|
|
else if(token == "{") // Continue in this loop until object structure ends
|
|
{
|
|
bracescount++;
|
|
}
|
|
else if(token == "}")
|
|
{
|
|
if(--bracescount < 1) break; // This was Cave Johnson. And we are done here.
|
|
}
|
|
}
|
|
|
|
// All done here
|
|
return true;
|
|
}
|
|
|
|
private bool ParseGlowingFlats()
|
|
{
|
|
// Next sould be opening brace
|
|
if(!NextTokenIs("{", false))
|
|
{
|
|
ReportError("Expected opening brace");
|
|
return false;
|
|
}
|
|
|
|
// Parse inner blocks
|
|
while(SkipWhitespace(true))
|
|
{
|
|
string token = ReadToken().ToLowerInvariant();
|
|
if(token == "}") break; // End of Glow structure
|
|
|
|
switch(token)
|
|
{
|
|
case "walls":
|
|
case "flats":
|
|
if(!NextTokenIs("{", false))
|
|
{
|
|
ReportError("Expected opening brace");
|
|
return false;
|
|
}
|
|
|
|
while(SkipWhitespace(true))
|
|
{
|
|
token = StripQuotes(ReadToken(false));
|
|
if(string.IsNullOrEmpty(token)) continue;
|
|
if(token == "}") break;
|
|
|
|
// Add glow data. Hash the name exactly as given.
|
|
long flatnamehash = Lump.MakeLongName(token, true);
|
|
glowingflats[flatnamehash] = new GlowingFlatData
|
|
{
|
|
Height = DEFAULT_GLOW_HEIGHT * 2,
|
|
Fullbright = true,
|
|
Color = new PixelColor(255, 255, 255, 255),
|
|
CalculateTextureColor = true
|
|
};
|
|
}
|
|
break;
|
|
|
|
case "texture":
|
|
{
|
|
PixelColor color = new PixelColor();
|
|
int glowheight = DEFAULT_GLOW_HEIGHT;
|
|
string texturename;
|
|
|
|
if(!ReadTextureName(out texturename)) return false;
|
|
if(string.IsNullOrEmpty(texturename))
|
|
{
|
|
ReportError("Expected " + token + " name");
|
|
return false;
|
|
}
|
|
|
|
// Now we should find a comma
|
|
if(!NextTokenIs(",", false))
|
|
{
|
|
ReportError("Expected a comma");
|
|
return false;
|
|
}
|
|
|
|
// Next is color
|
|
SkipWhitespace(true);
|
|
token = ReadToken(false);
|
|
|
|
if(!GetColorFromString(token, ref color))
|
|
{
|
|
ReportError("Expected glow color value, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
// The glow data is valid at thispoint. Hash the name exactly as given.
|
|
long texturehash = Lump.MakeLongName(texturename, true);
|
|
|
|
// Now we can find a comma
|
|
if(!NextTokenIs(",", false))
|
|
{
|
|
// Add glow data
|
|
glowingflats[texturehash] = new GlowingFlatData
|
|
{
|
|
Height = glowheight * 2,
|
|
Color = color.WithAlpha(255),
|
|
CalculateTextureColor = false
|
|
};
|
|
continue;
|
|
}
|
|
|
|
// Can be glow height
|
|
SkipWhitespace(true);
|
|
token = ReadToken();
|
|
|
|
int h;
|
|
if(int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out h))
|
|
{
|
|
// Can't pass glowheight directly cause TryParse will unconditionally set it to 0
|
|
glowheight = h;
|
|
|
|
// Now we can find a comma
|
|
if(!NextTokenIs(",", false))
|
|
{
|
|
// Add glow data
|
|
glowingflats[texturehash] = new GlowingFlatData
|
|
{
|
|
Height = glowheight * 2,
|
|
Color = color.WithAlpha(255),
|
|
CalculateTextureColor = false
|
|
};
|
|
continue;
|
|
}
|
|
|
|
// Read the flag
|
|
SkipWhitespace(true);
|
|
token = ReadToken().ToLowerInvariant();
|
|
}
|
|
|
|
// Next must be "fullbright" flag
|
|
if(token != "fullbright")
|
|
{
|
|
ReportError("Expected \"fullbright\" flag, but got \"" + token + "\"");
|
|
return false;
|
|
}
|
|
|
|
// Add glow data
|
|
glowingflats[texturehash] = new GlowingFlatData
|
|
{
|
|
Height = glowheight * 2,
|
|
Fullbright = true,
|
|
Color = color.WithAlpha(255),
|
|
CalculateTextureColor = false
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// All done here
|
|
return true;
|
|
}
|
|
|
|
private bool ParseSkybox()
|
|
{
|
|
SkipWhitespace(true);
|
|
string name;
|
|
|
|
if(!ReadTextureName(out name)) return false;
|
|
if(string.IsNullOrEmpty(name))
|
|
{
|
|
ReportError("Expected skybox name");
|
|
return false;
|
|
}
|
|
|
|
if(skyboxes.ContainsKey(name)) LogWarning("Skybox \"" + name + "\" is double defined");
|
|
|
|
SkyboxInfo info = new SkyboxInfo(name.ToUpperInvariant());
|
|
|
|
// FlipTop / opening brace
|
|
SkipWhitespace(true);
|
|
string token = ReadToken();
|
|
if(token.ToLowerInvariant() == "fliptop")
|
|
{
|
|
info.FlipTop = true;
|
|
if(!NextTokenIs("{")) return false;
|
|
}
|
|
else if(token != "{")
|
|
{
|
|
ReportError("Expected opening brace or \"fliptop\" keyword");
|
|
return false;
|
|
}
|
|
|
|
// Read skybox texture names
|
|
while(SkipWhitespace(true))
|
|
{
|
|
token = ReadToken();
|
|
if(token == "}") break;
|
|
info.Textures.Add(token);
|
|
}
|
|
|
|
// Sanity check. Should have 3 or 6 textrues
|
|
if(info.Textures.Count != 3 && info.Textures.Count != 6)
|
|
{
|
|
ReportError("Expected 3 or 6 skybox textures");
|
|
return false;
|
|
}
|
|
|
|
// Add to collection
|
|
skyboxes[name] = info;
|
|
|
|
// All done here
|
|
return true;
|
|
}
|
|
|
|
private bool ParseInclude(bool clearerrors)
|
|
{
|
|
//INFO: GZDoom GLDEFS include paths can't be relative ("../glstuff.txt")
|
|
//or absolute ("d:/project/glstuff.txt")
|
|
//or have backward slashes ("info\glstuff.txt")
|
|
//include paths are relative to the first parsed entry, not the current one
|
|
//also include paths may or may not be quoted
|
|
SkipWhitespace(true);
|
|
string includelump = StripQuotes(ReadToken(false)); // Don't skip newline
|
|
|
|
// Sanity checks
|
|
if(string.IsNullOrEmpty(includelump))
|
|
{
|
|
ReportError("Expected file name to include");
|
|
return false;
|
|
}
|
|
|
|
// Check invalid path chars
|
|
if(!CheckInvalidPathChars(includelump)) return false;
|
|
|
|
// Absolute paths are not supported...
|
|
if(Path.IsPathRooted(includelump))
|
|
{
|
|
ReportError("Absolute include paths are not supported by GZDoom");
|
|
return false;
|
|
}
|
|
|
|
// Relative paths are not supported
|
|
if(includelump.StartsWith(RELATIVE_PATH_MARKER) || includelump.StartsWith(CURRENT_FOLDER_PATH_MARKER) ||
|
|
includelump.StartsWith(ALT_RELATIVE_PATH_MARKER) || includelump.StartsWith(ALT_CURRENT_FOLDER_PATH_MARKER))
|
|
{
|
|
ReportError("Relative include paths are not supported by GZDoom");
|
|
return false;
|
|
}
|
|
|
|
// Backward slashes are not supported
|
|
if(includelump.Contains(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture)))
|
|
{
|
|
ReportError("Only forward slashes are supported by GZDoom");
|
|
return false;
|
|
}
|
|
|
|
// Already parsed?
|
|
if(parsedlumps.Contains(includelump))
|
|
{
|
|
ReportError("Already parsed \"" + includelump + "\". Check your #include directives");
|
|
return false;
|
|
}
|
|
|
|
// Add to collection
|
|
parsedlumps.Add(includelump);
|
|
|
|
// Callback to parse this file
|
|
if(OnInclude != null) OnInclude(this, includelump, clearerrors);
|
|
|
|
// All done here
|
|
return !this.HasError;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
internal void ClearIncludesList()
|
|
{
|
|
parsedlumps.Clear();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |