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
2014-02-26 14:11:06 +00:00
using System ;
2009-04-19 18:07:22 +00:00
using System.Collections.Generic ;
using System.Globalization ;
2013-04-19 11:35:56 +00:00
using CodeImp.DoomBuilder.Config ;
Fixed, Visual mode: in some cases ceiling glow effect was interfering with Transfer Brightness effect resulting in incorrectly lit sidedef geometry.
Fixed, Visual mode: UDMF sidedef brightness should be ignored when a wall section is affected by Transfer Brightness effect.
Fixed, Visual mode: any custom fog should be rendered regardless of sector brightness.
Fixed, Visual mode: "fogdensity" and "outsidefogdensity" MAPINFO values were processed incorrectly.
Fixed, Visual mode: in some cases Things were rendered twice during a render pass.
Fixed, Visual mode: floor glow effect should affect thing brightness only when applied to floor of the sector thing is in.
Fixed, TEXTURES parser: TEXTURES group was named incorrectly in the Textures Browser window when parsed from a WAD file.
Fixed, MAPINFO, GLDEFS, DECORATE parsers: "//$GZDB_SKIP" special comment was processed incorrectly.
Fixed, MAPINFO parser: "fogdensity" and "outsidefogdensity" properties are now initialized using GZDoom default value (255) instead of 0.
2016-01-25 13:42:53 +00:00
using CodeImp.DoomBuilder.Data ;
2016-02-08 21:51:03 +00:00
using CodeImp.DoomBuilder.Types ;
2023-09-15 19:28:20 +00:00
using CodeImp.DoomBuilder.Map ;
2024-01-31 21:33:47 +00:00
using System.Linq ;
2009-04-19 18:07:22 +00:00
#endregion
namespace CodeImp.DoomBuilder.ZDoom
{
2017-01-15 23:35:27 +00:00
public class ActorStructure
2009-04-19 18:07:22 +00:00
{
#region = = = = = = = = = = = = = = = = = = Constants
2016-07-13 21:53:48 +00:00
private readonly string [ ] SPRITE_CHECK_STATES = { "idle" , "see" , "inactive" , "spawn" } ; //mxd
2014-01-03 10:33:45 +00:00
internal const string ACTOR_CLASS_SPECIAL_TOKENS = ":{}\n;," ; //mxd
2017-01-15 23:35:27 +00:00
#endregion
2016-05-13 13:41:09 +00:00
2017-01-15 23:35:27 +00:00
#region = = = = = = = = = = = = = = = = = = Variables
// Declaration
internal string classname ;
internal string inheritclass ;
internal string replaceclass ;
internal int doomednum = - 1 ;
// Inheriting
internal ActorStructure baseclass ;
internal bool skipsuper ;
// Flags
internal Dictionary < string , bool > flags ;
// Properties
internal Dictionary < string , List < string > > props ;
internal Dictionary < string , UniversalType > uservars ; //mxd
2018-07-24 07:27:29 +00:00
internal Dictionary < string , object > uservar_defaults ; // [ZZ] should correspond to UniversalType
2017-01-15 23:35:27 +00:00
//mxd. Categories
internal DecorateCategoryInfo catinfo ;
2017-03-01 22:21:08 +00:00
// [ZZ] direct ArgumentInfos (from game configuration), or own ArgumentInfos (from props)
2023-09-15 19:28:20 +00:00
internal ArgumentInfo [ ] args = new ArgumentInfo [ Thing . NUM_ARGS ] ;
2023-09-19 17:06:32 +00:00
internal ArgumentInfo [ ] stringargs = new ArgumentInfo [ Thing . NUM_STRING_ARGS ] ;
2017-03-01 22:21:08 +00:00
2023-09-19 17:06:32 +00:00
// States
internal Dictionary < string , StateStructure > states ;
2009-04-19 18:07:22 +00:00
#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 ; } }
2015-04-14 11:33:57 +00:00
internal int DoomEdNum { get { return doomednum ; } set { doomednum = value ; } }
2016-02-08 21:51:03 +00:00
public Dictionary < string , UniversalType > UserVars { get { return uservars ; } } //mxd
2018-07-24 07:27:29 +00:00
public Dictionary < string , object > UserVarDefaults { get { return uservar_defaults ; } } // [ZZ]
2016-05-13 13:41:09 +00:00
internal DecorateCategoryInfo CategoryInfo { get { return catinfo ; } } //mxd
2009-04-19 18:07:22 +00:00
#endregion
#region = = = = = = = = = = = = = = = = = = Constructor / Disposer
// Constructor
2017-01-17 09:40:58 +00:00
internal ActorStructure ( )
2009-04-19 18:07:22 +00:00
{
// Initialize
2016-02-08 21:51:03 +00:00
flags = new Dictionary < string , bool > ( StringComparer . OrdinalIgnoreCase ) ;
props = new Dictionary < string , List < string > > ( StringComparer . OrdinalIgnoreCase ) ;
states = new Dictionary < string , StateStructure > ( StringComparer . OrdinalIgnoreCase ) ;
uservars = new Dictionary < string , UniversalType > ( StringComparer . OrdinalIgnoreCase ) ; //mxd
2018-07-24 07:27:29 +00:00
uservar_defaults = new Dictionary < string , object > ( StringComparer . OrdinalIgnoreCase ) ; // [ZZ]
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
}
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 )
{
2015-12-17 10:07:28 +00:00
if ( props . ContainsKey ( propname ) ) return true ;
if ( ! skipsuper & & ( baseclass ! = null ) ) return baseclass . HasProperty ( propname ) ;
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 )
{
2015-12-17 10:07:28 +00:00
if ( props . ContainsKey ( propname ) & & ( props [ propname ] . Count > 0 ) ) return true ;
if ( ! skipsuper & & ( baseclass ! = null ) ) return baseclass . HasPropertyWithValue ( propname ) ;
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 ( ) ) ;
2015-12-17 10:07:28 +00:00
if ( ! skipsuper & & ( baseclass ! = null ) )
2010-08-17 10:02:07 +00:00
return baseclass . GetPropertyAllValues ( propname ) ;
2015-12-17 10:07:28 +00:00
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>
2016-02-26 22:06:42 +00:00
public string GetPropertyValueString ( string propname , int valueindex ) { return GetPropertyValueString ( propname , valueindex , true ) ; } //mxd. Added "stripquotes" parameter
public string GetPropertyValueString ( string propname , int valueindex , bool stripquotes )
2009-08-15 08:41:43 +00:00
{
2010-08-17 10:02:07 +00:00
if ( props . ContainsKey ( propname ) & & ( props [ propname ] . Count > valueindex ) )
2016-02-26 22:06:42 +00:00
return ( stripquotes ? ZDTextParser . StripQuotes ( props [ propname ] [ valueindex ] ) : props [ propname ] [ valueindex ] ) ;
2015-12-17 10:07:28 +00:00
if ( ! skipsuper & & ( baseclass ! = null ) )
2016-02-26 22:06:42 +00:00
return baseclass . GetPropertyValueString ( propname , valueindex , stripquotes ) ;
2015-12-17 10:07:28 +00:00
return "" ;
2009-08-15 08:41:43 +00:00
}
/// <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 )
{
2016-02-26 22:06:42 +00:00
string str = GetPropertyValueString ( propname , valueindex , false ) ;
2015-01-27 13:27:49 +00:00
//mxd. It can be negative...
2016-02-26 22:06:42 +00:00
if ( str = = "-" & & props . Count > valueindex + 1 )
str + = GetPropertyValueString ( propname , valueindex + 1 , false ) ;
2010-08-17 10:02:07 +00:00
int intvalue ;
if ( int . TryParse ( str , NumberStyles . Integer , CultureInfo . InvariantCulture , out intvalue ) )
return intvalue ;
2013-12-23 09:51:52 +00:00
return 0 ;
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 float. Returns 0.0f when the propery does not have the specified value.
/// </summary>
public float GetPropertyValueFloat ( string propname , int valueindex )
{
2016-02-26 22:06:42 +00:00
string str = GetPropertyValueString ( propname , valueindex , false ) ;
2015-01-27 13:27:49 +00:00
//mxd. It can be negative...
if ( str = = "-" & & props . Count > valueindex + 1 )
2016-02-26 22:06:42 +00:00
str + = GetPropertyValueString ( propname , valueindex + 1 , false ) ;
2015-01-27 13:27:49 +00:00
2010-08-17 10:02:07 +00:00
float fvalue ;
if ( float . TryParse ( str , NumberStyles . Float , CultureInfo . InvariantCulture , out fvalue ) )
return fvalue ;
2013-12-23 09:51:52 +00:00
return 0.0f ;
2009-08-15 08:41:43 +00:00
}
2021-11-28 23:35:57 +00:00
/// <summary>
/// Gets all user vars from this actor and all actors in the inheritance chain.
/// </summary>
/// <returns>Dictionary of all user vars</returns>
public Dictionary < string , UniversalType > GetAllUserVars ( )
{
Dictionary < string , UniversalType > result = new Dictionary < string , UniversalType > ( uservars ) ;
ActorStructure parent = baseclass ;
while ( parent ! = null )
{
foreach ( string key in parent . UserVars . Keys )
if ( ! result . ContainsKey ( key ) )
result [ key ] = parent . UserVars [ key ] ;
parent = parent . baseclass ;
}
return result ;
}
/// <summary>
/// Gets all user var defauls from this actor and all actors in the inheritance chain.
/// </summary>
/// <returns>Dictionary of all user var defaults</returns>
public Dictionary < string , object > GetAllUserVarDefaults ( )
{
Dictionary < string , object > result = new Dictionary < string , object > ( uservar_defaults ) ;
ActorStructure parent = baseclass ;
while ( parent ! = null )
{
foreach ( string key in parent . UserVarDefaults . Keys )
if ( ! result . ContainsKey ( key ) )
result [ key ] = parent . UserVarDefaults [ key ] ;
parent = parent . baseclass ;
}
return result ;
}
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 )
{
2015-12-17 10:07:28 +00:00
if ( flags . ContainsKey ( flag ) ) return true ;
if ( ! skipsuper & & ( baseclass ! = null ) ) return baseclass . HasFlagValue ( flag ) ;
2013-12-23 09:51:52 +00:00
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 )
{
2015-12-17 10:07:28 +00:00
if ( flags . ContainsKey ( flag ) ) return flags [ flag ] ;
if ( ! skipsuper & & ( baseclass ! = null ) ) return baseclass . GetFlagValue ( flag , defaultvalue ) ;
2013-12-23 09:51:52 +00:00
return defaultvalue ;
2009-04-19 18:07:22 +00:00
}
2010-08-17 10:02:07 +00:00
/// <summary>
/// This checks if a state has been defined.
/// </summary>
public bool HasState ( string statename )
{
2017-03-04 00:25:10 +00:00
// [ZZ] we should NOT pick up any states from Actor. (except Spawn, as the very default state)
// for the greater good. (otherwise POL5 from GenericCrush is displayed for actors without frames, preferred before TNT1)
if ( classname . ToLowerInvariant ( ) = = "actor" & & statename . ToLowerInvariant ( ) ! = "spawn" )
return false ;
2015-12-17 10:07:28 +00:00
if ( states . ContainsKey ( statename ) ) return true ;
if ( ! skipsuper & & ( baseclass ! = null ) ) return baseclass . HasState ( statename ) ;
2013-12-23 09:51:52 +00:00
return false ;
2010-08-17 10:02:07 +00:00
}
/// <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
{
2017-03-04 00:25:10 +00:00
// [ZZ] we should NOT pick up any states from Actor. (except Spawn, as the very default state)
// for the greater good. (otherwise POL5 from GenericCrush is displayed for actors without frames, preferred before TNT1)
if ( classname . ToLowerInvariant ( ) = = "actor" & & statename . ToLowerInvariant ( ) ! = "spawn" )
return null ;
if ( states . ContainsKey ( statename ) ) return states [ statename ] ;
2015-12-17 10:07:28 +00:00
if ( ! skipsuper & & ( baseclass ! = null ) ) return baseclass . GetState ( statename ) ;
2013-12-23 09:51:52 +00:00
return null ;
2010-08-17 10:02:07 +00:00
}
/// <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
{
2017-03-04 00:25:10 +00:00
// [ZZ] we should NOT pick up any states from Actor. (except Spawn, as the very default state)
// for the greater good. (otherwise POL5 from GenericCrush is displayed for actors without frames, preferred before TNT1)
if ( classname . ToLowerInvariant ( ) = = "actor" )
{
Dictionary < string , StateStructure > list2 = new Dictionary < string , StateStructure > ( StringComparer . OrdinalIgnoreCase ) ;
if ( states . ContainsKey ( "spawn" ) )
list2 . Add ( "spawn" , states [ "spawn" ] ) ;
return list2 ;
}
Dictionary < string , StateStructure > list = new Dictionary < string , StateStructure > ( states , StringComparer . OrdinalIgnoreCase ) ;
if ( ! skipsuper & & ( baseclass ! = null ) )
2010-08-17 10:02:07 +00:00
{
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 ( )
{
2022-10-20 18:10:30 +00:00
// Only check if a map is opened. Otherwise we run into problems with the resource checker
if ( General . Map ! = null )
{
// Check if we want to include this actor
string includegames = General . Map . Config . DecorateGames . ToLowerInvariant ( ) ;
bool includeactor = ( props [ "game" ] . Count = = 0 ) ;
foreach ( string g in props [ "game" ] )
includeactor | = includegames . Contains ( g ) ;
return includeactor ;
}
else
{
return true ;
}
2009-04-19 18:07:22 +00:00
}
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>
2016-04-20 20:06:16 +00:00
public StateStructure . FrameInfo FindSuitableSprite ( )
2009-04-19 18:07:22 +00:00
{
2016-07-13 21:53:48 +00:00
// Info: actual sprites are resolved in ThingTypeInfo.SetupSpriteFrame() - mxd
2009-04-19 18:07:22 +00:00
// Sprite forced?
2010-08-17 10:02:07 +00:00
if ( HasPropertyWithValue ( "$sprite" ) )
2009-04-19 18:07:22 +00:00
{
2016-02-26 22:06:42 +00:00
string sprite = GetPropertyValueString ( "$sprite" , 0 , true ) ; //mxd
2016-04-20 20:06:16 +00:00
//mxd. Valid when internal or exists
if ( sprite . StartsWith ( DataManager . INTERNAL_PREFIX , StringComparison . OrdinalIgnoreCase ) | | General . Map . Data . GetSpriteExists ( sprite ) )
2016-07-13 21:53:48 +00:00
return new StateStructure . FrameInfo { Sprite = sprite } ;
2016-01-17 23:50:07 +00:00
//mxd. Bitch and moan
General . ErrorLogger . Add ( ErrorType . Warning , "DECORATE warning in " + classname + ":" + doomednum + ". The sprite \"" + sprite + "\" assigned by the \"$sprite\" property does not exist." ) ;
2009-04-19 18:07:22 +00:00
}
2016-01-17 23:50:07 +00:00
2017-03-04 00:13:39 +00:00
StateStructure . FrameInfo firstNonTntInfo = null ;
StateStructure . FrameInfo firstInfo = null ;
// Pick the first we can find (including and not including TNT1)
Dictionary < string , StateStructure > list = GetAllStates ( ) ;
foreach ( StateStructure s in list . Values )
{
StateStructure . FrameInfo info = s . GetSprite ( 0 ) ;
if ( string . IsNullOrEmpty ( info . Sprite ) ) continue ;
if ( ! info . IsEmpty ( ) ) firstNonTntInfo = info ;
if ( firstInfo = = null ) firstInfo = info ;
if ( firstNonTntInfo ! = null )
break ;
}
//mxd. Try to get a suitable sprite from our hardcoded states list
StateStructure . FrameInfo lastNonTntInfo = null ;
StateStructure . FrameInfo lastInfo = null ;
foreach ( string state in SPRITE_CHECK_STATES )
2016-01-17 23:50:07 +00:00
{
2016-07-13 21:53:48 +00:00
if ( ! HasState ( state ) ) continue ;
StateStructure s = GetState ( state ) ;
2016-04-20 20:06:16 +00:00
StateStructure . FrameInfo info = s . GetSprite ( 0 ) ;
2017-03-04 00:13:39 +00:00
//if(!string.IsNullOrEmpty(info.Sprite)) return info;
if ( string . IsNullOrEmpty ( info . Sprite ) ) continue ;
if ( ! info . IsEmpty ( ) ) lastNonTntInfo = info ;
if ( lastInfo = = null ) lastInfo = info ;
if ( lastNonTntInfo ! = null )
break ;
2016-01-17 23:50:07 +00:00
}
2017-03-04 00:13:39 +00:00
// [ZZ] return whatever is there by priority. try to pick non-TNT1 frames.
2017-03-04 14:50:46 +00:00
StateStructure . FrameInfo [ ] infos = new StateStructure . FrameInfo [ ] { lastNonTntInfo , lastInfo , firstNonTntInfo , firstInfo } ;
2017-03-04 00:13:39 +00:00
foreach ( StateStructure . FrameInfo info in infos )
if ( info ! = null ) return info ;
2016-01-17 23:50:07 +00:00
2016-07-13 21:53:48 +00:00
//mxd. No dice...
return null ;
2014-01-03 10:33:45 +00:00
}
2017-03-01 22:21:08 +00:00
/// <summary>
/// This method parses $argN into argumentinfos.
/// </summary>
public void ParseCustomArguments ( )
{
2023-09-15 19:28:20 +00:00
for ( int i = 0 ; i < Thing . NUM_ARGS ; i + + )
2017-03-01 22:21:08 +00:00
{
if ( HasProperty ( "$arg" + i ) )
args [ i ] = new ArgumentInfo ( this , i ) ;
else args [ i ] = null ;
}
2023-09-19 17:06:32 +00:00
for ( int i = 0 ; i < Thing . NUM_STRING_ARGS ; i + + )
{
if ( HasProperty ( "$stringarg" + i ) )
stringargs [ i ] = new ArgumentInfo ( this , i , true ) ;
else stringargs [ i ] = null ;
}
2017-03-01 22:21:08 +00:00
}
public ArgumentInfo GetArgumentInfo ( int idx )
{
if ( args [ idx ] ! = null )
return args [ idx ] ;
// if we have $clearargs, don't inherit anything!
if ( props . ContainsKey ( "$clearargs" ) )
return null ;
if ( baseclass ! = null )
return baseclass . GetArgumentInfo ( idx ) ;
return null ;
2023-09-19 17:06:32 +00:00
}
public ArgumentInfo GetStringArgumentInfo ( int idx )
{
if ( stringargs [ idx ] ! = null )
return stringargs [ idx ] ;
// if we have $clearargs, don't inherit anything!
if ( props . ContainsKey ( "$clearargs" ) )
return null ;
if ( baseclass ! = null )
return baseclass . GetStringArgumentInfo ( idx ) ;
return null ;
}
2024-01-31 21:33:47 +00:00
public string ReadFracunit ( string input , ZDTextParser parser )
{
if ( input . Contains ( "FRACUNIT" ) | | input . Contains ( "FRACBITS" ) | | input . Contains ( "FU" ) )
return new string ( input . Where ( c = > char . IsDigit ( c ) ) . ToArray ( ) ) ;
else if ( int . TryParse ( input , out int result ) )
return ( result > > 16 ) . ToString ( ) ;
else
{
parser . ReportError ( "Unknown text found in height/radius field: defaulting to 8" ) ;
return "8" ;
}
}
2023-09-19 17:06:32 +00:00
2009-04-19 18:07:22 +00:00
#endregion
}
}