2008-09-21 18:02:38 +00:00
/*
* * thingdef - parse . cpp
* *
* * Actor definitions - all parser related code
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2002 - 2007 Christoph Oelckers
* * Copyright 2004 - 2007 Randy Heit
* * All rights reserved .
* *
* * Redistribution and use in source and binary forms , with or without
* * modification , are permitted provided that the following conditions
* * are met :
* *
* * 1. Redistributions of source code must retain the above copyright
* * notice , this list of conditions and the following disclaimer .
* * 2. Redistributions in binary form must reproduce the above copyright
* * notice , this list of conditions and the following disclaimer in the
* * documentation and / or other materials provided with the distribution .
* * 3. The name of the author may not be used to endorse or promote products
* * derived from this software without specific prior written permission .
* * 4. When not used as part of ZDoom or a ZDoom derivative , this code will be
* * covered by the terms of the GNU General Public License as published by
* * the Free Software Foundation ; either version 2 of the License , or ( at
* * your option ) any later version .
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR
* * IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* * INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* * NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* * DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
# include "doomtype.h"
# include "actor.h"
# include "a_pickups.h"
# include "sc_man.h"
# include "thingdef.h"
# include "a_morph.h"
# include "cmdlib.h"
# include "templates.h"
# include "v_palette.h"
# include "doomerrors.h"
2008-09-23 07:46:23 +00:00
# include "i_system.h"
2008-10-18 17:17:44 +00:00
# include "thingdef_exp.h"
2008-10-19 21:43:36 +00:00
# include "w_wad.h"
2008-10-25 17:38:00 +00:00
# include "v_video.h"
2010-07-01 09:35:39 +00:00
# include "version.h"
# include "v_text.h"
# include "m_argv.h"
2008-09-21 18:02:38 +00:00
2008-10-25 17:38:00 +00:00
void ParseOldDecoration ( FScanner & sc , EDefinitionType def ) ;
//==========================================================================
//
// ParseParameter
//
2009-06-09 17:13:03 +00:00
// Parses a parameter - either a default in a function declaration
2008-10-25 17:38:00 +00:00
// or an argument in a function call.
//
//==========================================================================
FxExpression * ParseParameter ( FScanner & sc , PClass * cls , char type , bool constant )
{
FxExpression * x = NULL ;
int v ;
switch ( type )
{
case ' S ' :
case ' s ' : // Sound name
sc . MustGetString ( ) ;
x = new FxConstant ( FSoundID ( sc . String ) , sc ) ;
break ;
case ' M ' :
case ' m ' : // Actor name
sc . SetEscape ( true ) ;
sc . MustGetString ( ) ;
sc . SetEscape ( false ) ;
x = new FxClassTypeCast ( RUNTIME_CLASS ( AActor ) , new FxConstant ( FName ( sc . String ) , sc ) ) ;
break ;
case ' T ' :
case ' t ' : // String
sc . SetEscape ( true ) ;
sc . MustGetString ( ) ;
sc . SetEscape ( false ) ;
x = new FxConstant ( sc . String [ 0 ] ? FName ( sc . String ) : NAME_None , sc ) ;
break ;
case ' C ' :
case ' c ' : // Color
sc . MustGetString ( ) ;
if ( sc . Compare ( " none " ) )
{
v = - 1 ;
}
else if ( sc . Compare ( " " ) )
{
v = 0 ;
}
else
{
int c = V_GetColor ( NULL , sc . String ) ;
// 0 needs to be the default so we have to mark the color.
v = MAKEARGB ( 1 , RPART ( c ) , GPART ( c ) , BPART ( c ) ) ;
}
{
ExpVal val ;
val . Type = VAL_Color ;
val . Int = v ;
x = new FxConstant ( val , sc ) ;
break ;
}
case ' L ' :
case ' l ' :
{
// This forces quotation marks around the state name.
sc . MustGetToken ( TK_StringConst ) ;
if ( sc . String [ 0 ] = = 0 | | sc . Compare ( " None " ) )
{
x = new FxConstant ( ( FState * ) NULL , sc ) ;
}
else if ( sc . Compare ( " * " ) )
{
if ( constant )
{
x = new FxConstant ( ( FState * ) ( intptr_t ) - 1 , sc ) ;
}
else sc . ScriptError ( " Invalid state name '*' " ) ;
}
else
{
x = new FxMultiNameState ( sc . String , sc ) ;
}
break ;
}
case ' X ' :
case ' x ' :
2008-12-07 12:11:59 +00:00
case ' Y ' :
case ' y ' :
2008-10-25 17:38:00 +00:00
x = ParseExpression ( sc , cls ) ;
if ( constant & & ! x - > isConstant ( ) )
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " Default parameter must be constant. " ) ;
FScriptPosition : : ErrorCounter + + ;
2008-10-25 17:38:00 +00:00
}
break ;
default :
assert ( false ) ;
return NULL ;
}
return x ;
}
2008-09-21 18:02:38 +00:00
//==========================================================================
//
// ActorConstDef
//
// Parses a constant definition.
//
//==========================================================================
2008-10-25 17:38:00 +00:00
static void ParseConstant ( FScanner & sc , PSymbolTable * symt , PClass * cls )
2008-09-21 18:02:38 +00:00
{
2008-10-19 21:43:36 +00:00
// Read the type and make sure it's int or float.
if ( sc . CheckToken ( TK_Int ) | | sc . CheckToken ( TK_Float ) )
{
int type = sc . TokenType ;
sc . MustGetToken ( TK_Identifier ) ;
FName symname = sc . String ;
sc . MustGetToken ( ' = ' ) ;
FxExpression * expr = ParseExpression ( sc , cls ) ;
sc . MustGetToken ( ' ; ' ) ;
2008-09-21 18:02:38 +00:00
2008-10-19 21:43:36 +00:00
ExpVal val = expr - > EvalExpression ( NULL ) ;
delete expr ;
PSymbolConst * sym = new PSymbolConst ( symname ) ;
if ( type = = TK_Int )
{
sym - > ValueType = VAL_Int ;
sym - > Value = val . GetInt ( ) ;
}
else
{
sym - > ValueType = VAL_Float ;
2008-12-07 12:11:59 +00:00
sym - > Float = val . GetFloat ( ) ;
2008-10-19 21:43:36 +00:00
}
if ( symt - > AddSymbol ( sym ) = = NULL )
{
delete sym ;
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " '%s' is already defined in '%s'. " ,
2008-10-19 21:43:36 +00:00
symname . GetChars ( ) , cls ? cls - > TypeName . GetChars ( ) : " Global " ) ;
2010-01-02 11:38:27 +00:00
FScriptPosition : : ErrorCounter + + ;
2008-10-19 21:43:36 +00:00
}
}
else
2008-09-21 18:02:38 +00:00
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " Numeric type required for constant " ) ;
FScriptPosition : : ErrorCounter + + ;
2008-09-21 18:02:38 +00:00
}
}
//==========================================================================
//
// ActorEnumDef
//
// Parses an enum definition.
//
//==========================================================================
2008-10-25 17:38:00 +00:00
static void ParseEnum ( FScanner & sc , PSymbolTable * symt , PClass * cls )
2008-09-21 18:02:38 +00:00
{
int currvalue = 0 ;
sc . MustGetToken ( ' { ' ) ;
while ( ! sc . CheckToken ( ' } ' ) )
{
sc . MustGetToken ( TK_Identifier ) ;
FName symname = sc . String ;
if ( sc . CheckToken ( ' = ' ) )
{
2008-10-18 17:17:44 +00:00
FxExpression * expr = ParseExpression ( sc , cls ) ;
2008-10-19 21:43:36 +00:00
currvalue = expr - > EvalExpression ( NULL ) . GetInt ( ) ;
2008-10-18 17:17:44 +00:00
delete expr ;
2008-09-21 18:02:38 +00:00
}
2008-10-19 21:43:36 +00:00
PSymbolConst * sym = new PSymbolConst ( symname ) ;
sym - > ValueType = VAL_Int ;
2008-09-21 18:02:38 +00:00
sym - > Value = currvalue ;
if ( symt - > AddSymbol ( sym ) = = NULL )
{
delete sym ;
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " '%s' is already defined in '%s'. " ,
2008-10-19 21:43:36 +00:00
symname . GetChars ( ) , cls ? cls - > TypeName . GetChars ( ) : " Global " ) ;
2010-01-02 11:38:27 +00:00
FScriptPosition : : ErrorCounter + + ;
2008-09-21 18:02:38 +00:00
}
// This allows a comma after the last value but doesn't enforce it.
if ( sc . CheckToken ( ' } ' ) ) break ;
sc . MustGetToken ( ' , ' ) ;
currvalue + + ;
}
sc . MustGetToken ( ' ; ' ) ;
}
2008-10-19 21:43:36 +00:00
//==========================================================================
//
2009-10-25 02:19:51 +00:00
// ParseNativeVariable
2008-10-19 21:43:36 +00:00
//
2009-10-25 02:19:51 +00:00
// Parses a native variable declaration.
2008-10-19 21:43:36 +00:00
//
//==========================================================================
2009-10-25 02:19:51 +00:00
static void ParseNativeVariable ( FScanner & sc , PSymbolTable * symt , PClass * cls )
2008-10-19 21:43:36 +00:00
{
FExpressionType valuetype ;
if ( sc . LumpNum = = - 1 | | Wads . GetLumpFile ( sc . LumpNum ) > 0 )
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " variables can only be imported by internal class and actor definitions! " ) ;
FScriptPosition : : ErrorCounter + + ;
2008-10-19 21:43:36 +00:00
}
// Read the type and make sure it's int or float.
sc . MustGetAnyToken ( ) ;
switch ( sc . TokenType )
{
case TK_Int :
valuetype = VAL_Int ;
break ;
case TK_Float :
valuetype = VAL_Float ;
break ;
case TK_Angle_t :
valuetype = VAL_Angle ;
break ;
case TK_Fixed_t :
valuetype = VAL_Fixed ;
break ;
case TK_Bool :
valuetype = VAL_Bool ;
break ;
case TK_Identifier :
valuetype = VAL_Object ;
// Todo: Object type
sc . ScriptError ( " Object type variables not implemented yet! " ) ;
break ;
default :
sc . ScriptError ( " Invalid variable type %s " , sc . String ) ;
return ;
}
sc . MustGetToken ( TK_Identifier ) ;
FName symname = sc . String ;
if ( sc . CheckToken ( ' [ ' ) )
{
FxExpression * expr = ParseExpression ( sc , cls ) ;
int maxelems = expr - > EvalExpression ( NULL ) . GetInt ( ) ;
delete expr ;
sc . MustGetToken ( ' ] ' ) ;
valuetype . MakeArray ( maxelems ) ;
}
sc . MustGetToken ( ' ; ' ) ;
2009-08-02 03:38:57 +00:00
const FVariableInfo * vi = FindVariable ( symname , cls ) ;
2008-10-19 21:43:36 +00:00
if ( vi = = NULL )
{
sc . ScriptError ( " Unknown native variable '%s' " , symname . GetChars ( ) ) ;
}
PSymbolVariable * sym = new PSymbolVariable ( symname ) ;
sym - > offset = vi - > address ; // todo
sym - > ValueType = valuetype ;
2009-10-25 02:19:51 +00:00
sym - > bUserVar = false ;
2008-10-19 21:43:36 +00:00
if ( symt - > AddSymbol ( sym ) = = NULL )
{
delete sym ;
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " '%s' is already defined in '%s'. " ,
2008-10-19 21:43:36 +00:00
symname . GetChars ( ) , cls ? cls - > TypeName . GetChars ( ) : " Global " ) ;
2010-01-02 11:38:27 +00:00
FScriptPosition : : ErrorCounter + + ;
2008-10-19 21:43:36 +00:00
}
}
2009-10-25 02:19:51 +00:00
//==========================================================================
//
// ParseUserVariable
//
// Parses a user variable declaration.
//
//==========================================================================
static void ParseUserVariable ( FScanner & sc , PSymbolTable * symt , PClass * cls )
{
FExpressionType valuetype ;
// Only non-native classes may have user variables.
if ( ! cls - > bRuntimeClass )
{
sc . ScriptError ( " Native classes may not have user variables " ) ;
}
// Read the type and make sure it's int.
sc . MustGetAnyToken ( ) ;
2010-01-02 11:38:27 +00:00
if ( sc . TokenType ! = TK_Int )
2009-10-25 02:19:51 +00:00
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " User variables must be of type int " ) ;
FScriptPosition : : ErrorCounter + + ;
2009-10-25 02:19:51 +00:00
}
2010-01-02 11:38:27 +00:00
valuetype = VAL_Int ;
2009-10-25 02:19:51 +00:00
sc . MustGetToken ( TK_Identifier ) ;
// For now, restrict user variables to those that begin with "user_" to guarantee
// no clashes with internal member variable names.
if ( sc . StringLen < 6 | | strnicmp ( " user_ " , sc . String , 5 ) ! = 0 )
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " User variable names must begin with \" user_ \" " ) ;
FScriptPosition : : ErrorCounter + + ;
2009-10-25 02:19:51 +00:00
}
FName symname = sc . String ;
if ( sc . CheckToken ( ' [ ' ) )
{
FxExpression * expr = ParseExpression ( sc , cls ) ;
int maxelems = expr - > EvalExpression ( NULL ) . GetInt ( ) ;
delete expr ;
sc . MustGetToken ( ' ] ' ) ;
if ( maxelems < = 0 )
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " Array size must be positive " ) ;
FScriptPosition : : ErrorCounter + + ;
maxelems = 1 ;
2009-10-25 02:19:51 +00:00
}
valuetype . MakeArray ( maxelems ) ;
}
sc . MustGetToken ( ' ; ' ) ;
PSymbolVariable * sym = new PSymbolVariable ( symname ) ;
sym - > offset = cls - > Extend ( sizeof ( int ) * ( valuetype . Type = = VAL_Array ? valuetype . size : 1 ) ) ;
sym - > ValueType = valuetype ;
sym - > bUserVar = true ;
if ( symt - > AddSymbol ( sym ) = = NULL )
{
delete sym ;
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " '%s' is already defined in '%s'. " ,
2009-10-25 02:19:51 +00:00
symname . GetChars ( ) , cls ? cls - > TypeName . GetChars ( ) : " Global " ) ;
2010-01-02 11:38:27 +00:00
FScriptPosition : : ErrorCounter + + ;
2009-10-25 02:19:51 +00:00
}
}
2008-09-21 18:02:38 +00:00
//==========================================================================
//
// Parses a flag name
//
//==========================================================================
2008-10-25 17:38:00 +00:00
static void ParseActorFlag ( FScanner & sc , Baggage & bag , int mod )
2008-09-21 18:02:38 +00:00
{
sc . MustGetString ( ) ;
FString part1 = sc . String ;
const char * part2 = NULL ;
if ( sc . CheckString ( " . " ) )
{
sc . MustGetString ( ) ;
part2 = sc . String ;
}
HandleActorFlag ( sc , bag , part1 , part2 , mod ) ;
}
//==========================================================================
//
// Processes a flag. Also used by olddecorations.cpp
//
//==========================================================================
void HandleActorFlag ( FScanner & sc , Baggage & bag , const char * part1 , const char * part2 , int mod )
{
FFlagDef * fd ;
if ( ( fd = FindFlag ( bag . Info - > Class , part1 , part2 ) ) )
{
AActor * defaults = ( AActor * ) bag . Info - > Class - > Defaults ;
if ( fd - > structoffset = = - 1 ) // this is a deprecated flag that has been changed into a real property
{
HandleDeprecatedFlags ( defaults , bag . Info , mod = = ' + ' , fd - > flagbit ) ;
}
else
{
DWORD * flagvar = ( DWORD * ) ( ( char * ) defaults + fd - > structoffset ) ;
if ( mod = = ' + ' )
{
* flagvar | = fd - > flagbit ;
}
else
{
* flagvar & = ~ fd - > flagbit ;
}
}
}
else
{
if ( part2 = = NULL )
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " \" %s \" is an unknown flag \n " , part1 ) ;
2008-09-21 18:02:38 +00:00
}
else
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " \" %s.%s \" is an unknown flag \n " , part1 , part2 ) ;
2008-09-21 18:02:38 +00:00
}
2010-01-02 11:38:27 +00:00
FScriptPosition : : ErrorCounter + + ;
2008-09-21 18:02:38 +00:00
}
}
//==========================================================================
//
// [MH] parses a morph style expression
//
//==========================================================================
2009-09-15 14:16:55 +00:00
struct FParseValue
2008-09-21 18:02:38 +00:00
{
2009-09-15 14:16:55 +00:00
const char * Name ;
int Flag ;
} ;
2008-09-21 18:02:38 +00:00
2009-09-15 14:16:55 +00:00
int ParseFlagExpressionString ( FScanner & sc , const FParseValue * vals )
{
2008-09-21 18:02:38 +00:00
// May be given flags by number...
if ( sc . CheckNumber ( ) )
{
sc . MustGetNumber ( ) ;
return sc . Number ;
}
// ... else should be flags by name.
// NOTE: Later this should be removed and a normal expression used.
// The current DECORATE parser can't handle this though.
bool gotparen = sc . CheckString ( " ( " ) ;
int style = 0 ;
do
{
sc . MustGetString ( ) ;
2009-09-15 14:16:55 +00:00
style | = vals [ sc . MustMatchString ( & vals - > Name , sizeof ( * vals ) ) ] . Flag ;
2008-09-21 18:02:38 +00:00
}
while ( sc . CheckString ( " | " ) ) ;
if ( gotparen )
{
sc . MustGetStringName ( " ) " ) ;
}
return style ;
}
2009-09-15 14:16:55 +00:00
static int ParseMorphStyle ( FScanner & sc )
{
static const FParseValue morphstyles [ ] = {
{ " MRF_ADDSTAMINA " , MORPH_ADDSTAMINA } ,
{ " MRF_FULLHEALTH " , MORPH_FULLHEALTH } ,
{ " MRF_UNDOBYTOMEOFPOWER " , MORPH_UNDOBYTOMEOFPOWER } ,
{ " MRF_UNDOBYCHAOSDEVICE " , MORPH_UNDOBYCHAOSDEVICE } ,
{ " MRF_FAILNOTELEFRAG " , MORPH_FAILNOTELEFRAG } ,
{ " MRF_FAILNOLAUGH " , MORPH_FAILNOLAUGH } ,
{ " MRF_WHENINVULNERABLE " , MORPH_WHENINVULNERABLE } ,
{ " MRF_LOSEACTUALWEAPON " , MORPH_LOSEACTUALWEAPON } ,
{ " MRF_NEWTIDBEHAVIOUR " , MORPH_NEWTIDBEHAVIOUR } ,
{ " MRF_UNDOBYDEATH " , MORPH_UNDOBYDEATH } ,
{ " MRF_UNDOBYDEATHFORCED " , MORPH_UNDOBYDEATHFORCED } ,
{ " MRF_UNDOBYDEATHSAVES " , MORPH_UNDOBYDEATHSAVES } ,
{ NULL , 0 }
} ;
return ParseFlagExpressionString ( sc , morphstyles ) ;
}
static int ParseThingActivation ( FScanner & sc )
{
static const FParseValue activationstyles [ ] = {
{ " THINGSPEC_Default " , THINGSPEC_Default } ,
{ " THINGSPEC_ThingActs " , THINGSPEC_ThingActs } ,
{ " THINGSPEC_ThingTargets " , THINGSPEC_ThingTargets } ,
{ " THINGSPEC_TriggerTargets " , THINGSPEC_TriggerTargets } ,
{ " THINGSPEC_MonsterTrigger " , THINGSPEC_MonsterTrigger } ,
{ " THINGSPEC_MissileTrigger " , THINGSPEC_MissileTrigger } ,
{ " THINGSPEC_ClearSpecial " , THINGSPEC_ClearSpecial } ,
2009-09-28 15:59:20 +00:00
{ " THINGSPEC_NoDeathSpecial " , THINGSPEC_NoDeathSpecial } ,
{ " THINGSPEC_TriggerActs " , THINGSPEC_TriggerActs } ,
2009-10-09 20:35:07 +00:00
{ " THINGSPEC_Activate " , THINGSPEC_Activate } ,
{ " THINGSPEC_Deactivate " , THINGSPEC_Deactivate } ,
{ " THINGSPEC_Switch " , THINGSPEC_Switch } ,
2009-09-15 14:16:55 +00:00
{ NULL , 0 }
} ;
return ParseFlagExpressionString ( sc , activationstyles ) ;
}
2008-09-21 18:02:38 +00:00
//==========================================================================
//
// For getting a state address from the parent
// No attempts have been made to add new functionality here
// This is strictly for keeping compatibility with old WADs!
//
//==========================================================================
2008-10-25 17:38:00 +00:00
static FState * CheckState ( FScanner & sc , PClass * type )
2008-09-21 18:02:38 +00:00
{
int v = 0 ;
if ( sc . GetString ( ) & & ! sc . Crossed )
{
if ( sc . Compare ( " 0 " ) ) return NULL ;
else if ( sc . Compare ( " PARENT " ) )
{
FState * state = NULL ;
sc . MustGetString ( ) ;
FActorInfo * info = type - > ParentClass - > ActorInfo ;
if ( info ! = NULL )
{
state = info - > FindState ( FName ( sc . String ) ) ;
}
if ( sc . GetString ( ) )
{
if ( sc . Compare ( " + " ) )
{
sc . MustGetNumber ( ) ;
v = sc . Number ;
}
else
{
sc . UnGet ( ) ;
}
}
if ( state = = NULL & & v = = 0 ) return NULL ;
if ( v ! = 0 & & state = = NULL )
{
2010-01-02 11:38:27 +00:00
sc . ScriptMessage ( " Attempt to get invalid state from actor %s \n " , type - > ParentClass - > TypeName . GetChars ( ) ) ;
FScriptPosition : : ErrorCounter + + ;
2008-09-21 18:02:38 +00:00
return NULL ;
}
state + = v ;
return state ;
}
2010-01-02 11:38:27 +00:00
else
{
sc . ScriptMessage ( " Invalid state assignment " ) ;
FScriptPosition : : ErrorCounter + + ;
}
2008-09-21 18:02:38 +00:00
}
return NULL ;
}
//==========================================================================
//
// Parses an actor property's parameters and calls the handler
//
//==========================================================================
2008-10-25 17:38:00 +00:00
static bool ParsePropertyParams ( FScanner & sc , FPropertyInfo * prop , AActor * defaults , Baggage & bag )
2008-09-21 18:02:38 +00:00
{
static TArray < FPropParam > params ;
static TArray < FString > strings ;
params . Clear ( ) ;
strings . Clear ( ) ;
params . Reserve ( 1 ) ;
params [ 0 ] . i = 0 ;
if ( prop - > params [ 0 ] ! = ' 0 ' )
{
const char * p = prop - > params ;
bool nocomma ;
bool optcomma ;
while ( * p )
{
FPropParam conv ;
FPropParam pref ;
nocomma = false ;
conv . s = NULL ;
2008-10-05 18:45:05 +00:00
pref . s = NULL ;
2008-09-21 18:02:38 +00:00
pref . i = - 1 ;
switch ( ( * p ) & 223 )
{
case ' X ' : // Expression in parentheses or number.
if ( sc . CheckString ( " ( " ) )
{
2008-10-25 17:38:00 +00:00
FxExpression * x = ParseExpression ( sc , bag . Info - > Class ) ;
conv . i = 0x40000000 | StateParams . Add ( x , bag . Info - > Class , false ) ;
2008-09-21 18:02:38 +00:00
params . Push ( conv ) ;
sc . MustGetStringName ( " ) " ) ;
break ;
}
// fall through
case ' I ' :
sc . MustGetNumber ( ) ;
conv . i = sc . Number ;
break ;
case ' F ' :
sc . MustGetFloat ( ) ;
2009-05-15 10:39:40 +00:00
conv . f = float ( sc . Float ) ;
2008-09-21 18:02:38 +00:00
break ;
case ' Z ' : // an optional string. Does not allow any numerical value.
if ( sc . CheckFloat ( ) )
{
nocomma = true ;
sc . UnGet ( ) ;
break ;
}
// fall through
case ' S ' :
sc . MustGetString ( ) ;
conv . s = strings [ strings . Reserve ( 1 ) ] = sc . String ;
break ;
2009-10-16 16:04:19 +00:00
case ' T ' :
sc . MustGetString ( ) ;
conv . s = strings [ strings . Reserve ( 1 ) ] = strbin1 ( sc . String ) ;
break ;
2008-09-21 18:02:38 +00:00
case ' C ' :
if ( sc . CheckNumber ( ) )
{
int R , G , B ;
R = clamp ( sc . Number , 0 , 255 ) ;
sc . CheckString ( " , " ) ;
sc . MustGetNumber ( ) ;
G = clamp ( sc . Number , 0 , 255 ) ;
sc . CheckString ( " , " ) ;
sc . MustGetNumber ( ) ;
B = clamp ( sc . Number , 0 , 255 ) ;
conv . i = MAKERGB ( R , G , B ) ;
pref . i = 0 ;
}
else
{
sc . MustGetString ( ) ;
conv . s = strings [ strings . Reserve ( 1 ) ] = sc . String ;
pref . i = 1 ;
}
break ;
case ' M ' : // special case. An expression-aware parser will not need this.
conv . i = ParseMorphStyle ( sc ) ;
break ;
2009-09-22 01:16:54 +00:00
case ' N ' : // special case. An expression-aware parser will not need this.
conv . i = ParseThingActivation ( sc ) ;
break ;
2008-09-21 18:02:38 +00:00
case ' L ' : // Either a number or a list of strings
if ( sc . CheckNumber ( ) )
{
pref . i = 0 ;
conv . i = sc . Number ;
}
else
{
pref . i = 1 ;
params . Push ( pref ) ;
params [ 0 ] . i + + ;
do
{
sc . MustGetString ( ) ;
conv . s = strings [ strings . Reserve ( 1 ) ] = sc . String ;
params . Push ( conv ) ;
params [ 0 ] . i + + ;
}
while ( sc . CheckString ( " , " ) ) ;
goto endofparm ;
}
break ;
default :
assert ( false ) ;
break ;
}
if ( pref . i ! = - 1 )
{
params . Push ( pref ) ;
params [ 0 ] . i + + ;
}
params . Push ( conv ) ;
params [ 0 ] . i + + ;
endofparm :
p + + ;
// Hack for some properties that have to allow comma less
// parameter lists for compatibility.
if ( ( optcomma = ( * p = = ' _ ' ) ) )
p + + ;
if ( nocomma )
{
continue ;
}
else if ( * p = = 0 )
{
break ;
}
else if ( * p > = ' a ' )
{
if ( ! sc . CheckString ( " , " ) )
{
if ( optcomma )
{
if ( ! sc . CheckFloat ( ) ) break ;
else sc . UnGet ( ) ;
}
else break ;
}
}
else
{
if ( ! optcomma ) sc . MustGetStringName ( " , " ) ;
else sc . CheckString ( " , " ) ;
}
}
}
// call the handler
try
{
2008-12-07 12:11:59 +00:00
prop - > Handler ( defaults , bag . Info , bag , & params [ 0 ] ) ;
2008-09-21 18:02:38 +00:00
}
catch ( CRecoverableError & error )
{
sc . ScriptError ( " %s " , error . GetMessage ( ) ) ;
}
return true ;
}
//==========================================================================
//
// Parses an actor property
//
//==========================================================================
2008-10-25 17:38:00 +00:00
static void ParseActorProperty ( FScanner & sc , Baggage & bag )
2008-09-21 18:02:38 +00:00
{
static const char * statenames [ ] = {
" Spawn " , " See " , " Melee " , " Missile " , " Pain " , " Death " , " XDeath " , " Burn " ,
" Ice " , " Raise " , " Crash " , " Crush " , " Wound " , " Disintegrate " , " Heal " , NULL } ;
strlwr ( sc . String ) ;
FString propname = sc . String ;
if ( sc . CheckString ( " . " ) )
{
sc . MustGetString ( ) ;
propname + = ' . ' ;
strlwr ( sc . String ) ;
propname + = sc . String ;
}
else
{
sc . UnGet ( ) ;
}
FPropertyInfo * prop = FindProperty ( propname ) ;
if ( prop ! = NULL )
{
if ( bag . Info - > Class - > IsDescendantOf ( prop - > cls ) )
{
ParsePropertyParams ( sc , prop , ( AActor * ) bag . Info - > Class - > Defaults , bag ) ;
}
else
{
2009-02-19 14:36:37 +00:00
sc . ScriptMessage ( " \" %s \" requires an actor of type \" %s \" \n " , propname . GetChars ( ) , prop - > cls - > TypeName . GetChars ( ) ) ;
FScriptPosition : : ErrorCounter + + ;
2008-09-21 18:02:38 +00:00
}
}
else if ( ! propname . CompareNoCase ( " States " ) )
{
2009-02-19 14:36:37 +00:00
if ( bag . StateSet )
{
sc . ScriptMessage ( " '%s' contains multiple state declarations " , bag . Info - > Class - > TypeName . GetChars ( ) ) ;
FScriptPosition : : ErrorCounter + + ;
}
ParseStates ( sc , bag . Info , ( AActor * ) bag . Info - > Class - > Defaults , bag ) ;
2008-09-21 18:02:38 +00:00
bag . StateSet = true ;
}
else if ( MatchString ( propname , statenames ) ! = - 1 )
{
2008-12-07 12:11:59 +00:00
bag . statedef . SetStateLabel ( propname , CheckState ( sc , bag . Info - > Class ) ) ;
2008-09-21 18:02:38 +00:00
}
else
{
sc . ScriptError ( " \" %s \" is an unknown actor property \n " , propname . GetChars ( ) ) ;
}
}
//==========================================================================
//
2008-10-25 17:38:00 +00:00
// ActorActionDef
//
// Parses an action function definition. A lot of this is essentially
// documentation in the declaration for when I have a proper language
// ready.
2008-09-21 18:02:38 +00:00
//
//==========================================================================
2008-10-25 17:38:00 +00:00
static void ParseActionDef ( FScanner & sc , PClass * cls )
2008-09-21 18:02:38 +00:00
{
2008-10-25 17:38:00 +00:00
enum
{
OPTIONAL = 1
} ;
2009-02-19 14:36:37 +00:00
bool error = false ;
2009-08-02 03:38:57 +00:00
const AFuncDesc * afd ;
2008-10-25 17:38:00 +00:00
FName funcname ;
FString args ;
TArray < FxExpression * > DefaultParams ;
bool hasdefaults = false ;
if ( sc . LumpNum = = - 1 | | Wads . GetLumpFile ( sc . LumpNum ) > 0 )
2008-09-21 22:25:23 +00:00
{
2009-02-19 14:36:37 +00:00
sc . ScriptMessage ( " action functions can only be imported by internal class and actor definitions! " ) ;
error + + ;
2008-09-21 22:25:23 +00:00
}
2008-10-25 17:38:00 +00:00
sc . MustGetToken ( TK_Native ) ;
sc . MustGetToken ( TK_Identifier ) ;
funcname = sc . String ;
afd = FindFunction ( sc . String ) ;
if ( afd = = NULL )
2008-09-21 22:25:23 +00:00
{
2009-02-19 14:36:37 +00:00
sc . ScriptMessage ( " The function '%s' has not been exported from the executable. " , sc . String ) ;
error + + ;
2008-09-21 22:25:23 +00:00
}
2008-10-25 17:38:00 +00:00
sc . MustGetToken ( ' ( ' ) ;
if ( ! sc . CheckToken ( ' ) ' ) )
2008-09-21 18:02:38 +00:00
{
2008-10-25 17:38:00 +00:00
while ( sc . TokenType ! = ' ) ' )
2008-09-21 18:02:38 +00:00
{
2008-10-25 17:38:00 +00:00
int flags = 0 ;
char type = ' @ ' ;
// Retrieve flags before type name
for ( ; ; )
{
if ( sc . CheckToken ( TK_Coerce ) | | sc . CheckToken ( TK_Native ) )
{
}
else
{
break ;
}
}
// Read the variable type
sc . MustGetAnyToken ( ) ;
switch ( sc . TokenType )
{
case TK_Bool :
case TK_Int :
type = ' x ' ;
break ;
2008-12-07 12:11:59 +00:00
case TK_Float :
type = ' y ' ;
break ;
2008-10-25 17:38:00 +00:00
case TK_Sound : type = ' s ' ; break ;
case TK_String : type = ' t ' ; break ;
case TK_Name : type = ' t ' ; break ;
case TK_State : type = ' l ' ; break ;
case TK_Color : type = ' c ' ; break ;
case TK_Class :
sc . MustGetToken ( ' < ' ) ;
sc . MustGetToken ( TK_Identifier ) ; // Skip class name, since the parser doesn't care
sc . MustGetToken ( ' > ' ) ;
type = ' m ' ;
break ;
case TK_Ellipsis :
type = ' + ' ;
sc . MustGetToken ( ' ) ' ) ;
sc . UnGet ( ) ;
break ;
default :
2009-02-19 14:36:37 +00:00
sc . ScriptMessage ( " Unknown variable type %s " , sc . TokenName ( sc . TokenType , sc . String ) . GetChars ( ) ) ;
type = ' x ' ;
FScriptPosition : : ErrorCounter + + ;
2008-10-25 17:38:00 +00:00
break ;
}
// Read the optional variable name
if ( ! sc . CheckToken ( ' , ' ) & & ! sc . CheckToken ( ' ) ' ) )
{
sc . MustGetToken ( TK_Identifier ) ;
}
else
{
sc . UnGet ( ) ;
}
FxExpression * def ;
if ( sc . CheckToken ( ' = ' ) )
2008-09-21 18:02:38 +00:00
{
2008-10-25 17:38:00 +00:00
hasdefaults = true ;
flags | = OPTIONAL ;
def = ParseParameter ( sc , cls , type , true ) ;
}
else
{
def = NULL ;
}
DefaultParams . Push ( def ) ;
if ( ! ( flags & OPTIONAL ) & & type ! = ' + ' )
{
type - = ' a ' - ' A ' ;
}
args + = type ;
sc . MustGetAnyToken ( ) ;
if ( sc . TokenType ! = ' , ' & & sc . TokenType ! = ' ) ' )
{
sc . ScriptError ( " Expected ',' or ')' but got %s instead " , sc . TokenName ( sc . TokenType , sc . String ) . GetChars ( ) ) ;
2008-09-21 18:02:38 +00:00
}
}
2008-10-25 17:38:00 +00:00
}
sc . MustGetToken ( ' ; ' ) ;
2009-06-30 19:20:39 +00:00
if ( afd ! = NULL )
2008-10-25 17:38:00 +00:00
{
2009-06-30 19:20:39 +00:00
PSymbolActionFunction * sym = new PSymbolActionFunction ( funcname ) ;
sym - > Arguments = args ;
sym - > Function = afd - > Function ;
if ( hasdefaults )
2008-10-25 17:38:00 +00:00
{
2009-06-30 19:20:39 +00:00
sym - > defaultparameterindex = StateParams . Size ( ) ;
for ( unsigned int i = 0 ; i < DefaultParams . Size ( ) ; i + + )
{
StateParams . Add ( DefaultParams [ i ] , cls , true ) ;
}
}
else
{
sym - > defaultparameterindex = - 1 ;
}
if ( error )
{
FScriptPosition : : ErrorCounter + + ;
}
else if ( cls - > Symbols . AddSymbol ( sym ) = = NULL )
{
delete sym ;
sc . ScriptMessage ( " '%s' is already defined in class '%s'. " ,
funcname . GetChars ( ) , cls - > TypeName . GetChars ( ) ) ;
FScriptPosition : : ErrorCounter + + ;
2008-10-25 17:38:00 +00:00
}
}
}
//==========================================================================
//
// Starts a new actor definition
//
//==========================================================================
static FActorInfo * ParseActorHeader ( FScanner & sc , Baggage * bag )
{
FName typeName ;
FName parentName ;
FName replaceName ;
bool native = false ;
int DoomEdNum = - 1 ;
// Get actor name
sc . MustGetString ( ) ;
char * colon = strchr ( sc . String , ' : ' ) ;
if ( colon ! = NULL )
{
* colon + + = 0 ;
}
typeName = sc . String ;
// Do some tweaking so that a definition like 'Actor:Parent' which is read as a single token is recognized as well
// without having resort to C-mode (which disallows periods in actor names.)
if ( colon = = NULL )
{
sc . MustGetString ( ) ;
if ( sc . String [ 0 ] = = ' : ' )
{
colon = sc . String + 1 ;
}
}
if ( colon ! = NULL )
{
if ( colon [ 0 ] = = 0 )
{
sc . MustGetString ( ) ;
colon = sc . String ;
}
}
if ( colon = = NULL )
{
sc . UnGet ( ) ;
}
parentName = colon ;
// Check for "replaces"
if ( sc . CheckString ( " replaces " ) )
{
// Get actor name
sc . MustGetString ( ) ;
replaceName = sc . String ;
2010-02-13 08:56:08 +00:00
if ( replaceName = = typeName )
{
sc . ScriptMessage ( " Cannot replace class %s with itself " , typeName . GetChars ( ) ) ;
FScriptPosition : : ErrorCounter + + ;
}
2008-10-25 17:38:00 +00:00
}
// Now, after the actor names have been parsed, it is time to switch to C-mode
// for the rest of the actor definition.
sc . SetCMode ( true ) ;
if ( sc . CheckNumber ( ) )
{
if ( sc . Number > = - 1 & & sc . Number < 32768 ) DoomEdNum = sc . Number ;
2009-02-19 14:36:37 +00:00
else
{
// does not need to be fatal.
sc . ScriptMessage ( " DoomEdNum must be in the range [-1,32767] " ) ;
FScriptPosition : : ErrorCounter + + ;
}
2008-10-25 17:38:00 +00:00
}
if ( sc . CheckString ( " native " ) )
{
native = true ;
}
try
{
2009-02-21 21:44:15 +00:00
FActorInfo * info = CreateNewActor ( sc , typeName , parentName , native ) ;
2008-12-07 12:11:59 +00:00
info - > DoomEdNum = DoomEdNum > 0 ? DoomEdNum : - 1 ;
2010-07-26 17:10:43 +00:00
info - > Class - > Meta . SetMetaString ( ACMETA_Lump , Wads . GetLumpFullPath ( sc . LumpNum ) ) ;
2012-08-12 03:36:49 +00:00
SetReplacement ( sc , info , replaceName ) ;
2008-12-07 12:11:59 +00:00
2008-10-25 17:38:00 +00:00
ResetBaggage ( bag , info - > Class - > ParentClass ) ;
bag - > Info = info ;
bag - > Lumpnum = sc . LumpNum ;
# ifdef _DEBUG
bag - > ClassName = typeName ;
# endif
return info ;
}
catch ( CRecoverableError & err )
{
sc . ScriptError ( " %s " , err . GetMessage ( ) ) ;
return NULL ;
}
}
//==========================================================================
//
// Reads an actor definition
//
//==========================================================================
static void ParseActor ( FScanner & sc )
{
FActorInfo * info = NULL ;
Baggage bag ;
info = ParseActorHeader ( sc , & bag ) ;
sc . MustGetToken ( ' { ' ) ;
while ( sc . MustGetAnyToken ( ) , sc . TokenType ! = ' } ' )
{
switch ( sc . TokenType )
2008-09-21 18:02:38 +00:00
{
2008-10-25 17:38:00 +00:00
case TK_Action :
ParseActionDef ( sc , info - > Class ) ;
break ;
case TK_Const :
ParseConstant ( sc , & info - > Class - > Symbols , info - > Class ) ;
break ;
case TK_Enum :
ParseEnum ( sc , & info - > Class - > Symbols , info - > Class ) ;
break ;
case TK_Native :
2009-10-25 02:19:51 +00:00
ParseNativeVariable ( sc , & info - > Class - > Symbols , info - > Class ) ;
break ;
case TK_Var :
ParseUserVariable ( sc , & info - > Class - > Symbols , info - > Class ) ;
2008-10-25 17:38:00 +00:00
break ;
case TK_Identifier :
ParseActorProperty ( sc , bag ) ;
break ;
case ' + ' :
case ' - ' :
ParseActorFlag ( sc , bag , sc . TokenType ) ;
break ;
default :
sc . ScriptError ( " Unexpected '%s' in definition of '%s' " , sc . String , bag . Info - > Class - > TypeName . GetChars ( ) ) ;
break ;
2008-09-21 18:02:38 +00:00
}
}
2008-10-25 17:38:00 +00:00
FinishActor ( sc , info , bag ) ;
sc . SetCMode ( false ) ;
}
2012-04-07 12:11:17 +00:00
//==========================================================================
//
// Reads a damage definition
//
//==========================================================================
2008-10-25 17:38:00 +00:00
2012-04-07 12:11:17 +00:00
static void ParseDamageDefinition ( FScanner & sc )
{
sc . SetCMode ( true ) ; // This may be 100% irrelevant for such a simple syntax, but I don't know
// Get DamageType
sc . MustGetString ( ) ;
FName damageType = sc . String ;
DamageTypeDefinition dtd ;
sc . MustGetToken ( ' { ' ) ;
while ( sc . MustGetAnyToken ( ) , sc . TokenType ! = ' } ' )
{
if ( sc . Compare ( " FACTOR " ) )
{
sc . MustGetFloat ( ) ;
2012-04-07 15:29:47 +00:00
dtd . DefaultFactor = FLOAT2FIXED ( sc . Float ) ;
2012-04-07 12:11:17 +00:00
if ( ! dtd . DefaultFactor ) dtd . ReplaceFactor = true ; // Multiply by 0 yields 0: FixedMul(damage, FixedMul(factor, 0)) is more wasteful than FixedMul(factor, 0)
}
else if ( sc . Compare ( " REPLACEFACTOR " ) )
{
dtd . ReplaceFactor = true ;
}
else if ( sc . Compare ( " NOARMOR " ) )
{
dtd . NoArmor = true ;
}
else
{
sc . ScriptError ( " Unexpected data (%s) in damagetype definition. " , sc . String ) ;
}
}
dtd . Apply ( damageType ) ;
sc . SetCMode ( false ) ; // (set to true earlier in function)
}
2008-10-25 17:38:00 +00:00
//==========================================================================
//
// ParseDecorate
//
// Parses a single DECORATE lump
//
//==========================================================================
void ParseDecorate ( FScanner & sc )
{
// Get actor class name.
for ( ; ; )
2008-09-21 18:02:38 +00:00
{
2008-10-25 17:38:00 +00:00
FScanner : : SavedPos pos = sc . SavePos ( ) ;
if ( ! sc . GetToken ( ) )
{
return ;
}
switch ( sc . TokenType )
{
case TK_Include :
{
sc . MustGetString ( ) ;
2010-07-01 09:35:39 +00:00
// This check needs to remain overridable for testing purposes.
if ( Wads . GetLumpFile ( sc . LumpNum ) = = 0 & & ! Args - > CheckParm ( " -allowdecoratecrossincludes " ) )
{
int includefile = Wads . GetLumpFile ( Wads . CheckNumForFullName ( sc . String , true ) ) ;
if ( includefile ! = 0 )
{
I_FatalError ( " File %s is overriding core lump %s. " ,
Wads . GetWadFullName ( includefile ) , sc . String ) ;
}
}
2008-10-25 17:38:00 +00:00
FScanner newscanner ;
newscanner . Open ( sc . String ) ;
ParseDecorate ( newscanner ) ;
break ;
}
case TK_Const :
ParseConstant ( sc , & GlobalSymbols , NULL ) ;
break ;
case TK_Enum :
ParseEnum ( sc , & GlobalSymbols , NULL ) ;
break ;
case TK_Native :
2009-10-25 02:19:51 +00:00
ParseNativeVariable ( sc , & GlobalSymbols , NULL ) ;
2008-10-25 17:38:00 +00:00
break ;
case ' ; ' :
// ';' is the start of a comment in the non-cmode parser which
// is used to parse parts of the DECORATE lump. If we don't add
// a check here the user will only get weird non-informative
// error messages if a semicolon is found.
sc . ScriptError ( " Unexpected ';' " ) ;
break ;
case TK_Identifier :
// 'ACTOR' cannot be a keyword because it is also needed as a class identifier
// so let's do a special case for this.
if ( sc . Compare ( " ACTOR " ) )
{
ParseActor ( sc ) ;
break ;
}
2008-12-07 12:11:59 +00:00
else if ( sc . Compare ( " PICKUP " ) )
{
ParseOldDecoration ( sc , DEF_Pickup ) ;
2008-12-07 21:35:56 +00:00
break ;
2008-12-07 12:11:59 +00:00
}
else if ( sc . Compare ( " BREAKABLE " ) )
{
ParseOldDecoration ( sc , DEF_BreakableDecoration ) ;
2008-12-07 21:35:56 +00:00
break ;
2008-12-07 12:11:59 +00:00
}
else if ( sc . Compare ( " PROJECTILE " ) )
{
ParseOldDecoration ( sc , DEF_Projectile ) ;
2008-12-07 21:35:56 +00:00
break ;
2008-12-07 12:11:59 +00:00
}
2012-04-07 12:11:17 +00:00
else if ( sc . Compare ( " DAMAGETYPE " ) )
{
ParseDamageDefinition ( sc ) ;
break ;
}
2008-10-25 17:38:00 +00:00
default :
sc . RestorePos ( pos ) ;
ParseOldDecoration ( sc , DEF_Decoration ) ;
break ;
}
2008-09-21 18:02:38 +00:00
}
}