2012-05-22 22:56:42 +00:00
using System ;
using System.IO ;
using System.Collections.Generic ;
using System.Globalization ;
using System.Text ;
using CodeImp.DoomBuilder.ZDoom ;
2012-06-01 10:17:47 +00:00
using CodeImp.DoomBuilder.GZBuilder.Data ;
2012-05-22 22:56:42 +00:00
using SlimDX ;
using SlimDX.Direct3D9 ;
namespace CodeImp.DoomBuilder.GZBuilder.GZDoom {
public class GldefsParser : ZDTextParser {
2012-06-01 10:17:47 +00:00
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
}
2012-05-22 22:56:42 +00:00
public override bool Parse ( Stream stream , string sourcefilename ) {
base . Parse ( stream , sourcefilename ) ;
2012-06-01 10:17:47 +00:00
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 ;
2012-05-22 22:56:42 +00:00
// Continue until at the end of the stream
while ( SkipWhitespace ( true ) ) {
string token = ReadToken ( ) ;
if ( ! string . IsNullOrEmpty ( token ) ) {
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( token . ToLowerInvariant ( ) ) ; //Quotes can be anywhere! ANYWHERE!!! And GZDoom will still parse data correctly
2012-05-22 22:56:42 +00:00
//got light structure
if ( token = = GldefsLightType . POINT | | token = = GldefsLightType . PULSE | | token = = GldefsLightType . FLICKER | | token = = GldefsLightType . FLICKER2 | | token = = GldefsLightType . SECTOR ) {
bool gotErrors = false ;
2012-06-01 10:17:47 +00:00
string lightType = token ;
GZDoomLight light = new GZDoomLight ( ) ;
light . Type = GldefsLightType . GLDEFS_TO_GZDOOM_LIGHT_TYPE [ lightType ] ;
2012-05-22 22:56:42 +00:00
//find classname
SkipWhitespace ( true ) ;
2012-06-01 10:17:47 +00:00
string lightName = StripTokenQuotes ( ReadToken ( ) ) . ToLowerInvariant ( ) ;
2012-05-22 22:56:42 +00:00
if ( ! string . IsNullOrEmpty ( lightName ) ) {
//now find opening brace
SkipWhitespace ( true ) ;
token = ReadToken ( ) ;
if ( token ! = "{" ) {
2012-06-01 10:17:47 +00:00
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": expected '{', but got " + token ) ;
continue ; //something wrong with gldefs declaration, continue to next one
2012-05-22 22:56:42 +00:00
}
//read gldefs light structure
while ( SkipWhitespace ( true ) ) {
token = ReadToken ( ) ;
if ( ! string . IsNullOrEmpty ( token ) ) {
token = token . ToLowerInvariant ( ) ;
2012-06-01 10:17:47 +00:00
//color
2012-05-22 22:56:42 +00:00
if ( token = = "color" ) {
SkipWhitespace ( true ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
2012-05-22 22:56:42 +00:00
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 ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
2012-05-22 22:56:42 +00:00
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 ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
2012-05-22 22:56:42 +00:00
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 ;
}
2012-06-01 10:17:47 +00:00
//size
2012-05-22 22:56:42 +00:00
} else if ( token = = "size" ) {
2012-06-01 10:17:47 +00:00
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 ;
2012-05-22 22:56:42 +00:00
2012-06-01 10:17:47 +00:00
} else {
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": '" + token + "' is not valid property for " + lightType ) ;
2012-05-22 22:56:42 +00:00
gotErrors = true ;
break ;
}
2012-06-01 10:17:47 +00:00
//offset
2012-05-22 22:56:42 +00:00
} else if ( token = = "offset" ) {
SkipWhitespace ( true ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
if ( ! ReadSignedFloat ( token , ref light . Offset . X ) ) {
2012-05-22 22:56:42 +00:00
// Not numeric!
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": expected Offset X value, but got '" + token + "'" ) ;
gotErrors = true ;
break ;
}
SkipWhitespace ( true ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
if ( ! ReadSignedFloat ( token , ref light . Offset . Z ) ) {
2012-05-22 22:56:42 +00:00
// Not numeric!
2012-06-01 19:53:14 +00:00
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": expected Offset Y value, but got '" + token + "'" ) ;
2012-05-22 22:56:42 +00:00
gotErrors = true ;
break ;
}
SkipWhitespace ( true ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
if ( ! ReadSignedFloat ( token , ref light . Offset . Y ) ) {
2012-05-22 22:56:42 +00:00
// Not numeric!
2012-06-01 19:53:14 +00:00
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": expected Offset Z value, but got '" + token + "'" ) ;
2012-05-22 22:56:42 +00:00
gotErrors = true ;
break ;
}
2012-06-01 10:17:47 +00:00
//subtractive
2012-05-22 22:56:42 +00:00
} else if ( token = = "subtractive" ) {
SkipWhitespace ( true ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
2012-05-22 22:56:42 +00:00
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 ;
2012-06-01 10:17:47 +00:00
//dontlightself
2012-05-22 22:56:42 +00:00
} else if ( token = = "dontlightself" ) {
SkipWhitespace ( true ) ;
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( ReadToken ( ) ) ;
2012-05-22 22:56:42 +00:00
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 ;
}
2012-06-01 10:17:47 +00:00
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 ;
}
2012-05-22 22:56:42 +00:00
2012-06-01 10:17:47 +00:00
//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 ) ;
2012-05-22 22:56:42 +00:00
gotErrors = true ;
break ;
}
2012-06-01 10:17:47 +00:00
//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 ;
2012-05-22 22:56:42 +00:00
2012-06-01 10:17:47 +00:00
} else {
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": '" + token + "' is not valid property for " + lightType ) ;
2012-05-22 22:56:42 +00:00
gotErrors = true ;
break ;
}
2012-06-01 10:17:47 +00:00
//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 ;
}
2012-05-22 22:56:42 +00:00
2012-06-01 10:17:47 +00:00
//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 ) ;
2012-05-22 22:56:42 +00:00
gotErrors = true ;
break ;
}
2012-06-01 10:17:47 +00:00
//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 ;
}
2012-05-22 22:56:42 +00:00
2012-06-01 19:53:14 +00:00
if ( scale > 1.0f ) {
2012-06-01 10:17:47 +00:00
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": scale must be in 0.0 - 1.0 range, but is " + scale ) ;
gotErrors = true ;
break ;
}
2012-05-22 22:56:42 +00:00
2012-06-01 10:17:47 +00:00
//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 ) ;
2012-05-22 22:56:42 +00:00
gotErrors = true ;
break ;
}
2012-06-01 10:17:47 +00:00
2012-05-22 22:56:42 +00:00
//end of structure
} else if ( token = = "}" ) {
if ( ! gotErrors ) {
2012-06-01 10:17:47 +00:00
//general checks
2012-05-22 22:56:42 +00:00
if ( light . Color . Red = = 0.0f & & light . Color . Green = = 0.0f & & light . Color . Blue = = 0.0f ) {
2012-06-01 10:17:47 +00:00
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 ;
2012-05-22 22:56:42 +00:00
}
2012-06-01 10:17:47 +00:00
//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 ;
}
2012-05-22 22:56:42 +00:00
}
2012-06-01 10:17:47 +00:00
//offset it slightly to avoid shading glitches
2012-06-01 19:53:14 +00:00
if ( light . Offset . Z = = 0.0f )
light . Offset . Z = 0.1f ;
2012-06-01 10:17:47 +00:00
if ( ! gotErrors ) {
if ( lightsByName . ContainsKey ( lightName ) ) {
lightsByName [ lightName ] = light ;
} else {
lightsByName . Add ( lightName , light ) ;
}
}
2012-05-22 22:56:42 +00:00
}
break ; //break out of this parsing loop
}
}
}
}
} else if ( token = = "object" ) {
SkipWhitespace ( true ) ;
//read object class
2012-06-01 10:17:47 +00:00
string objectClass = StripTokenQuotes ( ReadToken ( ) ) . ToLowerInvariant ( ) ;
2012-05-22 22:56:42 +00:00
if ( ! string . IsNullOrEmpty ( objectClass ) ) {
//now find opening brace
SkipWhitespace ( true ) ;
token = ReadToken ( ) ;
2012-06-01 10:17:47 +00:00
2012-05-22 22:56:42 +00:00
if ( token ! = "{" ) {
2012-06-01 10:17:47 +00:00
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": expected '{', but got " + token ) ;
2012-05-22 22:56:42 +00:00
continue ;
}
2012-06-01 10:17:47 +00:00
int bracesCount = 1 ;
bool foundLight = false ;
2012-05-22 22:56:42 +00:00
//read frames structure
while ( SkipWhitespace ( true ) ) {
token = ReadToken ( ) ;
if ( ! string . IsNullOrEmpty ( token ) ) {
2012-06-01 10:17:47 +00:00
token = StripTokenQuotes ( token ) . ToLowerInvariant ( ) ;
2012-05-22 22:56:42 +00:00
2012-06-01 10:17:47 +00:00
if ( ! foundLight & & token = = "light" ) { //just use first light from first frame and be done with it
2012-05-22 22:56:42 +00:00
SkipWhitespace ( true ) ;
token = ReadToken ( ) . ToLowerInvariant ( ) ; //should be light name
if ( ! string . IsNullOrEmpty ( token ) ) {
if ( lightsByName . ContainsKey ( token ) ) {
2012-06-01 10:17:47 +00:00
if ( objects . ContainsKey ( objectClass ) )
objects [ objectClass ] = token ;
else
objects . Add ( objectClass , token ) ;
foundLight = true ;
2012-05-22 22:56:42 +00:00
} else {
2012-06-01 10:17:47 +00:00
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": light declaration not found for light '" + token + "'" ) ;
2012-05-22 22:56:42 +00:00
}
}
2012-06-01 10:17:47 +00:00
} 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.
2012-05-22 22:56:42 +00:00
}
}
}
}
} else if ( token = = "#include" ) {
SkipWhitespace ( true ) ;
string includeLump = StripTokenQuotes ( ReadToken ( ) ) . ToLowerInvariant ( ) ;
2012-06-01 10:17:47 +00:00
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 ;
2012-05-22 22:56:42 +00:00
} else {
2012-06-01 10:17:47 +00:00
GZBuilder . GZGeneral . LogAndTraceWarning ( "Error in '" + sourcefilename + "' at line " + GetCurrentLineNumber ( ) + ": got #include directive with missing or incorrect path: '" + includeLump + "'" ) ;
2012-05-22 22:56:42 +00:00
}
} 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 ) ;
}
}
}
2012-06-01 10:17:47 +00:00
if ( objects . Count > 0 )
2012-05-22 22:56:42 +00:00
return true ;
return false ;
}
}
2012-06-01 10:17:47 +00:00
}