2016-03-01 15:47:10 +00:00
/*
* * thingdef . cpp
* *
* * Actor definitions
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2002 - 2008 Christoph Oelckers
* * Copyright 2004 - 2008 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 "gi.h"
# include "actor.h"
# include "info.h"
# include "sc_man.h"
# include "tarray.h"
# include "w_wad.h"
# include "templates.h"
# include "r_defs.h"
# include "a_pickups.h"
# include "s_sound.h"
# include "cmdlib.h"
# include "p_lnspec.h"
# include "a_action.h"
# include "decallib.h"
# include "m_random.h"
# include "i_system.h"
# include "m_argv.h"
# include "p_local.h"
# include "doomerrors.h"
# include "a_weaponpiece.h"
# include "p_conversation.h"
# include "v_text.h"
# include "thingdef.h"
2016-10-15 08:43:02 +00:00
# include "codegeneration/codegen.h"
2016-03-01 15:47:10 +00:00
# include "a_sharedglobal.h"
# include "vmbuilder.h"
# include "stats.h"
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
void InitThingdef ( ) ;
// STATIC FUNCTION PROTOTYPES --------------------------------------------
PClassActor * QuestItemClasses [ 31 ] ;
2016-11-15 10:21:08 +00:00
static TMap < FState * , FScriptPosition > StateSourceLines ;
static FScriptPosition unknownstatesource ( " unknown file " , 0 ) ;
//==========================================================================
//
// Saves the state's source lines for error messages during postprocessing
//
//==========================================================================
void SaveStateSourceLines ( FState * firststate , TArray < FScriptPosition > & positions )
{
for ( unsigned i = 0 ; i < positions . Size ( ) ; i + + )
{
StateSourceLines [ firststate + i ] = positions [ i ] ;
}
}
FScriptPosition & GetStateSource ( FState * state )
{
auto check = StateSourceLines . CheckKey ( state ) ;
return check ? * check : unknownstatesource ;
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
2016-10-12 22:53:59 +00:00
// SetImplicitArgs
//
// Adds the parameters implied by the function flags.
2016-03-01 15:47:10 +00:00
//
//==========================================================================
2016-11-15 10:21:08 +00:00
void SetImplicitArgs ( TArray < PType * > * args , TArray < DWORD > * argflags , TArray < FName > * argnames , PClass * cls , DWORD funcflags , int useflags )
2016-03-01 15:47:10 +00:00
{
2016-10-12 22:53:59 +00:00
// Must be called before adding any other arguments.
2016-10-15 12:36:08 +00:00
assert ( args = = nullptr | | args - > Size ( ) = = 0 ) ;
assert ( argflags = = nullptr | | argflags - > Size ( ) = = 0 ) ;
2016-03-01 15:47:10 +00:00
2016-10-12 22:53:59 +00:00
if ( funcflags & VARF_Method )
2016-03-01 15:47:10 +00:00
{
2016-10-12 22:53:59 +00:00
// implied self pointer
2016-10-15 15:40:27 +00:00
if ( args ! = nullptr ) args - > Push ( NewPointer ( cls ) ) ;
2016-10-20 23:12:54 +00:00
if ( argflags ! = nullptr ) argflags - > Push ( VARF_Implicit | VARF_ReadOnly ) ;
2016-10-15 12:36:08 +00:00
if ( argnames ! = nullptr ) argnames - > Push ( NAME_self ) ;
2016-03-01 15:47:10 +00:00
}
2016-10-12 22:53:59 +00:00
if ( funcflags & VARF_Action )
2016-03-01 15:47:10 +00:00
{
2016-10-12 22:53:59 +00:00
// implied caller and callingstate pointers
2016-10-15 12:36:08 +00:00
if ( args ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2016-11-15 10:21:08 +00:00
// Special treatment for weapons and CustomInventory flagged functions: 'self' is not the defining class but the actual user of the item, so this pointer must be of type 'Actor'
if ( useflags & ( SUF_WEAPON | SUF_ITEM ) )
2016-10-15 12:36:08 +00:00
{
2016-10-15 15:40:27 +00:00
args - > Insert ( 0 , NewPointer ( RUNTIME_CLASS ( AActor ) ) ) ; // this must go in before the real pointer to the containing class.
2016-10-15 12:36:08 +00:00
}
2016-10-15 13:10:48 +00:00
else
2016-10-15 12:36:08 +00:00
{
2016-10-15 15:40:27 +00:00
args - > Push ( NewPointer ( cls ) ) ;
2016-10-15 12:36:08 +00:00
}
args - > Push ( TypeState /*Info*/ ) ; // fixme: TypeState is not the correct type here!!!
2016-03-01 15:47:10 +00:00
}
2016-10-15 12:36:08 +00:00
if ( argflags ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2016-10-20 23:12:54 +00:00
argflags - > Push ( VARF_Implicit | VARF_ReadOnly ) ;
argflags - > Push ( VARF_Implicit | VARF_ReadOnly ) ;
2016-03-01 15:47:10 +00:00
}
2016-10-15 12:36:08 +00:00
if ( argnames ! = nullptr )
{
argnames - > Push ( NAME_invoker ) ;
argnames - > Push ( NAME_stateinfo ) ;
}
2016-03-01 15:47:10 +00:00
}
}
2016-10-15 12:36:08 +00:00
//==========================================================================
//
// CreateAnonymousFunction
//
// Creates a function symbol for an anonymous function
// This contains actual info about the implied variables which is needed
// during code generation.
//
//==========================================================================
2016-11-06 12:14:46 +00:00
PFunction * CreateAnonymousFunction ( PClass * containingclass , PType * returntype , int flags )
2016-10-15 12:36:08 +00:00
{
TArray < PType * > rets ( 1 ) ;
TArray < PType * > args ;
TArray < uint32_t > argflags ;
TArray < FName > argnames ;
2016-11-15 10:21:08 +00:00
// Functions that only get flagged for actors do not need the additional two context parameters.
int fflags = ( flags & ( SUF_OVERLAY | SUF_WEAPON | SUF_ITEM ) ) ? VARF_Action | VARF_Method : VARF_Method ;
2016-10-15 12:36:08 +00:00
rets [ 0 ] = returntype ! = nullptr ? returntype : TypeError ; // Use TypeError as placeholder if we do not know the return type yet.
2016-11-15 10:21:08 +00:00
SetImplicitArgs ( & args , & argflags , & argnames , containingclass , fflags , flags ) ;
2016-10-15 12:36:08 +00:00
2016-10-15 13:50:45 +00:00
PFunction * sym = new PFunction ( containingclass , NAME_None ) ; // anonymous functions do not have names.
2016-11-15 20:38:12 +00:00
sym - > AddVariant ( NewPrototype ( rets , args ) , argflags , argnames , nullptr , fflags , flags ) ;
2016-10-15 12:36:08 +00:00
return sym ;
}
2016-10-15 18:16:44 +00:00
//==========================================================================
//
// FindClassMemberFunction
//
// Looks for a name in a class's symbol table and outputs appropriate messages
//
//==========================================================================
2016-10-22 10:10:19 +00:00
PFunction * FindClassMemberFunction ( PClass * selfcls , PClass * funccls , FName name , FScriptPosition & sc , bool * error )
2016-10-15 18:16:44 +00:00
{
// Skip ACS_NamedExecuteWithResult. Anything calling this should use the builtin instead.
if ( name = = NAME_ACS_NamedExecuteWithResult ) return nullptr ;
PSymbolTable * symtable ;
auto symbol = selfcls - > Symbols . FindSymbolInTable ( name , symtable ) ;
auto funcsym = dyn_cast < PFunction > ( symbol ) ;
if ( symbol ! = nullptr )
{
if ( funcsym = = nullptr )
{
sc . Message ( MSG_ERROR , " %s is not a member function of %s " , name . GetChars ( ) , selfcls - > TypeName . GetChars ( ) ) ;
}
else if ( funcsym - > Variants [ 0 ] . Flags & VARF_Private & & symtable ! = & funccls - > Symbols )
{
// private access is only allowed if the symbol table belongs to the class in which the current function is being defined.
sc . Message ( MSG_ERROR , " %s is declared private and not accessible " , symbol - > SymbolName . GetChars ( ) ) ;
}
else if ( funcsym - > Variants [ 0 ] . Flags & VARF_Deprecated )
{
sc . Message ( MSG_WARNING , " Call to deprecated function %s " , symbol - > SymbolName . GetChars ( ) ) ;
}
}
// return nullptr if the name cannot be found in the symbol table so that the calling code can do other checks.
return funcsym ;
}
2016-10-26 12:04:49 +00:00
//==========================================================================
//
// CreateDamageFunction
//
// creates a damage function from the given expression
//
//==========================================================================
void CreateDamageFunction ( PClassActor * info , AActor * defaults , FxExpression * id , bool fromDecorate )
{
if ( id = = nullptr )
{
defaults - > DamageFunc = nullptr ;
}
else
{
auto dmg = new FxReturnStatement ( new FxIntCast ( id , true ) , id - > ScriptPosition ) ;
2016-11-15 10:21:08 +00:00
auto funcsym = CreateAnonymousFunction ( info , TypeSInt32 , 0 ) ;
2016-10-26 12:04:49 +00:00
defaults - > DamageFunc = FunctionBuildList . AddFunction ( funcsym , dmg , FStringf ( " %s.DamageFunction " , info - > TypeName . GetChars ( ) ) , fromDecorate ) ;
}
}
2016-11-13 11:02:41 +00:00
//==========================================================================
//
// CheckForUnsafeStates
//
// Performs a quick analysis to find potentially bad states.
// This is not perfect because it cannot track jumps by function.
// For such cases a runtime check in the relevant places is also present.
//
//==========================================================================
2016-11-15 10:21:08 +00:00
static void CheckForUnsafeStates ( PClassActor * obj )
2016-11-13 11:02:41 +00:00
{
static ENamedName weaponstates [ ] = { NAME_Ready , NAME_Deselect , NAME_Select , NAME_Fire , NAME_AltFire , NAME_Hold , NAME_AltHold , NAME_Flash , NAME_AltFlash , NAME_None } ;
static ENamedName pickupstates [ ] = { NAME_Pickup , NAME_Drop , NAME_Use , NAME_None } ;
TMap < FState * , bool > checked ;
ENamedName * test ;
if ( obj - > IsDescendantOf ( RUNTIME_CLASS ( AWeapon ) ) )
{
2016-11-15 10:21:08 +00:00
if ( obj - > Size = = RUNTIME_CLASS ( AWeapon ) - > Size ) return ; // This class cannot have user variables.
2016-11-13 11:02:41 +00:00
test = weaponstates ;
}
else if ( obj - > IsDescendantOf ( RUNTIME_CLASS ( ACustomInventory ) ) )
{
2016-11-15 10:21:08 +00:00
if ( obj - > Size = = RUNTIME_CLASS ( ACustomInventory ) - > Size ) return ; // This class cannot have user variables.
2016-11-13 11:02:41 +00:00
test = pickupstates ;
}
2016-11-15 10:21:08 +00:00
else return ; // something else derived from AStateProvider. We do not know what this may be.
2016-11-13 11:02:41 +00:00
for ( ; * test ! = NAME_None ; test + + )
{
FState * state = obj - > FindState ( * test ) ;
while ( state ! = nullptr & & checked . CheckKey ( state ) = = nullptr ) // have we checked this state already. If yes, we can stop checking the current chain.
{
checked [ state ] = true ;
if ( state - > ActionFunc & & state - > ActionFunc - > Unsafe )
{
// If an unsafe function (i.e. one that accesses user variables) is being detected, print a warning once and remove the bogus function. We may not call it because that would inevitably crash.
auto owner = FState : : StaticFindStateOwner ( state ) ;
2016-11-15 10:21:08 +00:00
GetStateSource ( state ) . Message ( MSG_ERROR , TEXTCOLOR_RED " Unsafe state call in state %s.%d which accesses user variables, reached by %s.%s. \n " ,
owner - > TypeName . GetChars ( ) , state - owner - > OwnedStates , obj - > TypeName . GetChars ( ) , FName ( * test ) . GetChars ( ) ) ;
2016-11-13 11:02:41 +00:00
}
state = state - > NextState ;
}
}
2016-11-15 10:21:08 +00:00
}
//==========================================================================
//
// CheckStates
//
// Checks if states link to ones with proper restrictions
// Checks that all base labels refer a string with proper restrictions.
// For these cases a runtime check in the relevant places is also present.
//
//==========================================================================
static void CheckLabel ( PClassActor * obj , FStateLabel * slb , int useflag , FName statename , const char * descript )
{
auto state = slb - > State ;
if ( state ! = nullptr )
{
if ( ! ( state - > UseFlags & useflag ) )
{
auto owner = FState : : StaticFindStateOwner ( state ) ;
GetStateSource ( state ) . Message ( MSG_ERROR , TEXTCOLOR_RED " %s references state %s.%d as %s state, but this state is not flagged for use as %s. \n " ,
obj - > TypeName . GetChars ( ) , owner - > TypeName . GetChars ( ) , int ( state - owner - > OwnedStates ) , statename . GetChars ( ) , descript ) ;
}
}
if ( slb - > Children ! = nullptr )
{
for ( int i = 0 ; i < slb - > Children - > NumLabels ; i + + )
{
auto state = slb - > Children - > Labels [ i ] . State ;
CheckLabel ( obj , & slb - > Children - > Labels [ i ] , useflag , statename , descript ) ;
}
}
}
static void CheckStateLabels ( PClassActor * obj , ENamedName * test , int useflag , const char * descript )
{
FStateLabels * labels = obj - > StateList ;
for ( ; * test ! = NAME_None ; test + + )
{
auto label = labels - > FindLabel ( * test ) ;
if ( label ! = nullptr )
{
CheckLabel ( obj , label , useflag , * test , descript ) ;
}
}
}
static void CheckStates ( PClassActor * obj )
{
static ENamedName actorstates [ ] = { NAME_Spawn , NAME_See , NAME_Melee , NAME_Missile , NAME_Pain , NAME_Death , NAME_Wound , NAME_Raise , NAME_Yes , NAME_No , NAME_Greetings , NAME_None } ;
static ENamedName weaponstates [ ] = { NAME_Ready , NAME_Deselect , NAME_Select , NAME_Fire , NAME_AltFire , NAME_Hold , NAME_AltHold , NAME_Flash , NAME_AltFlash , NAME_None } ;
static ENamedName pickupstates [ ] = { NAME_Pickup , NAME_Drop , NAME_Use , NAME_None } ;
TMap < FState * , bool > checked ;
CheckStateLabels ( obj , actorstates , SUF_ACTOR , " actor sprites " ) ;
if ( obj - > IsDescendantOf ( RUNTIME_CLASS ( AWeapon ) ) )
{
CheckStateLabels ( obj , weaponstates , SUF_WEAPON , " weapon sprites " ) ;
}
else if ( obj - > IsDescendantOf ( RUNTIME_CLASS ( ACustomInventory ) ) )
{
CheckStateLabels ( obj , pickupstates , SUF_ITEM , " CustomInventory state chain " ) ;
}
for ( int i = 0 ; i < obj - > NumOwnedStates ; i + + )
{
auto state = obj - > OwnedStates + i ;
if ( state - > NextState & & ( state - > UseFlags & state - > NextState - > UseFlags ) ! = state - > UseFlags )
{
GetStateSource ( state ) . Message ( MSG_ERROR , TEXTCOLOR_RED " State %s.%d links to a state with incompatible restrictions. \n " ,
obj - > TypeName . GetChars ( ) , int ( state - obj - > OwnedStates ) ) ;
}
}
2016-11-13 11:02:41 +00:00
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// LoadActors
//
// Called from FActor::StaticInit()
//
//==========================================================================
2016-10-07 23:05:27 +00:00
void ParseScripts ( ) ;
2016-10-12 22:53:59 +00:00
void ParseAllDecorate ( ) ;
2016-03-01 15:47:10 +00:00
void LoadActors ( )
{
cycle_t timer ;
timer . Reset ( ) ; timer . Clock ( ) ;
2016-10-12 22:53:59 +00:00
FScriptPosition : : ResetErrorCounter ( ) ;
2016-03-01 15:47:10 +00:00
InitThingdef ( ) ;
2016-10-26 11:22:36 +00:00
FScriptPosition : : StrictErrors = true ;
2016-10-07 23:05:27 +00:00
ParseScripts ( ) ;
2016-10-15 10:15:25 +00:00
2016-10-26 11:22:36 +00:00
FScriptPosition : : StrictErrors = false ;
2016-10-12 22:53:59 +00:00
ParseAllDecorate ( ) ;
FunctionBuildList . Build ( ) ;
2016-10-12 18:42:41 +00:00
2016-03-01 15:47:10 +00:00
if ( FScriptPosition : : ErrorCounter > 0 )
{
I_Error ( " %d errors while parsing DECORATE scripts " , FScriptPosition : : ErrorCounter ) ;
}
2016-11-15 10:21:08 +00:00
FScriptPosition : : ResetErrorCounter ( ) ;
2016-10-12 22:53:59 +00:00
for ( auto ti : PClassActor : : AllActorClasses )
{
if ( ti - > Size = = TentativeClass )
{
Printf ( TEXTCOLOR_RED " Class %s referenced but not defined \n " , ti - > TypeName . GetChars ( ) ) ;
2016-11-15 10:21:08 +00:00
FScriptPosition : : ErrorCounter + + ;
2016-10-12 22:53:59 +00:00
continue ;
}
if ( GetDefaultByType ( ti ) = = nullptr )
{
Printf ( TEXTCOLOR_RED " No ActorInfo defined for class '%s' \n " , ti - > TypeName . GetChars ( ) ) ;
2016-11-15 10:21:08 +00:00
FScriptPosition : : ErrorCounter + + ;
2016-10-12 22:53:59 +00:00
continue ;
}
2016-11-13 11:02:41 +00:00
2016-11-15 10:21:08 +00:00
CheckStates ( ti ) ;
2016-11-13 11:02:41 +00:00
if ( ti - > bDecorateClass & & ti - > IsDescendantOf ( RUNTIME_CLASS ( AStateProvider ) ) )
{
// either a DECORATE based weapon or CustomInventory.
// These are subject to relaxed rules for user variables in states.
// Although there is a runtime check for bogus states, let's do a quick analysis if any of the known entry points
// hits an unsafe state. If we can find something here it can be handled wuth a compile error rather than a runtime error.
2016-11-15 10:21:08 +00:00
CheckForUnsafeStates ( ti ) ;
2016-11-13 11:02:41 +00:00
}
2016-11-15 10:21:08 +00:00
2016-10-12 22:53:59 +00:00
}
2016-11-15 10:21:08 +00:00
if ( FScriptPosition : : ErrorCounter > 0 )
2016-10-12 22:53:59 +00:00
{
2016-11-15 10:21:08 +00:00
I_Error ( " %d errors during actor postprocessing " , FScriptPosition : : ErrorCounter ) ;
2016-10-12 22:53:59 +00:00
}
2016-03-01 15:47:10 +00:00
timer . Unclock ( ) ;
2016-11-15 10:21:08 +00:00
if ( ! batchrun ) Printf ( " script parsing took %.2f ms \n " , timer . TimeMS ( ) ) ;
2016-10-12 18:42:41 +00:00
// Since these are defined in DECORATE now the table has to be initialized here.
for ( int i = 0 ; i < 31 ; i + + )
{
char fmt [ 20 ] ;
mysnprintf ( fmt , countof ( fmt ) , " QuestItem%d " , i + 1 ) ;
QuestItemClasses [ i ] = PClass : : FindActor ( fmt ) ;
}
2016-11-15 10:21:08 +00:00
StateSourceLines . Clear ( ) ;
2016-03-01 15:47:10 +00:00
}