2009-04-19 18:07:22 +00:00
#region = = = = = = = = = = = = = = = = = = Copyright ( c ) 2007 Pascal vd Heiden
/ *
* Copyright ( c ) 2007 Pascal vd Heiden , www . codeimp . com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* /
#endregion
#region = = = = = = = = = = = = = = = = = = Namespaces
using System.Collections.Generic ;
using System.Globalization ;
2013-04-19 11:35:56 +00:00
using CodeImp.DoomBuilder.Config ;
2009-04-19 18:07:22 +00:00
#endregion
namespace CodeImp.DoomBuilder.ZDoom
{
public sealed class ActorStructure
{
#region = = = = = = = = = = = = = = = = = = Constants
private readonly string [ ] SPRITE_POSTFIXES = new string [ ] { "2C8" , "2D8" , "2A8" , "2B8" , "1C1" , "1D1" , "1A1" , "1B1" , "A2" , "A1" , "A0" , "2" , "1" , "0" } ;
#endregion
#region = = = = = = = = = = = = = = = = = = Variables
// Declaration
private string classname ;
private string inheritclass ;
private string replaceclass ;
private int doomednum = - 1 ;
2010-08-17 10:02:07 +00:00
// Inheriting
private ActorStructure baseclass ;
private bool skipsuper ;
2009-04-19 18:07:22 +00:00
// Flags
private Dictionary < string , bool > flags ;
// Properties
2009-08-15 08:41:43 +00:00
private Dictionary < string , List < string > > props ;
2013-03-18 13:52:27 +00:00
private List < string > userVars ; //mxd
2009-04-19 18:07:22 +00:00
// States
private Dictionary < string , StateStructure > states ;
#endregion
#region = = = = = = = = = = = = = = = = = = Properties
public Dictionary < string , bool > Flags { get { return flags ; } }
2009-08-15 08:41:43 +00:00
public Dictionary < string , List < string > > Properties { get { return props ; } }
2009-04-19 18:07:22 +00:00
public string ClassName { get { return classname ; } }
public string InheritsClass { get { return inheritclass ; } }
public string ReplacesClass { get { return replaceclass ; } }
2010-08-17 10:02:07 +00:00
public ActorStructure BaseClass { get { return baseclass ; } }
2009-04-19 18:07:22 +00:00
public int DoomEdNum { get { return doomednum ; } }
2013-03-18 13:52:27 +00:00
public List < string > UserVars { get { return userVars ; } } //mxd
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
internal ActorStructure ( DecorateParser parser )
{
// Initialize
flags = new Dictionary < string , bool > ( ) ;
2009-08-15 08:41:43 +00:00
props = new Dictionary < string , List < string > > ( ) ;
2009-04-19 18:07:22 +00:00
states = new Dictionary < string , StateStructure > ( ) ;
2013-03-18 13:52:27 +00:00
userVars = new List < string > ( ) ; //mxd
2010-08-17 10:02:07 +00:00
2009-08-15 08:41:43 +00:00
// Always define a game property, but default to 0 values
props [ "game" ] = new List < string > ( ) ;
2009-04-19 18:07:22 +00:00
inheritclass = "actor" ;
replaceclass = null ;
2010-08-17 10:02:07 +00:00
baseclass = null ;
skipsuper = false ;
2009-04-19 18:07:22 +00:00
// First next token is the class name
parser . SkipWhitespace ( true ) ;
classname = parser . StripTokenQuotes ( parser . ReadToken ( ) ) ;
if ( string . IsNullOrEmpty ( classname ) )
{
parser . ReportError ( "Expected actor class name" ) ;
return ;
}
// Parse tokens before entering the actor scope
while ( parser . SkipWhitespace ( true ) )
{
string token = parser . ReadToken ( ) ;
if ( ! string . IsNullOrEmpty ( token ) )
{
token = token . ToLowerInvariant ( ) ;
if ( token = = ":" )
{
2013-09-11 09:47:53 +00:00
// The next token must be the class to inherit from
2009-04-19 18:07:22 +00:00
parser . SkipWhitespace ( true ) ;
inheritclass = parser . StripTokenQuotes ( parser . ReadToken ( ) ) ;
if ( string . IsNullOrEmpty ( inheritclass ) | | parser . IsSpecialToken ( inheritclass ) )
{
parser . ReportError ( "Expected class name to inherit from" ) ;
return ;
}
else
{
// Find the actor to inherit from
2010-08-17 10:02:07 +00:00
baseclass = parser . GetArchivedActorByName ( inheritclass ) ;
2009-04-19 18:07:22 +00:00
}
}
else if ( token = = "replaces" )
{
// The next token must be the class to replace
parser . SkipWhitespace ( true ) ;
replaceclass = parser . StripTokenQuotes ( parser . ReadToken ( ) ) ;
if ( string . IsNullOrEmpty ( replaceclass ) | | parser . IsSpecialToken ( replaceclass ) )
{
parser . ReportError ( "Expected class name to replace" ) ;
return ;
}
}
else if ( token = = "native" )
{
// Igore this token
}
else if ( token = = "{" )
{
// Actor scope begins here,
// break out of this parse loop
break ;
}
2010-08-01 20:08:29 +00:00
else if ( token = = "-" )
{
// This could be a negative doomednum (but our parser sees the - as separate token)
// So read whatever is after this token and ignore it (negative doomednum indicates no doomednum)
parser . ReadToken ( ) ;
}
2009-04-19 18:07:22 +00:00
else
{
// Check if numeric
if ( ! int . TryParse ( token , NumberStyles . Integer , CultureInfo . InvariantCulture , out doomednum ) )
{
// Not numeric!
2013-03-18 13:52:27 +00:00
parser . ReportError ( "Expected numeric editor thing number or start of actor scope while parsing '" + classname + "'" ) ;
2009-04-19 18:07:22 +00:00
return ;
}
}
}
else
{
parser . ReportError ( "Unexpected end of structure" ) ;
return ;
}
}
// Now parse the contents of actor structure
string previoustoken = "" ;
while ( parser . SkipWhitespace ( true ) )
{
string token = parser . ReadToken ( ) ;
token = token . ToLowerInvariant ( ) ;
2009-08-15 08:41:43 +00:00
2009-04-19 18:07:22 +00:00
if ( ( token = = "+" ) | | ( token = = "-" ) )
{
// Next token is a flag (option) to set or remove
bool flagvalue = ( token = = "+" ) ;
parser . SkipWhitespace ( true ) ;
string flagname = parser . ReadToken ( ) ;
if ( ! string . IsNullOrEmpty ( flagname ) )
{
// Add the flag with its value
flagname = flagname . ToLowerInvariant ( ) ;
flags [ flagname ] = flagvalue ;
}
else
{
parser . ReportError ( "Expected flag name" ) ;
return ;
}
}
else if ( ( token = = "action" ) | | ( token = = "native" ) )
{
// We don't need this, ignore up to the first next ;
while ( parser . SkipWhitespace ( true ) )
{
string t = parser . ReadToken ( ) ;
if ( ( t = = ";" ) | | ( t = = null ) ) break ;
}
}
2010-08-17 10:02:07 +00:00
else if ( token = = "skip_super" )
{
skipsuper = true ;
}
2009-04-19 18:07:22 +00:00
else if ( token = = "states" )
{
// Now parse actor states until we reach the end of the states structure
while ( parser . SkipWhitespace ( true ) )
{
string statetoken = parser . ReadToken ( ) ;
if ( ! string . IsNullOrEmpty ( statetoken ) )
{
// Start of scope?
if ( statetoken = = "{" )
{
// This is fine
}
// End of scope?
else if ( statetoken = = "}" )
{
// Done with the states,
// break out of this parse loop
break ;
}
// State label?
else if ( statetoken = = ":" )
{
if ( ! string . IsNullOrEmpty ( previoustoken ) )
{
// Parse actor state
2010-08-17 10:02:07 +00:00
StateStructure st = new StateStructure ( this , parser , previoustoken ) ;
2009-04-19 18:07:22 +00:00
if ( parser . HasError ) return ;
states [ previoustoken . ToLowerInvariant ( ) ] = st ;
}
else
{
parser . ReportError ( "Unexpected end of structure" ) ;
return ;
}
}
else
{
// Keep token
previoustoken = statetoken ;
}
}
else
{
parser . ReportError ( "Unexpected end of structure" ) ;
return ;
}
}
2013-03-18 13:52:27 +00:00
}
else if ( token = = "var" ) //mxd
{
while ( parser . SkipWhitespace ( true ) ) {
string t = parser . ReadToken ( ) ;
if ( ( t = = ";" ) | | ( t = = null ) ) break ;
if ( t . StartsWith ( "user_" ) & & ! userVars . Contains ( t ) ) userVars . Add ( t ) ;
}
2009-04-19 18:07:22 +00:00
}
else if ( token = = "}" )
{
// Actor scope ends here,
// break out of this parse loop
break ;
}
// Monster property?
else if ( token = = "monster" )
{
// This sets certain flags we are interested in
2009-08-15 08:41:43 +00:00
flags [ "shootable" ] = true ;
flags [ "countkill" ] = true ;
2009-04-19 18:07:22 +00:00
flags [ "solid" ] = true ;
2009-08-15 08:41:43 +00:00
flags [ "canpushwalls" ] = true ;
flags [ "canusewalls" ] = true ;
flags [ "activatemcross" ] = true ;
flags [ "canpass" ] = true ;
flags [ "ismonster" ] = true ;
}
// Projectile property?
else if ( token = = "projectile" )
{
// This sets certain flags we are interested in
flags [ "noblockmap" ] = true ;
flags [ "nogravity" ] = true ;
flags [ "dropoff" ] = true ;
flags [ "missile" ] = true ;
flags [ "activateimpact" ] = true ;
flags [ "activatepcross" ] = true ;
flags [ "noteleport" ] = true ;
}
// Clearflags property?
else if ( token = = "clearflags" )
{
// Clear all flags
flags . Clear ( ) ;
2009-04-19 18:07:22 +00:00
}
// Game property?
else if ( token = = "game" )
{
// Include all tokens on the same line
2009-08-15 08:41:43 +00:00
List < string > games = new List < string > ( ) ;
2009-04-19 18:07:22 +00:00
while ( parser . SkipWhitespace ( false ) )
{
string v = parser . ReadToken ( ) ;
if ( v = = null )
{
parser . ReportError ( "Unexpected end of structure" ) ;
return ;
}
if ( v = = "\n" ) break ;
2013-09-11 09:47:53 +00:00
if ( v = = "}" ) return ; //mxd
2009-08-15 08:41:43 +00:00
if ( v ! = "," )
games . Add ( v . ToLowerInvariant ( ) ) ;
2009-04-19 18:07:22 +00:00
}
2009-08-15 08:41:43 +00:00
props [ token ] = games ;
2009-04-19 18:07:22 +00:00
}
2009-08-15 08:41:43 +00:00
// Property
else
2009-04-19 18:07:22 +00:00
{
2009-08-15 08:41:43 +00:00
// Property begins with $? Then the whole line is a single value
if ( token . StartsWith ( "$" ) )
{
// This is for editor-only properties such as $sprite and $category
List < string > values = new List < string > ( ) ;
if ( parser . SkipWhitespace ( false ) )
values . Add ( parser . ReadLine ( ) ) ;
else
values . Add ( "" ) ;
props [ token ] = values ;
}
else
{
// Next tokens up until the next newline are values
List < string > values = new List < string > ( ) ;
while ( parser . SkipWhitespace ( false ) )
{
string v = parser . ReadToken ( ) ;
if ( v = = null )
{
parser . ReportError ( "Unexpected end of structure" ) ;
return ;
}
if ( v = = "\n" ) break ;
2013-09-11 09:47:53 +00:00
if ( v = = "}" ) return ; //mxd
2009-08-15 08:41:43 +00:00
if ( v ! = "," )
values . Add ( v ) ;
}
props [ token ] = values ;
}
2009-04-19 18:07:22 +00:00
}
// Keep token
previoustoken = token ;
}
2013-04-19 11:35:56 +00:00
//mxd. Check if baseclass is valid
2013-04-25 09:43:24 +00:00
if ( inheritclass ! = "actor" & & doomednum > - 1 & & baseclass = = null ) {
2013-04-19 11:35:56 +00:00
//check if this class inherits from a class defined in game configuration
Dictionary < int , ThingTypeInfo > things = General . Map . Config . GetThingTypes ( ) ;
inheritclass = inheritclass . ToLowerInvariant ( ) ;
foreach ( KeyValuePair < int , ThingTypeInfo > ti in things ) {
if ( ti . Value . ClassName = = inheritclass ) {
//states
if ( states . Count = = 0 & & ! string . IsNullOrEmpty ( ti . Value . Sprite ) )
states . Add ( "spawn" , new StateStructure ( ti . Value . Sprite . Substring ( 0 , 4 ) ) ) ;
//flags
if ( ti . Value . Hangs & & ! flags . ContainsKey ( "spawnceiling" ) )
flags [ "spawnceiling" ] = true ;
if ( ti . Value . Blocking > 0 & & ! flags . ContainsKey ( "solid" ) )
flags [ "solid" ] = true ;
//properties
if ( ! props . ContainsKey ( "height" ) )
props [ "height" ] = new List < string > ( ) { ti . Value . Height . ToString ( ) } ;
if ( ! props . ContainsKey ( "radius" ) )
props [ "radius" ] = new List < string > ( ) { ti . Value . Radius . ToString ( ) } ;
return ;
}
}
General . ErrorLogger . Add ( ErrorType . Warning , "Unable to find the DECORATE class '" + inheritclass + "' to inherit from, while parsing '" + classname + ":" + doomednum + "'" ) ;
}
2009-04-19 18:07:22 +00:00
}
2010-08-19 15:06:15 +00:00
// Disposer
public void Dispose ( )
{
baseclass = null ;
flags = null ;
props = null ;
states = null ;
}
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Methods
2009-08-15 08:41:43 +00:00
/// <summary>
/// This checks if the actor has a specific property.
/// </summary>
public bool HasProperty ( string propname )
{
2010-08-17 10:02:07 +00:00
if ( props . ContainsKey ( propname ) )
return true ;
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . HasProperty ( propname ) ;
else
return false ;
2009-08-15 08:41:43 +00:00
}
/// <summary>
/// This checks if the actor has a specific property with at least one value.
/// </summary>
public bool HasPropertyWithValue ( string propname )
{
2010-08-17 10:02:07 +00:00
if ( props . ContainsKey ( propname ) & & ( props [ propname ] . Count > 0 ) )
return true ;
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . HasPropertyWithValue ( propname ) ;
else
return false ;
2009-08-15 08:41:43 +00:00
}
/// <summary>
/// This returns values of a specific property as a complete string. Returns an empty string when the propery has no values.
/// </summary>
public string GetPropertyAllValues ( string propname )
{
2010-08-17 10:02:07 +00:00
if ( props . ContainsKey ( propname ) & & ( props [ propname ] . Count > 0 ) )
return string . Join ( " " , props [ propname ] . ToArray ( ) ) ;
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . GetPropertyAllValues ( propname ) ;
else
return "" ;
2009-08-15 08:41:43 +00:00
}
2010-08-17 10:02:07 +00:00
2009-08-15 08:41:43 +00:00
/// <summary>
/// This returns a specific value of a specific property as a string. Returns an empty string when the propery does not have the specified value.
/// </summary>
public string GetPropertyValueString ( string propname , int valueindex )
{
2010-08-17 10:02:07 +00:00
if ( props . ContainsKey ( propname ) & & ( props [ propname ] . Count > valueindex ) )
2009-08-15 08:41:43 +00:00
return props [ propname ] [ valueindex ] ;
2010-08-17 10:02:07 +00:00
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . GetPropertyValueString ( propname , valueindex ) ;
2009-08-15 08:41:43 +00:00
else
return "" ;
}
/// <summary>
/// This returns a specific value of a specific property as an integer. Returns 0 when the propery does not have the specified value.
/// </summary>
public int GetPropertyValueInt ( string propname , int valueindex )
{
2010-08-17 10:02:07 +00:00
string str = GetPropertyValueString ( propname , valueindex ) ;
int intvalue ;
if ( int . TryParse ( str , NumberStyles . Integer , CultureInfo . InvariantCulture , out intvalue ) )
return intvalue ;
2009-08-15 08:41:43 +00:00
else
return 0 ;
}
2010-08-17 10:02:07 +00:00
2009-08-15 08:41:43 +00:00
/// <summary>
/// This returns a specific value of a specific property as a float. Returns 0.0f when the propery does not have the specified value.
/// </summary>
public float GetPropertyValueFloat ( string propname , int valueindex )
{
2010-08-17 10:02:07 +00:00
string str = GetPropertyValueString ( propname , valueindex ) ;
float fvalue ;
if ( float . TryParse ( str , NumberStyles . Float , CultureInfo . InvariantCulture , out fvalue ) )
return fvalue ;
2009-08-15 08:41:43 +00:00
else
return 0.0f ;
}
2010-08-17 10:02:07 +00:00
2009-08-15 08:41:43 +00:00
/// <summary>
/// This returns the status of a flag.
/// </summary>
2009-04-19 18:07:22 +00:00
public bool HasFlagValue ( string flag )
{
2010-08-17 10:02:07 +00:00
if ( flags . ContainsKey ( flag ) )
return true ;
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . HasFlagValue ( flag ) ;
else
return false ;
2009-04-19 18:07:22 +00:00
}
2009-08-15 08:41:43 +00:00
/// <summary>
/// This returns the status of a flag.
/// </summary>
2009-04-19 18:07:22 +00:00
public bool GetFlagValue ( string flag , bool defaultvalue )
{
if ( flags . ContainsKey ( flag ) )
return flags [ flag ] ;
2010-08-17 10:02:07 +00:00
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . GetFlagValue ( flag , defaultvalue ) ;
2009-04-19 18:07:22 +00:00
else
return defaultvalue ;
}
2010-08-17 10:02:07 +00:00
/// <summary>
/// This checks if a state has been defined.
/// </summary>
public bool HasState ( string statename )
{
if ( states . ContainsKey ( statename ) )
return true ;
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . HasState ( statename ) ;
else
return false ;
}
/// <summary>
/// This returns a specific state, or null when the state can't be found.
/// </summary>
2010-08-22 11:36:09 +00:00
public StateStructure GetState ( string statename )
2010-08-17 10:02:07 +00:00
{
if ( states . ContainsKey ( statename ) )
return states [ statename ] ;
else if ( ! skipsuper & & ( baseclass ! = null ) )
return baseclass . GetState ( statename ) ;
else
return null ;
}
/// <summary>
/// This creates a list of all states, also those inherited from the base class.
/// </summary>
2010-08-22 11:36:09 +00:00
public Dictionary < string , StateStructure > GetAllStates ( )
2010-08-17 10:02:07 +00:00
{
Dictionary < string , StateStructure > list = new Dictionary < string , StateStructure > ( states ) ;
if ( ! skipsuper & & ( baseclass ! = null ) )
{
Dictionary < string , StateStructure > baselist = baseclass . GetAllStates ( ) ;
foreach ( KeyValuePair < string , StateStructure > s in baselist )
if ( ! list . ContainsKey ( s . Key ) ) list . Add ( s . Key , s . Value ) ;
}
return list ;
}
2009-08-15 08:41:43 +00:00
/// <summary>
/// This checks if this actor is meant for the current decorate game support
/// </summary>
2009-04-19 18:07:22 +00:00
public bool CheckActorSupported ( )
{
// Check if we want to include this actor
string includegames = General . Map . Config . DecorateGames . ToLowerInvariant ( ) ;
2009-08-15 08:41:43 +00:00
bool includeactor = ( props [ "game" ] . Count = = 0 ) ;
foreach ( string g in props [ "game" ] )
2009-04-19 18:07:22 +00:00
includeactor | = includegames . Contains ( g ) ;
return includeactor ;
}
2009-08-15 08:41:43 +00:00
/// <summary>
/// This finds the best suitable sprite to use when presenting this actor to the user.
/// </summary>
2009-04-19 18:07:22 +00:00
public string FindSuitableSprite ( )
{
string result = "" ;
// Sprite forced?
2010-08-17 10:02:07 +00:00
if ( HasPropertyWithValue ( "$sprite" ) )
2009-04-19 18:07:22 +00:00
{
2010-08-17 10:02:07 +00:00
return GetPropertyValueString ( "$sprite" , 0 ) ;
2009-04-19 18:07:22 +00:00
}
else
{
// Try the idle state
2010-08-17 10:02:07 +00:00
if ( HasState ( "idle" ) )
2009-04-19 18:07:22 +00:00
{
2010-08-17 10:02:07 +00:00
StateStructure s = GetState ( "idle" ) ;
2010-08-19 15:06:15 +00:00
string spritename = s . GetSprite ( 0 ) ;
if ( ! string . IsNullOrEmpty ( spritename ) )
result = spritename ;
2009-04-19 18:07:22 +00:00
}
// Try the see state
2010-08-17 10:02:07 +00:00
if ( string . IsNullOrEmpty ( result ) & & HasState ( "see" ) )
2009-04-19 18:07:22 +00:00
{
2010-08-17 10:02:07 +00:00
StateStructure s = GetState ( "see" ) ;
2010-08-19 15:06:15 +00:00
string spritename = s . GetSprite ( 0 ) ;
if ( ! string . IsNullOrEmpty ( spritename ) )
result = spritename ;
2009-04-19 18:07:22 +00:00
}
2009-08-15 08:41:43 +00:00
2009-04-19 18:07:22 +00:00
// Try the inactive state
2010-08-17 10:02:07 +00:00
if ( string . IsNullOrEmpty ( result ) & & HasState ( "inactive" ) )
2009-04-19 18:07:22 +00:00
{
2010-08-17 10:02:07 +00:00
StateStructure s = GetState ( "inactive" ) ;
2010-08-19 15:06:15 +00:00
string spritename = s . GetSprite ( 0 ) ;
if ( ! string . IsNullOrEmpty ( spritename ) )
result = spritename ;
2009-04-19 18:07:22 +00:00
}
2010-08-17 10:02:07 +00:00
2010-08-12 05:49:22 +00:00
// Try the spawn state
2010-08-17 10:02:07 +00:00
if ( string . IsNullOrEmpty ( result ) & & HasState ( "spawn" ) )
2010-08-12 05:49:22 +00:00
{
2010-08-17 10:02:07 +00:00
StateStructure s = GetState ( "spawn" ) ;
2010-08-19 15:06:15 +00:00
string spritename = s . GetSprite ( 0 ) ;
if ( ! string . IsNullOrEmpty ( spritename ) )
result = spritename ;
2010-08-12 05:49:22 +00:00
}
2010-08-17 10:02:07 +00:00
2009-04-19 18:07:22 +00:00
// Still no sprite found? then just pick the first we can find
if ( string . IsNullOrEmpty ( result ) )
{
2010-08-17 10:02:07 +00:00
Dictionary < string , StateStructure > list = GetAllStates ( ) ;
foreach ( StateStructure s in list . Values )
2009-04-19 18:07:22 +00:00
{
2010-08-19 15:06:15 +00:00
string spritename = s . GetSprite ( 0 ) ;
if ( ! string . IsNullOrEmpty ( spritename ) )
2009-04-19 18:07:22 +00:00
{
2010-08-19 15:06:15 +00:00
result = spritename ;
2009-04-19 18:07:22 +00:00
break ;
}
}
}
if ( ! string . IsNullOrEmpty ( result ) )
{
// The sprite name is not actually complete, we still have to append
// the direction characters to it. Find an existing sprite with direction.
foreach ( string postfix in SPRITE_POSTFIXES )
{
if ( General . Map . Data . GetSpriteExists ( result + postfix ) )
return result + postfix ;
}
}
}
// No sprite found
return "" ;
}
#endregion
}
}