2016-03-01 15:47:10 +00:00
/*
* * info . cpp
* * Keeps track of available actors and their states
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 1998 - 2006 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 .
* *
* * 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 .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
* * This is completely different from Doom ' s info . c .
* *
*/
# include "doomstat.h"
# include "info.h"
# include "c_dispatch.h"
# include "d_net.h"
# include "v_text.h"
# include "gi.h"
# include "actor.h"
# include "r_state.h"
# include "p_local.h"
# include "stats.h"
2016-10-12 18:42:41 +00:00
# include "thingdef.h"
2016-03-01 15:47:10 +00:00
# include "d_player.h"
2017-01-22 07:58:48 +00:00
# include "events.h"
2017-04-12 23:12:04 +00:00
# include "types.h"
2018-12-02 13:03:03 +00:00
# include "w_wad.h"
# include "g_levellocals.h"
2016-03-01 15:47:10 +00:00
extern void LoadActors ( ) ;
extern void InitBotStuff ( ) ;
extern void ClearStrifeTypes ( ) ;
TArray < PClassActor * > PClassActor : : AllActorClasses ;
FRandom FState : : pr_statetics ( " StateTics " ) ;
cycle_t ActionCycles ;
2017-04-11 17:37:56 +00:00
//==========================================================================
//
// special type for the native ActorInfo. This allows to let this struct
// be handled by the generic object constructors for the VM.
//
//==========================================================================
2017-04-13 10:47:41 +00:00
class PActorInfo : public PCompoundType
2017-04-11 17:37:56 +00:00
{
public :
PActorInfo ( )
2017-04-13 10:47:41 +00:00
: PCompoundType ( sizeof ( FActorInfo ) , alignof ( FActorInfo ) )
2017-04-11 17:37:56 +00:00
{
}
2017-04-12 12:40:29 +00:00
void SetDefaultValue ( void * base , unsigned offset , TArray < FTypeAndOffset > * special ) override
2017-04-11 17:37:56 +00:00
{
if ( base ! = nullptr ) new ( ( uint8_t * ) base + offset ) FActorInfo ;
if ( special ! = nullptr )
{
special - > Push ( std : : make_pair ( this , offset ) ) ;
}
}
void InitializeValue ( void * addr , const void * def ) const override
{
if ( def = = nullptr )
{
new ( addr ) FActorInfo ;
}
else
{
new ( addr ) FActorInfo ( * ( const FActorInfo * ) def ) ;
}
}
void DestroyValue ( void * addr ) const override
{
FActorInfo * self = ( FActorInfo * ) addr ;
self - > ~ FActorInfo ( ) ;
}
} ;
void AddActorInfo ( PClass * cls )
{
auto type = new PActorInfo ;
2017-04-13 13:44:51 +00:00
TypeTable . AddType ( type , NAME_Actor ) ;
2017-04-11 17:37:56 +00:00
cls - > AddField ( " * " , type , VARF_Meta ) ;
}
2016-03-01 15:47:10 +00:00
void FState : : SetAction ( const char * name )
{
2016-11-06 10:36:12 +00:00
ActionFunc = FindVMFunction ( RUNTIME_CLASS ( AActor ) , name ) ;
2016-03-01 15:47:10 +00:00
}
2018-11-15 08:03:54 +00:00
2018-11-15 08:24:17 +00:00
void FState : : CheckCallerType ( AActor * self , AActor * stateowner )
{
auto CheckType = [ = ] ( AActor * check , PType * requiredType )
{
// This should really never happen. Any valid action function must have actor pointers here.
if ( ! requiredType - > isObjectPointer ( ) )
{
ThrowAbortException ( X_OTHER , " Bad function prototype in function call to %s " , ActionFunc - > PrintableName . GetChars ( ) ) ;
}
auto cls = static_cast < PObjectPointer * > ( requiredType ) - > PointedClass ( ) ;
2018-12-03 17:51:24 +00:00
if ( check = = nullptr )
{
ThrowAbortException ( X_OTHER , " %s called without valid caller. %s expected " , ActionFunc - > PrintableName . GetChars ( ) , cls - > TypeName . GetChars ( ) ) ;
}
2019-01-12 10:21:34 +00:00
if ( ! ( StateFlags & STF_DEHACKED ) & & ! check - > IsKindOf ( cls ) )
2018-11-15 08:24:17 +00:00
{
ThrowAbortException ( X_OTHER , " Invalid class %s in function call to %s. %s expected " , check - > GetClass ( ) - > TypeName . GetChars ( ) , ActionFunc - > PrintableName . GetChars ( ) , cls - > TypeName . GetChars ( ) ) ;
}
} ;
if ( ActionFunc - > ImplicitArgs > = 1 )
{
auto argtypes = ActionFunc - > Proto - > ArgumentTypes ;
CheckType ( self , argtypes [ 0 ] ) ;
if ( ActionFunc - > ImplicitArgs > = 2 )
{
CheckType ( stateowner , argtypes [ 1 ] ) ;
}
}
}
2018-11-16 19:38:52 +00:00
TArray < VMValue > actionParams ;
2018-11-15 08:24:17 +00:00
2016-06-16 14:11:00 +00:00
bool FState : : CallAction ( AActor * self , AActor * stateowner , FStateParamInfo * info , FState * * stateret )
2016-03-01 15:47:10 +00:00
{
2017-04-12 23:12:04 +00:00
if ( ActionFunc ! = nullptr )
2016-03-01 15:47:10 +00:00
{
ActionCycles . Clock ( ) ;
// If the function returns a state, store it at *stateret.
2017-04-12 23:12:04 +00:00
// If it doesn't return a state but stateret is non-nullptr, we need
// to set *stateret to nullptr.
if ( stateret ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2017-04-12 23:12:04 +00:00
* stateret = nullptr ;
if ( ActionFunc - > Proto = = nullptr | |
2016-03-01 15:47:10 +00:00
ActionFunc - > Proto - > ReturnTypes . Size ( ) = = 0 | |
ActionFunc - > Proto - > ReturnTypes [ 0 ] ! = TypeState )
{
2017-04-12 23:12:04 +00:00
stateret = nullptr ;
2016-03-01 15:47:10 +00:00
}
}
2018-11-16 19:38:52 +00:00
VMReturn ret ;
ret . PointerAt ( ( void * * ) stateret ) ;
2016-12-03 11:23:13 +00:00
try
2016-03-01 15:47:10 +00:00
{
2018-11-15 08:24:17 +00:00
CheckCallerType ( self , stateowner ) ;
2018-11-16 19:38:52 +00:00
// Build the parameter array. Action functions have never any explicit parameters but need to pass the defaults
// and fill in the implicit arguments of the called function.
if ( ActionFunc - > DefaultArgs . Size ( ) > 0 )
2016-12-03 11:23:13 +00:00
{
2018-11-18 18:31:13 +00:00
auto defs = ActionFunc - > DefaultArgs ;
auto index = actionParams . Reserve ( defs . Size ( ) ) ;
for ( unsigned i = 0 ; i < defs . Size ( ) ; i + + )
{
actionParams [ i + index ] = defs [ i ] ;
}
2018-11-16 19:38:52 +00:00
if ( ActionFunc - > ImplicitArgs > = 1 )
{
actionParams [ index ] = self ;
}
if ( ActionFunc - > ImplicitArgs = = 3 )
{
actionParams [ index + 1 ] = stateowner ;
actionParams [ index + 2 ] = VMValue ( info ) ;
}
VMCallAction ( ActionFunc , & actionParams [ index ] , ActionFunc - > DefaultArgs . Size ( ) , & ret , stateret ! = nullptr ) ;
actionParams . Clamp ( index ) ;
2016-12-03 11:23:13 +00:00
}
else
{
2018-11-16 19:38:52 +00:00
VMValue params [ 3 ] = { self , stateowner , VMValue ( info ) } ;
VMCallAction ( ActionFunc , params , ActionFunc - > ImplicitArgs , & ret , stateret ! = nullptr ) ;
2016-12-03 11:23:13 +00:00
}
2016-03-01 15:47:10 +00:00
}
2016-12-03 11:23:13 +00:00
catch ( CVMAbortException & err )
2016-03-01 15:47:10 +00:00
{
2016-12-03 11:23:13 +00:00
err . MaybePrintMessage ( ) ;
2018-07-19 10:38:49 +00:00
if ( stateowner ! = nullptr )
2016-12-03 11:23:13 +00:00
{
2018-07-19 10:38:49 +00:00
const char * callinfo = " " ;
if ( info ! = nullptr & & info - > mStateType = = STATE_Psprite )
{
if ( stateowner - > IsKindOf ( NAME_Weapon ) & & stateowner ! = self ) callinfo = " weapon " ;
else callinfo = " overlay " ;
}
err . stacktrace . AppendFormat ( " Called from %sstate %s in %s \n " , callinfo , FState : : StaticGetStateName ( this ) . GetChars ( ) , stateowner - > GetClass ( ) - > TypeName . GetChars ( ) ) ;
2016-12-03 11:23:13 +00:00
}
2018-12-03 17:51:24 +00:00
else
{
err . stacktrace . AppendFormat ( " Called from state %s \n " , FState : : StaticGetStateName ( this ) . GetChars ( ) ) ;
}
2018-07-19 10:38:49 +00:00
2016-12-03 11:23:13 +00:00
throw ;
2016-03-01 15:47:10 +00:00
}
2016-12-03 11:23:13 +00:00
2016-03-01 15:47:10 +00:00
ActionCycles . Unclock ( ) ;
return true ;
}
else
{
return false ;
}
}
//==========================================================================
//
//
//==========================================================================
int GetSpriteIndex ( const char * spritename , bool add )
{
static char lastsprite [ 5 ] ;
static int lastindex ;
// Make sure that the string is upper case and 4 characters long
char upper [ 5 ] = { 0 , 0 , 0 , 0 , 0 } ;
for ( int i = 0 ; spritename [ i ] ! = 0 & & i < 4 ; i + + )
{
upper [ i ] = toupper ( spritename [ i ] ) ;
}
// cache the name so if the next one is the same the function doesn't have to perform a search.
if ( ! strcmp ( upper , lastsprite ) )
{
return lastindex ;
}
strcpy ( lastsprite , upper ) ;
for ( unsigned i = 0 ; i < sprites . Size ( ) ; + + i )
{
if ( strcmp ( sprites [ i ] . name , upper ) = = 0 )
{
return ( lastindex = ( int ) i ) ;
}
}
if ( ! add )
{
return ( lastindex = - 1 ) ;
}
spritedef_t temp ;
strcpy ( temp . name , upper ) ;
temp . numframes = 0 ;
temp . spriteframes = 0 ;
return ( lastindex = ( int ) sprites . Push ( temp ) ) ;
}
2018-12-02 13:03:03 +00:00
//==========================================================================
//
// Load alt HUD icons. This is meant to be an override of the item's own settings.
//
//==========================================================================
static void LoadAltHudStuff ( )
{
// Now read custom icon overrides
int lump , lastlump = 0 ;
switch ( gameinfo . gametype )
{
case GAME_Heretic :
case GAME_Hexen :
gameinfo . healthpic = TexMan . CheckForTexture ( " ARTIPTN2 " , ETextureType : : MiscPatch ) . GetIndex ( ) ;
gameinfo . berserkpic = - 1 ;
break ;
case GAME_Strife :
gameinfo . healthpic = TexMan . CheckForTexture ( " I_MDKT " , ETextureType : : MiscPatch ) . GetIndex ( ) ;
gameinfo . berserkpic = - 1 ;
break ;
default :
gameinfo . healthpic = TexMan . CheckForTexture ( " MEDIA0 " , ETextureType : : Sprite ) . GetIndex ( ) ;
gameinfo . berserkpic = TexMan . CheckForTexture ( " PSTRA0 " , ETextureType : : Sprite ) . GetIndex ( ) ;
break ;
}
while ( ( lump = Wads . FindLump ( " ALTHUDCF " , & lastlump ) ) ! = - 1 )
{
FScanner sc ( lump ) ;
while ( sc . GetString ( ) )
{
if ( sc . Compare ( " Health " ) )
{
sc . MustGetString ( ) ;
FTextureID tex = TexMan . CheckForTexture ( sc . String , ETextureType : : MiscPatch ) ;
if ( tex . isValid ( ) ) gameinfo . healthpic = tex . GetIndex ( ) ;
}
else if ( sc . Compare ( " Berserk " ) )
{
sc . MustGetString ( ) ;
FTextureID tex = TexMan . CheckForTexture ( sc . String , ETextureType : : MiscPatch ) ;
if ( tex . isValid ( ) ) gameinfo . berserkpic = tex . GetIndex ( ) ;
}
else
{
PClass * ti = PClass : : FindClass ( sc . String ) ;
if ( ! ti )
{
Printf ( " Unknown item class '%s' in ALTHUDCF \n " , sc . String ) ;
}
2018-12-02 20:35:04 +00:00
else if ( ! ti - > IsDescendantOf ( NAME_Inventory ) )
2018-12-02 13:03:03 +00:00
{
Printf ( " Invalid item class '%s' in ALTHUDCF \n " , sc . String ) ;
ti = NULL ;
}
sc . MustGetString ( ) ;
FTextureID tex ;
if ( ! sc . Compare ( " 0 " ) & & ! sc . Compare ( " NULL " ) & & ! sc . Compare ( " " ) )
{
tex = TexMan . CheckForTexture ( sc . String , ETextureType : : MiscPatch ) ;
}
else tex . SetInvalid ( ) ;
2018-12-03 23:22:26 +00:00
if ( ti ) GetDefaultByType ( ti ) - > TextureIDVar ( NAME_AltHUDIcon ) = tex ;
2018-12-02 13:03:03 +00:00
}
}
}
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// PClassActor :: StaticInit STATIC
//
//==========================================================================
void PClassActor : : StaticInit ( )
{
sprites . Clear ( ) ;
if ( sprites . Size ( ) = = 0 )
{
spritedef_t temp ;
// Sprite 0 is always TNT1
memcpy ( temp . name , " TNT1 " , 5 ) ;
temp . numframes = 0 ;
temp . spriteframes = 0 ;
sprites . Push ( temp ) ;
// Sprite 1 is always ----
memcpy ( temp . name , " ---- " , 5 ) ;
sprites . Push ( temp ) ;
// Sprite 2 is always ####
memcpy ( temp . name , " #### " , 5 ) ;
sprites . Push ( temp ) ;
}
if ( ! batchrun ) Printf ( " LoadActors: Load actor definitions. \n " ) ;
ClearStrifeTypes ( ) ;
LoadActors ( ) ;
2018-12-02 13:03:03 +00:00
LoadAltHudStuff ( ) ;
2016-03-01 15:47:10 +00:00
InitBotStuff ( ) ;
2017-01-22 07:58:48 +00:00
// reinit GLOBAL static stuff from gameinfo, once classes are loaded.
2019-02-06 14:33:19 +00:00
staticEventManager . InitStaticHandlers ( primaryLevel , false ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// PClassActor :: StaticSetActorNums STATIC
//
// Called after Dehacked patches are applied
//
//==========================================================================
void PClassActor : : StaticSetActorNums ( )
{
for ( unsigned int i = 0 ; i < PClassActor : : AllActorClasses . Size ( ) ; + + i )
{
2017-04-12 08:29:04 +00:00
PClassActor : : AllActorClasses [ i ] - > RegisterIDs ( ) ;
2016-03-01 15:47:10 +00:00
}
}
2016-10-12 18:42:41 +00:00
//==========================================================================
//
// PClassActor :: SetReplacement
//
// Sets as a replacement class for another class.
//
//==========================================================================
bool PClassActor : : SetReplacement ( FName replaceName )
{
// Check for "replaces"
if ( replaceName ! = NAME_None )
{
// Get actor name
PClassActor * replacee = PClass : : FindActor ( replaceName ) ;
if ( replacee = = nullptr )
{
return false ;
}
if ( replacee ! = nullptr )
{
2017-04-11 17:37:56 +00:00
replacee - > ActorInfo ( ) - > Replacement = this ;
ActorInfo ( ) - > Replacee = replacee ;
2016-10-12 18:42:41 +00:00
}
}
return true ;
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// PClassActor :: RegisterIDs
//
// Registers this class's SpawnID and DoomEdNum in the appropriate tables.
//
//==========================================================================
void PClassActor : : RegisterIDs ( )
{
PClassActor * cls = PClass : : FindActor ( TypeName ) ;
2017-04-12 23:12:04 +00:00
if ( cls = = nullptr )
2016-03-01 15:47:10 +00:00
{
Printf ( TEXTCOLOR_RED " The actor '%s' has been hidden by a non-actor of the same name \n " , TypeName . GetChars ( ) ) ;
return ;
}
2017-10-07 13:30:49 +00:00
FActorInfo * actorInfo = ActorInfo ( ) ;
if ( nullptr = = actorInfo )
{
// Undefined class, exiting
return ;
}
2016-03-01 15:47:10 +00:00
// Conversation IDs have never been filtered by game so we cannot start doing that.
2017-10-07 13:30:49 +00:00
auto ConversationID = actorInfo - > ConversationID ;
2016-03-01 15:47:10 +00:00
if ( ConversationID > 0 )
{
StrifeTypes [ ConversationID ] = cls ;
if ( cls ! = this )
{
2017-04-11 21:29:37 +00:00
Printf ( TEXTCOLOR_RED " Conversation ID %d refers to hidden class type '%s' \n " , ConversationID , cls - > TypeName . GetChars ( ) ) ;
2016-03-01 15:47:10 +00:00
}
}
2017-10-07 13:30:49 +00:00
if ( actorInfo - > GameFilter = = GAME_Any | | ( ActorInfo ( ) - > GameFilter & gameinfo . gametype ) )
2016-03-01 15:47:10 +00:00
{
2017-10-07 13:30:49 +00:00
auto SpawnID = actorInfo - > SpawnID ;
2016-03-01 15:47:10 +00:00
if ( SpawnID > 0 )
{
SpawnableThings [ SpawnID ] = cls ;
if ( cls ! = this )
{
Printf ( TEXTCOLOR_RED " Spawn ID %d refers to hidden class type '%s' \n " , SpawnID , cls - > TypeName . GetChars ( ) ) ;
}
}
2017-10-07 13:30:49 +00:00
auto DoomEdNum = actorInfo - > DoomEdNum ;
2016-03-01 15:47:10 +00:00
if ( DoomEdNum ! = - 1 )
{
FDoomEdEntry * oldent = DoomEdMap . CheckKey ( DoomEdNum ) ;
2017-04-12 23:12:04 +00:00
if ( oldent ! = nullptr & & oldent - > Special = = - 2 )
2016-03-01 15:47:10 +00:00
{
Printf ( TEXTCOLOR_RED " Editor number %d defined twice for classes '%s' and '%s' \n " , DoomEdNum , cls - > TypeName . GetChars ( ) , oldent - > Type - > TypeName . GetChars ( ) ) ;
}
FDoomEdEntry ent ;
memset ( & ent , 0 , sizeof ( ent ) ) ;
ent . Type = cls ;
ent . Special = - 2 ; // use -2 instead of -1 so that we can recognize DECORATE defined entries and print a warning message if duplicates occur.
DoomEdMap . Insert ( DoomEdNum , ent ) ;
if ( cls ! = this )
{
Printf ( TEXTCOLOR_RED " Editor number %d refers to hidden class type '%s' \n " , DoomEdNum , cls - > TypeName . GetChars ( ) ) ;
}
}
}
}
//==========================================================================
//
// PClassActor :: GetReplacement
//
//==========================================================================
2019-02-02 15:43:11 +00:00
PClassActor * PClassActor : : GetReplacement ( FLevelLocals * Level , bool lookskill )
2016-03-01 15:47:10 +00:00
{
2018-08-18 23:14:15 +00:00
FName skillrepname = NAME_None ;
2016-03-01 15:47:10 +00:00
if ( lookskill & & AllSkills . Size ( ) > ( unsigned ) gameskill )
{
skillrepname = AllSkills [ gameskill ] . GetReplacement ( TypeName ) ;
2017-04-12 23:12:04 +00:00
if ( skillrepname ! = NAME_None & & PClass : : FindClass ( skillrepname ) = = nullptr )
2016-03-01 15:47:10 +00:00
{
Printf ( " Warning: incorrect actor name in definition of skill %s: \n "
" class %s is replaced by non-existent class %s \n "
" Skill replacement will be ignored for this actor. \n " ,
AllSkills [ gameskill ] . Name . GetChars ( ) ,
TypeName . GetChars ( ) , skillrepname . GetChars ( ) ) ;
AllSkills [ gameskill ] . SetReplacement ( TypeName , NAME_None ) ;
AllSkills [ gameskill ] . SetReplacedBy ( skillrepname , NAME_None ) ;
lookskill = false ; skillrepname = NAME_None ;
}
}
2019-02-02 16:29:13 +00:00
// [MK] ZScript replacement through Event Handlers, has priority over others.
2018-08-15 15:46:03 +00:00
PClassActor * Replacement = ActorInfo ( ) - > Replacement ;
2019-02-02 16:29:13 +00:00
// Level can be null when initializing dynamic lights.
// Since they only want to check for DehackedPickups this code should be skipped for them.
if ( Level & & Level - > localEventManager - > CheckReplacement ( this , & Replacement ) )
2018-08-16 18:46:40 +00:00
{
// [MK] the replacement is final, so don't continue with the chain
return Replacement ? Replacement : this ;
}
2017-04-11 17:37:56 +00:00
if ( Replacement = = nullptr & & ( ! lookskill | | skillrepname = = NAME_None ) )
2016-03-01 15:47:10 +00:00
{
return this ;
}
// The Replacement field is temporarily NULLed to prevent
// potential infinite recursion.
2018-09-23 21:34:20 +00:00
PClassActor * oldrep = ActorInfo ( ) - > Replacement ;
2017-04-11 17:37:56 +00:00
ActorInfo ( ) - > Replacement = nullptr ;
PClassActor * rep = Replacement ;
2016-03-01 15:47:10 +00:00
// Handle skill-based replacement here. It has precedence on DECORATE replacement
// in that the skill replacement is applied first, followed by DECORATE replacement
// on the actor indicated by the skill replacement.
if ( lookskill & & ( skillrepname ! = NAME_None ) )
{
rep = PClass : : FindActor ( skillrepname ) ;
}
// Now handle DECORATE replacement chain
// Skill replacements are not recursive, contrarily to DECORATE replacements
2019-02-02 16:29:13 +00:00
rep = rep - > GetReplacement ( Level , false ) ;
2016-03-01 15:47:10 +00:00
// Reset the temporarily NULLed field
2018-09-23 21:34:20 +00:00
ActorInfo ( ) - > Replacement = oldrep ;
2016-03-01 15:47:10 +00:00
return rep ;
}
//==========================================================================
//
// PClassActor :: GetReplacee
//
//==========================================================================
2019-02-02 15:43:11 +00:00
PClassActor * PClassActor : : GetReplacee ( FLevelLocals * Level , bool lookskill )
2016-03-01 15:47:10 +00:00
{
2018-08-18 23:14:15 +00:00
FName skillrepname = NAME_None ;
2016-03-01 15:47:10 +00:00
if ( lookskill & & AllSkills . Size ( ) > ( unsigned ) gameskill )
{
skillrepname = AllSkills [ gameskill ] . GetReplacedBy ( TypeName ) ;
2017-04-12 23:12:04 +00:00
if ( skillrepname ! = NAME_None & & PClass : : FindClass ( skillrepname ) = = nullptr )
2016-03-01 15:47:10 +00:00
{
Printf ( " Warning: incorrect actor name in definition of skill %s: \n "
" non-existent class %s is replaced by class %s \n "
" Skill replacement will be ignored for this actor. \n " ,
AllSkills [ gameskill ] . Name . GetChars ( ) ,
skillrepname . GetChars ( ) , TypeName . GetChars ( ) ) ;
AllSkills [ gameskill ] . SetReplacedBy ( TypeName , NAME_None ) ;
AllSkills [ gameskill ] . SetReplacement ( skillrepname , NAME_None ) ;
lookskill = false ;
}
}
2017-04-11 17:37:56 +00:00
PClassActor * savedrep = ActorInfo ( ) - > Replacee ;
2019-01-28 00:03:04 +00:00
// [MC] Same code as CheckReplacement but turned around so modders can indicate
// what monsters spawn from which entity. I.e. instead of a randomspawner
// showing up, one can assign an Arachnotron as the one being replaced
// so functions like CheckReplacee and A_BossDeath can actually work, given
// modders set it up that way.
2019-02-02 15:43:11 +00:00
if ( Level - > localEventManager - > CheckReplacee ( & savedrep , this ) )
2019-01-28 00:03:04 +00:00
{
// [MK] the replacement is final, so don't continue with the chain
return savedrep ? savedrep : this ;
}
2017-04-11 17:37:56 +00:00
if ( savedrep = = nullptr & & ( ! lookskill | | skillrepname = = NAME_None ) )
2016-03-01 15:47:10 +00:00
{
return this ;
}
// The Replacee field is temporarily NULLed to prevent
// potential infinite recursion.
2017-04-11 17:37:56 +00:00
ActorInfo ( ) - > Replacee = nullptr ;
2016-03-01 15:47:10 +00:00
PClassActor * rep = savedrep ;
2017-04-12 23:12:04 +00:00
if ( lookskill & & ( skillrepname ! = NAME_None ) & & ( PClass : : FindClass ( skillrepname ) ! = nullptr ) )
2016-03-01 15:47:10 +00:00
{
rep = PClass : : FindActor ( skillrepname ) ;
}
2019-02-02 16:29:13 +00:00
rep = rep - > GetReplacee ( Level , false ) ;
2017-04-11 17:37:56 +00:00
ActorInfo ( ) - > Replacee = savedrep ;
2016-03-01 15:47:10 +00:00
return rep ;
}
//==========================================================================
//
// PClassActor :: SetDamageFactor
//
//==========================================================================
2016-03-22 15:35:41 +00:00
void PClassActor : : SetDamageFactor ( FName type , double factor )
2016-03-01 15:47:10 +00:00
{
2017-04-11 22:07:41 +00:00
for ( auto & p : ActorInfo ( ) - > DamageFactors )
2016-03-01 15:47:10 +00:00
{
2017-04-15 09:33:26 +00:00
if ( p . first = = type )
{
p . second = factor ;
return ;
}
2016-03-01 15:47:10 +00:00
}
2017-04-11 22:07:41 +00:00
ActorInfo ( ) - > DamageFactors . Push ( { type , factor } ) ;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// PClassActor :: SetPainChance
//
//==========================================================================
void PClassActor : : SetPainChance ( FName type , int chance )
{
2017-04-11 22:07:41 +00:00
for ( auto & p : ActorInfo ( ) - > PainChances )
2016-03-01 15:47:10 +00:00
{
2017-04-20 19:09:18 +00:00
if ( p . first = = type )
{
p . second = chance ;
return ;
}
2016-03-01 15:47:10 +00:00
}
2017-04-11 21:29:37 +00:00
if ( chance > = 0 )
2016-03-01 15:47:10 +00:00
{
2017-04-11 22:07:41 +00:00
ActorInfo ( ) - > PainChances . Push ( { type , MIN ( chance , 256 ) } ) ;
2016-03-01 15:47:10 +00:00
}
}
//==========================================================================
//
// DmgFactors :: CheckFactor
//
// Checks for the existance of a certain damage type. If that type does not
// exist, the damage factor for type 'None' will be returned, if present.
//
//==========================================================================
2016-03-22 15:35:41 +00:00
int DmgFactors : : Apply ( FName type , int damage )
2016-03-01 15:47:10 +00:00
{
2017-04-11 21:29:37 +00:00
double factor = - 1. ;
for ( auto & p : * this )
2016-03-01 15:47:10 +00:00
{
2017-04-11 21:29:37 +00:00
if ( p . first = = type )
{
factor = p . second ;
break ;
}
if ( p . first = = NAME_None )
{
factor = p . second ;
}
2016-03-01 15:47:10 +00:00
}
2017-04-11 21:29:37 +00:00
if ( factor < 0. ) return damage ;
return int ( damage * factor ) ;
2016-03-01 15:47:10 +00:00
}
2016-03-22 15:35:41 +00:00
2016-03-01 15:47:10 +00:00
static void SummonActor ( int command , int command2 , FCommandLine argv )
{
if ( CheckCheatmode ( ) )
return ;
if ( argv . argc ( ) > 1 )
{
PClassActor * type = PClass : : FindActor ( argv [ 1 ] ) ;
2017-04-12 23:12:04 +00:00
if ( type = = nullptr )
2016-03-01 15:47:10 +00:00
{
Printf ( " Unknown actor '%s' \n " , argv [ 1 ] ) ;
return ;
}
2019-12-12 01:13:19 +00:00
if ( type - > bAbstract )
{
Printf ( " Cannot instantiate abstract class %s \n " , argv [ 1 ] ) ;
return ;
}
2016-03-01 15:47:10 +00:00
Net_WriteByte ( argv . argc ( ) > 2 ? command2 : command ) ;
Net_WriteString ( type - > TypeName . GetChars ( ) ) ;
if ( argv . argc ( ) > 2 )
{
Net_WriteWord ( atoi ( argv [ 2 ] ) ) ; // angle
Net_WriteWord ( ( argv . argc ( ) > 3 ) ? atoi ( argv [ 3 ] ) : 0 ) ; // TID
Net_WriteByte ( ( argv . argc ( ) > 4 ) ? atoi ( argv [ 4 ] ) : 0 ) ; // special
for ( int i = 5 ; i < 10 ; i + + )
{ // args[5]
Net_WriteLong ( ( i < argv . argc ( ) ) ? atoi ( argv [ i ] ) : 0 ) ;
}
}
}
}
CCMD ( summon )
{
SummonActor ( DEM_SUMMON , DEM_SUMMON2 , argv ) ;
}
CCMD ( summonfriend )
{
SummonActor ( DEM_SUMMONFRIEND , DEM_SUMMONFRIEND2 , argv ) ;
}
CCMD ( summonmbf )
{
SummonActor ( DEM_SUMMONMBF , DEM_SUMMONFRIEND2 , argv ) ;
}
CCMD ( summonfoe )
{
SummonActor ( DEM_SUMMONFOE , DEM_SUMMONFOE2 , argv ) ;
}
// Damage type defaults / global settings
TMap < FName , DamageTypeDefinition > GlobalDamageDefinitions ;
void DamageTypeDefinition : : Apply ( FName type )
{
GlobalDamageDefinitions [ type ] = * this ;
}
DamageTypeDefinition * DamageTypeDefinition : : Get ( FName type )
{
return GlobalDamageDefinitions . CheckKey ( type ) ;
}
bool DamageTypeDefinition : : IgnoreArmor ( FName type )
{
DamageTypeDefinition * dtd = Get ( type ) ;
if ( dtd ) return dtd - > NoArmor ;
return false ;
}
2017-01-18 21:15:48 +00:00
DEFINE_ACTION_FUNCTION ( _DamageTypeDefinition , IgnoreArmor )
{
PARAM_PROLOGUE ;
PARAM_NAME ( type ) ;
ACTION_RETURN_BOOL ( DamageTypeDefinition : : IgnoreArmor ( type ) ) ;
}
2017-02-26 20:36:06 +00:00
FString DamageTypeDefinition : : GetObituary ( FName type )
{
DamageTypeDefinition * dtd = Get ( type ) ;
if ( dtd ) return dtd - > Obituary ;
return " " ;
}
2017-01-18 21:15:48 +00:00
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// DamageTypeDefinition :: ApplyMobjDamageFactor
//
// Calculates mobj damage based on original damage, defined damage factors
// and damage type.
//
// If the specific damage type is not defined, the damage factor for
// type 'None' will be used (with 1.0 as a default value).
//
// Globally declared damage types may override or multiply the damage
// factor when 'None' is used as a fallback in this function.
//
//==========================================================================
2016-03-22 15:35:41 +00:00
double DamageTypeDefinition : : GetMobjDamageFactor ( FName type , DmgFactors const * const factors )
2016-03-01 15:47:10 +00:00
{
2017-04-11 21:29:37 +00:00
double defaultfac = - 1. ;
2016-03-01 15:47:10 +00:00
if ( factors )
{
// If the actor has named damage factors, look for a specific factor
2017-04-11 21:29:37 +00:00
for ( auto & p : * factors )
{
if ( p . first = = type ) return p . second ; // type specific damage type
if ( p . first = = NAME_None ) defaultfac = p . second ;
}
2016-03-01 15:47:10 +00:00
// If this was nonspecific damage, don't fall back to nonspecific search
2016-03-22 15:35:41 +00:00
if ( type = = NAME_None ) return 1. ;
2016-03-01 15:47:10 +00:00
}
// If this was nonspecific damage, don't fall back to nonspecific search
else if ( type = = NAME_None )
{
2016-03-22 15:35:41 +00:00
return 1. ;
2016-03-01 15:47:10 +00:00
}
else
{
// Normal is unsupplied / 1.0, so there's no difference between modifying and overriding
DamageTypeDefinition * dtd = Get ( type ) ;
2016-03-22 15:35:41 +00:00
return dtd ? dtd - > DefaultFactor : 1. ;
2016-03-01 15:47:10 +00:00
}
{
DamageTypeDefinition * dtd = Get ( type ) ;
// Here we are looking for modifications to untyped damage
// If the calling actor defines untyped damage factor, that is contained in "pdf".
2017-04-11 21:29:37 +00:00
if ( defaultfac > = 0. ) // normal damage available
2016-03-01 15:47:10 +00:00
{
if ( dtd )
{
2016-03-22 15:35:41 +00:00
if ( dtd - > ReplaceFactor ) return dtd - > DefaultFactor ; // use default instead of untyped factor
2017-04-11 21:29:37 +00:00
return defaultfac * dtd - > DefaultFactor ; // use default as modification of untyped factor
2016-03-01 15:47:10 +00:00
}
2017-04-11 21:29:37 +00:00
return defaultfac ; // there was no default, so actor default is used
2016-03-01 15:47:10 +00:00
}
else if ( dtd )
{
2016-03-22 15:35:41 +00:00
return dtd - > DefaultFactor ; // implicit untyped factor 1.0 does not need to be applied/replaced explicitly
2016-03-01 15:47:10 +00:00
}
}
2016-03-22 15:35:41 +00:00
return 1. ;
}
int DamageTypeDefinition : : ApplyMobjDamageFactor ( int damage , FName type , DmgFactors const * const factors )
{
double factor = GetMobjDamageFactor ( type , factors ) ;
return int ( damage * factor ) ;
2016-03-01 15:47:10 +00:00
}
2016-10-14 08:46:15 +00:00
//==========================================================================
//
// Reads a damage definition
//
//==========================================================================
void FMapInfoParser : : ParseDamageDefinition ( )
{
sc . MustGetString ( ) ;
FName damageType = sc . String ;
DamageTypeDefinition dtd ;
ParseOpenBrace ( ) ;
while ( sc . MustGetAnyToken ( ) , sc . TokenType ! = ' } ' )
{
if ( sc . Compare ( " FACTOR " ) )
{
sc . MustGetStringName ( " = " ) ;
sc . MustGetFloat ( ) ;
dtd . DefaultFactor = sc . Float ;
if ( dtd . DefaultFactor = = 0 ) dtd . ReplaceFactor = true ;
}
2017-02-27 13:53:39 +00:00
else if ( sc . Compare ( " OBITUARY " ) )
2017-02-26 20:36:06 +00:00
{
sc . MustGetStringName ( " = " ) ;
sc . MustGetString ( ) ;
dtd . Obituary = sc . String ;
}
2016-10-14 08:46:15 +00:00
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 ) ;
}