1261 lines
32 KiB
C++
1261 lines
32 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2002 Valve LLC. All rights reserved.
|
|
*
|
|
* This product contains software technology licensed from Id
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
* This source code contains proprietary and confidential information of
|
|
* Valve LLC and its suppliers. Access to this code is restricted to
|
|
* persons who have executed a written SDK license with Valve. Any access,
|
|
* use or distribution of this code by or to any unlicensed person is illegal.
|
|
*
|
|
****/
|
|
/*
|
|
|
|
|
|
===== scripted.cpp ========================================================
|
|
|
|
*/
|
|
|
|
#include "extdll.h"
|
|
#include "util.h"
|
|
#include "cbase.h"
|
|
#include "monsters.h"
|
|
|
|
#ifndef ANIMATION_H
|
|
#include "animation.h"
|
|
#endif
|
|
|
|
#ifndef SAVERESTORE_H
|
|
#include "saverestore.h"
|
|
#endif
|
|
|
|
#include "schedule.h"
|
|
#include "scripted.h"
|
|
#include "defaultai.h"
|
|
|
|
|
|
|
|
/*
|
|
classname "scripted_sequence"
|
|
targetname "me" - there can be more than one with the same name, and they act in concert
|
|
target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist
|
|
play "name_of_sequence"
|
|
idle "name of idle sequence to play before starting"
|
|
donetrigger "whatever" - can be any other triggerable entity such as another sequence, train, door, or a special case like "die" or "remove"
|
|
moveto - if set the monster first moves to this nodes position
|
|
range # - only search this far to find the target
|
|
spawnflags - (stop if blocked, stop if player seen)
|
|
*/
|
|
|
|
|
|
//
|
|
// Cache user-entity-field values until spawn is called.
|
|
//
|
|
|
|
void CCineMonster :: KeyValue( KeyValueData *pkvd )
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "m_iszIdle"))
|
|
{
|
|
m_iszIdle = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "m_iszPlay"))
|
|
{
|
|
m_iszPlay = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "m_iszEntity"))
|
|
{
|
|
m_iszEntity = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "m_fMoveTo"))
|
|
{
|
|
m_fMoveTo = atoi( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "m_flRepeat"))
|
|
{
|
|
m_flRepeat = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "m_flRadius"))
|
|
{
|
|
m_flRadius = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "m_iFinishSchedule"))
|
|
{
|
|
m_iFinishSchedule = atoi( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CBaseMonster::KeyValue( pkvd );
|
|
}
|
|
}
|
|
|
|
TYPEDESCRIPTION CCineMonster::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD( CCineMonster, m_iszIdle, FIELD_STRING ),
|
|
DEFINE_FIELD( CCineMonster, m_iszPlay, FIELD_STRING ),
|
|
DEFINE_FIELD( CCineMonster, m_iszEntity, FIELD_STRING ),
|
|
DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ),
|
|
DEFINE_FIELD( CCineMonster, m_flRepeat, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CCineMonster, m_flRadius, FIELD_FLOAT ),
|
|
|
|
DEFINE_FIELD( CCineMonster, m_iDelay, FIELD_INTEGER ),
|
|
DEFINE_FIELD( CCineMonster, m_startTime, FIELD_TIME ),
|
|
|
|
DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ),
|
|
DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ),
|
|
DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ),
|
|
DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ),
|
|
DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ),
|
|
};
|
|
|
|
|
|
IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster );
|
|
|
|
LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster );
|
|
#define CLASSNAME "scripted_sequence"
|
|
|
|
LINK_ENTITY_TO_CLASS( aiscripted_sequence, CCineAI );
|
|
|
|
|
|
void CCineMonster :: Spawn( void )
|
|
{
|
|
// pev->solid = SOLID_TRIGGER;
|
|
// UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8));
|
|
pev->solid = SOLID_NOT;
|
|
|
|
|
|
// REMOVE: The old side-effect
|
|
#if 0
|
|
if ( m_iszIdle )
|
|
m_fMoveTo = 4;
|
|
#endif
|
|
|
|
// if no targetname, start now
|
|
if ( FStringNull(pev->targetname) || !FStringNull( m_iszIdle ) )
|
|
{
|
|
SetThink( CineThink );
|
|
pev->nextthink = gpGlobals->time + 1.0;
|
|
// Wait to be used?
|
|
if ( pev->targetname )
|
|
m_startTime = gpGlobals->time + 1E6;
|
|
}
|
|
if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT )
|
|
m_interruptable = FALSE;
|
|
else
|
|
m_interruptable = TRUE;
|
|
}
|
|
|
|
//=========================================================
|
|
// FCanOverrideState - returns FALSE, scripted sequences
|
|
// cannot possess entities regardless of state.
|
|
//=========================================================
|
|
BOOL CCineMonster :: FCanOverrideState( void )
|
|
{
|
|
if ( pev->spawnflags & SF_SCRIPT_OVERRIDESTATE )
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
//=========================================================
|
|
// FCanOverrideState - returns true because scripted AI can
|
|
// possess entities regardless of their state.
|
|
//=========================================================
|
|
BOOL CCineAI :: FCanOverrideState( void )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// CineStart
|
|
//
|
|
void CCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
// do I already know who I should use
|
|
CBaseEntity *pEntity = m_hTargetEnt;
|
|
CBaseMonster *pTarget = NULL;
|
|
|
|
if ( pEntity )
|
|
pTarget = pEntity->MyMonsterPointer();
|
|
|
|
if ( pTarget )
|
|
{
|
|
// am I already playing the script?
|
|
if ( pTarget->m_scriptState == SCRIPT_PLAYING )
|
|
return;
|
|
|
|
m_startTime = gpGlobals->time + 0.05;
|
|
}
|
|
else
|
|
{
|
|
// if not, try finding them
|
|
SetThink( CineThink );
|
|
pev->nextthink = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
|
|
// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events
|
|
void CCineMonster :: Blocked( CBaseEntity *pOther )
|
|
{
|
|
|
|
}
|
|
|
|
void CCineMonster :: Touch( CBaseEntity *pOther )
|
|
{
|
|
/*
|
|
ALERT( at_aiconsole, "Cine Touch\n" );
|
|
if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget))
|
|
{
|
|
CBaseMonster *pTarget = GetClassPtr((CBaseMonster *)VARS(m_pentTarget));
|
|
pTarget->m_monsterState == MONSTERSTATE_SCRIPT;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
/*
|
|
entvars_t *pevOther = VARS( gpGlobals->other );
|
|
|
|
if ( !FBitSet ( pevOther->flags , FL_MONSTER ) )
|
|
{// touched by a non-monster.
|
|
return;
|
|
}
|
|
|
|
pevOther->origin.z += 1;
|
|
|
|
if ( FBitSet ( pevOther->flags, FL_ONGROUND ) )
|
|
{// clear the onground so physics don't bitch
|
|
pevOther->flags -= FL_ONGROUND;
|
|
}
|
|
|
|
// toss the monster!
|
|
pevOther->velocity = pev->movedir * pev->speed;
|
|
pevOther->velocity.z += m_flHeight;
|
|
|
|
|
|
pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE
|
|
}
|
|
*/
|
|
|
|
|
|
//
|
|
// ********** Cinematic DIE **********
|
|
//
|
|
void CCineMonster :: Die( void )
|
|
{
|
|
SetThink( SUB_Remove );
|
|
}
|
|
|
|
//
|
|
// ********** Cinematic PAIN **********
|
|
//
|
|
void CCineMonster :: Pain( void )
|
|
{
|
|
|
|
}
|
|
|
|
//
|
|
// ********** Cinematic Think **********
|
|
//
|
|
|
|
// find a viable entity
|
|
int CCineMonster :: FindEntity( void )
|
|
{
|
|
edict_t *pentTarget;
|
|
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity));
|
|
m_hTargetEnt = NULL;
|
|
CBaseMonster *pTarget = NULL;
|
|
|
|
while (!FNullEnt(pentTarget))
|
|
{
|
|
if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER ))
|
|
{
|
|
pTarget = GetMonsterPointer( pentTarget );
|
|
if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_BY_NAME ) )
|
|
{
|
|
m_hTargetEnt = pTarget;
|
|
return TRUE;
|
|
}
|
|
ALERT( at_console, "Found %s, but can't play!\n", STRING(m_iszEntity) );
|
|
}
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity));
|
|
pTarget = NULL;
|
|
}
|
|
|
|
if ( !pTarget )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL)
|
|
{
|
|
if (FClassnameIs( pEntity->pev, STRING(m_iszEntity)))
|
|
{
|
|
if ( FBitSet( pEntity->pev->flags, FL_MONSTER ))
|
|
{
|
|
pTarget = pEntity->MyMonsterPointer( );
|
|
if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_IDLE ) )
|
|
{
|
|
m_hTargetEnt = pTarget;
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pTarget = NULL;
|
|
m_hTargetEnt = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
// make the entity enter a scripted sequence
|
|
void CCineMonster :: PossessEntity( void )
|
|
{
|
|
CBaseEntity *pEntity = m_hTargetEnt;
|
|
CBaseMonster *pTarget = NULL;
|
|
if ( pEntity )
|
|
pTarget = pEntity->MyMonsterPointer();
|
|
|
|
if ( pTarget )
|
|
{
|
|
|
|
// FindEntity() just checked this!
|
|
#if 0
|
|
if ( !pTarget->CanPlaySequence( FCanOverrideState() ) )
|
|
{
|
|
ALERT( at_aiconsole, "Can't possess entity %s\n", STRING(pTarget->pev->classname) );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
pTarget->m_pGoalEnt = this;
|
|
pTarget->m_pCine = this;
|
|
pTarget->m_hTargetEnt = this;
|
|
|
|
m_saved_movetype = pTarget->pev->movetype;
|
|
m_saved_solid = pTarget->pev->solid;
|
|
m_saved_effects = pTarget->pev->effects;
|
|
pTarget->pev->effects |= pev->effects;
|
|
|
|
switch (m_fMoveTo)
|
|
{
|
|
case 0:
|
|
pTarget->m_scriptState = SCRIPT_WAIT;
|
|
break;
|
|
|
|
case 1:
|
|
pTarget->m_scriptState = SCRIPT_WALK_TO_MARK;
|
|
DelayStart( 1 );
|
|
break;
|
|
|
|
case 2:
|
|
pTarget->m_scriptState = SCRIPT_RUN_TO_MARK;
|
|
DelayStart( 1 );
|
|
break;
|
|
|
|
case 4:
|
|
UTIL_SetOrigin( pTarget->pev, pev->origin );
|
|
pTarget->pev->ideal_yaw = pev->angles.y;
|
|
pTarget->pev->avelocity = Vector( 0, 0, 0 );
|
|
pTarget->pev->velocity = Vector( 0, 0, 0 );
|
|
pTarget->pev->effects |= EF_NOINTERP;
|
|
pTarget->pev->angles.y = pev->angles.y;
|
|
pTarget->m_scriptState = SCRIPT_WAIT;
|
|
m_startTime = gpGlobals->time + 1E6;
|
|
// UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters
|
|
// pTarget->pev->flags &= ~FL_ONGROUND;
|
|
break;
|
|
}
|
|
// ALERT( at_aiconsole, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->pev->targetname ), FBitSet(pev->spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" );
|
|
|
|
pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT;
|
|
if (m_iszIdle)
|
|
{
|
|
StartSequence( pTarget, m_iszIdle, FALSE );
|
|
if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay)))
|
|
{
|
|
pTarget->pev->framerate = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// make the entity carry out the scripted sequence instructions, but without
|
|
// destroying the monster's state.
|
|
void CCineAI :: PossessEntity( void )
|
|
{
|
|
Schedule_t *pNewSchedule;
|
|
|
|
CBaseEntity *pEntity = m_hTargetEnt;
|
|
CBaseMonster *pTarget = NULL;
|
|
if ( pEntity )
|
|
pTarget = pEntity->MyMonsterPointer();
|
|
|
|
if ( pTarget )
|
|
{
|
|
if ( !pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_AI ) )
|
|
{
|
|
ALERT( at_aiconsole, "(AI)Can't possess entity %s\n", STRING(pTarget->pev->classname) );
|
|
return;
|
|
}
|
|
|
|
pTarget->m_pGoalEnt = this;
|
|
pTarget->m_pCine = this;
|
|
pTarget->m_hTargetEnt = this;
|
|
|
|
m_saved_movetype = pTarget->pev->movetype;
|
|
m_saved_solid = pTarget->pev->solid;
|
|
m_saved_effects = pTarget->pev->effects;
|
|
pTarget->pev->effects |= pev->effects;
|
|
|
|
switch (m_fMoveTo)
|
|
{
|
|
case 0:
|
|
case 5:
|
|
pTarget->m_scriptState = SCRIPT_WAIT;
|
|
break;
|
|
|
|
case 1:
|
|
pTarget->m_scriptState = SCRIPT_WALK_TO_MARK;
|
|
break;
|
|
|
|
case 2:
|
|
pTarget->m_scriptState = SCRIPT_RUN_TO_MARK;
|
|
break;
|
|
|
|
case 4:
|
|
// zap the monster instantly to the site of the script entity.
|
|
UTIL_SetOrigin( pTarget->pev, pev->origin );
|
|
pTarget->pev->ideal_yaw = pev->angles.y;
|
|
pTarget->pev->avelocity = Vector( 0, 0, 0 );
|
|
pTarget->pev->velocity = Vector( 0, 0, 0 );
|
|
pTarget->pev->effects |= EF_NOINTERP;
|
|
pTarget->pev->angles.y = pev->angles.y;
|
|
pTarget->m_scriptState = SCRIPT_WAIT;
|
|
m_startTime = gpGlobals->time + 1E6;
|
|
// UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters
|
|
pTarget->pev->flags &= ~FL_ONGROUND;
|
|
break;
|
|
default:
|
|
ALERT ( at_aiconsole, "aiscript: invalid Move To Position value!" );
|
|
break;
|
|
}
|
|
|
|
ALERT( at_aiconsole, "\"%s\" found and used\n", STRING( pTarget->pev->targetname ) );
|
|
|
|
pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT;
|
|
|
|
/*
|
|
if (m_iszIdle)
|
|
{
|
|
StartSequence( pTarget, m_iszIdle, FALSE );
|
|
if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay)))
|
|
{
|
|
pTarget->pev->framerate = 0;
|
|
}
|
|
}
|
|
*/
|
|
// Already in a scripted state?
|
|
if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT )
|
|
{
|
|
pNewSchedule = pTarget->GetScheduleOfType( SCHED_AISCRIPT );
|
|
pTarget->ChangeSchedule( pNewSchedule );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCineMonster :: CineThink( void )
|
|
{
|
|
if (FindEntity())
|
|
{
|
|
PossessEntity( );
|
|
ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) );
|
|
}
|
|
else
|
|
{
|
|
CancelScript( );
|
|
ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) );
|
|
pev->nextthink = gpGlobals->time + 1.0;
|
|
}
|
|
}
|
|
|
|
|
|
// lookup a sequence name and setup the target monster to play it
|
|
BOOL CCineMonster :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty )
|
|
{
|
|
if ( !iszSeq && completeOnEmpty )
|
|
{
|
|
SequenceDone( pTarget );
|
|
return FALSE;
|
|
}
|
|
|
|
pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) );
|
|
if (pTarget->pev->sequence == -1)
|
|
{
|
|
ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) );
|
|
pTarget->pev->sequence = 0;
|
|
// return FALSE;
|
|
}
|
|
|
|
#if 0
|
|
char *s;
|
|
if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT )
|
|
s = "No";
|
|
else
|
|
s = "Yes";
|
|
|
|
ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq), s );
|
|
#endif
|
|
|
|
pTarget->pev->frame = 0;
|
|
pTarget->ResetSequenceInfo( );
|
|
return TRUE;
|
|
}
|
|
|
|
// lookup a sequence name and setup the target monster to play it
|
|
// overridden for CCineAI because it's ok for them to not have an animation sequence
|
|
// for the monster to play. For a regular Scripted Sequence, that situation is an error.
|
|
BOOL CCineAI :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty )
|
|
{
|
|
if ( iszSeq == 0 && completeOnEmpty )
|
|
{
|
|
// no sequence was provided. Just let the monster proceed, however, we still have to fire any Sequence target
|
|
// and remove any non-repeatable CineAI entities here ( because there is code elsewhere that handles those tasks, but
|
|
// not until the animation sequence is finished. We have to manually take care of these things where there is no sequence.
|
|
|
|
SequenceDone ( pTarget );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) );
|
|
|
|
if (pTarget->pev->sequence == -1)
|
|
{
|
|
ALERT( at_error, "%s: unknown aiscripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) );
|
|
pTarget->pev->sequence = 0;
|
|
// return FALSE;
|
|
}
|
|
|
|
pTarget->pev->frame = 0;
|
|
pTarget->ResetSequenceInfo( );
|
|
return TRUE;
|
|
}
|
|
|
|
//=========================================================
|
|
// SequenceDone - called when a scripted sequence animation
|
|
// sequence is done playing ( or when an AI Scripted Sequence
|
|
// doesn't supply an animation sequence to play ). Expects
|
|
// the CBaseMonster pointer to the monster that the sequence
|
|
// possesses.
|
|
//=========================================================
|
|
void CCineMonster :: SequenceDone ( CBaseMonster *pMonster )
|
|
{
|
|
//ALERT( at_aiconsole, "Sequence %s finished\n", STRING( m_pCine->m_iszPlay ) );
|
|
|
|
if ( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) )
|
|
{
|
|
SetThink( SUB_Remove );
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
}
|
|
|
|
// This is done so that another sequence can take over the monster when triggered by the first
|
|
|
|
pMonster->CineCleanup();
|
|
|
|
FixScriptMonsterSchedule( pMonster );
|
|
|
|
// This may cause a sequence to attempt to grab this guy NOW, so we have to clear him out
|
|
// of the existing sequence
|
|
SUB_UseTargets( NULL, USE_TOGGLE, 0 );
|
|
}
|
|
|
|
//=========================================================
|
|
// When a monster finishes a scripted sequence, we have to
|
|
// fix up its state and schedule for it to return to a
|
|
// normal AI monster.
|
|
//
|
|
// Scripted sequences just dirty the Schedule and drop the
|
|
// monster in Idle State.
|
|
//=========================================================
|
|
void CCineMonster :: FixScriptMonsterSchedule( CBaseMonster *pMonster )
|
|
{
|
|
if ( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD )
|
|
pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE;
|
|
pMonster->ClearSchedule();
|
|
}
|
|
|
|
//=========================================================
|
|
// When a monster finishes a scripted sequence, we have to
|
|
// fix up its state and schedule for it to return to a
|
|
// normal AI monster.
|
|
//
|
|
// AI Scripted sequences will, depending on what the level
|
|
// designer selects:
|
|
//
|
|
// -Dirty the monster's schedule and drop out of the
|
|
// sequence in their current state.
|
|
//
|
|
// -Select a specific AMBUSH schedule, regardless of state.
|
|
//=========================================================
|
|
void CCineAI :: FixScriptMonsterSchedule( CBaseMonster *pMonster )
|
|
{
|
|
switch ( m_iFinishSchedule )
|
|
{
|
|
case SCRIPT_FINISHSCHED_DEFAULT:
|
|
pMonster->ClearSchedule();
|
|
break;
|
|
case SCRIPT_FINISHSCHED_AMBUSH:
|
|
pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_AMBUSH ) );
|
|
break;
|
|
default:
|
|
ALERT ( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" );
|
|
pMonster->ClearSchedule();
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOL CBaseMonster :: ExitScriptedSequence( )
|
|
{
|
|
if ( pev->deadflag == DEAD_DYING )
|
|
{
|
|
// is this legal?
|
|
// BUGBUG -- This doesn't call Killed()
|
|
m_IdealMonsterState = MONSTERSTATE_DEAD;
|
|
return FALSE;
|
|
}
|
|
|
|
if (m_pCine)
|
|
{
|
|
m_pCine->CancelScript( );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void CCineMonster::AllowInterrupt( BOOL fAllow )
|
|
{
|
|
if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT )
|
|
return;
|
|
m_interruptable = fAllow;
|
|
}
|
|
|
|
|
|
BOOL CCineMonster::CanInterrupt( void )
|
|
{
|
|
if ( !m_interruptable )
|
|
return FALSE;
|
|
|
|
CBaseEntity *pTarget = m_hTargetEnt;
|
|
|
|
if ( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO )
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
int CCineMonster::IgnoreConditions( void )
|
|
{
|
|
if ( CanInterrupt() )
|
|
return 0;
|
|
return SCRIPT_BREAK_CONDITIONS;
|
|
}
|
|
|
|
|
|
void ScriptEntityCancel( edict_t *pentCine )
|
|
{
|
|
// make sure they are a scripted_sequence
|
|
if (FClassnameIs( pentCine, CLASSNAME ))
|
|
{
|
|
CCineMonster *pCineTarget = GetClassPtr((CCineMonster *)VARS(pentCine));
|
|
// make sure they have a monster in mind for the script
|
|
CBaseEntity *pEntity = pCineTarget->m_hTargetEnt;
|
|
CBaseMonster *pTarget = NULL;
|
|
if ( pEntity )
|
|
pTarget = pEntity->MyMonsterPointer();
|
|
|
|
if (pTarget)
|
|
{
|
|
// make sure their monster is actually playing a script
|
|
if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT )
|
|
{
|
|
// tell them do die
|
|
pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP;
|
|
// do it now
|
|
pTarget->CineCleanup( );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// find all the cinematic entities with my targetname and stop them from playing
|
|
void CCineMonster :: CancelScript( void )
|
|
{
|
|
ALERT( at_aiconsole, "Cancelling script: %s\n", STRING(m_iszPlay) );
|
|
|
|
if ( !pev->targetname )
|
|
{
|
|
ScriptEntityCancel( edict() );
|
|
return;
|
|
}
|
|
|
|
edict_t *pentCineTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname));
|
|
|
|
while (!FNullEnt(pentCineTarget))
|
|
{
|
|
ScriptEntityCancel( pentCineTarget );
|
|
pentCineTarget = FIND_ENTITY_BY_TARGETNAME(pentCineTarget, STRING(pev->targetname));
|
|
}
|
|
}
|
|
|
|
|
|
// find all the cinematic entities with my targetname and tell them to wait before starting
|
|
void CCineMonster :: DelayStart( int state )
|
|
{
|
|
edict_t *pentCine = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname));
|
|
|
|
while (!FNullEnt(pentCine))
|
|
{
|
|
if (FClassnameIs( pentCine, "scripted_sequence" ))
|
|
{
|
|
CCineMonster *pTarget = GetClassPtr((CCineMonster *)VARS(pentCine));
|
|
if (state)
|
|
{
|
|
pTarget->m_iDelay++;
|
|
}
|
|
else
|
|
{
|
|
pTarget->m_iDelay--;
|
|
if (pTarget->m_iDelay <= 0)
|
|
pTarget->m_startTime = gpGlobals->time + 0.05;
|
|
}
|
|
}
|
|
pentCine = FIND_ENTITY_BY_TARGETNAME(pentCine, STRING(pev->targetname));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Find an entity that I'm interested in and precache the sounds he'll need in the sequence.
|
|
void CCineMonster :: Activate( void )
|
|
{
|
|
edict_t *pentTarget;
|
|
CBaseMonster *pTarget;
|
|
|
|
// The entity name could be a target name or a classname
|
|
// Check the targetname
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity));
|
|
pTarget = NULL;
|
|
|
|
while (!pTarget && !FNullEnt(pentTarget))
|
|
{
|
|
if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER ))
|
|
{
|
|
pTarget = GetMonsterPointer( pentTarget );
|
|
}
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity));
|
|
}
|
|
|
|
// If no entity with that targetname, check the classname
|
|
if ( !pTarget )
|
|
{
|
|
pentTarget = FIND_ENTITY_BY_CLASSNAME(NULL, STRING(m_iszEntity));
|
|
while (!pTarget && !FNullEnt(pentTarget))
|
|
{
|
|
pTarget = GetMonsterPointer( pentTarget );
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity));
|
|
}
|
|
}
|
|
// Found a compatible entity
|
|
if ( pTarget )
|
|
{
|
|
void *pmodel;
|
|
pmodel = GET_MODEL_PTR( pTarget->edict() );
|
|
if ( pmodel )
|
|
{
|
|
// Look through the event list for stuff to precache
|
|
SequencePrecache( pmodel, STRING( m_iszIdle ) );
|
|
SequencePrecache( pmodel, STRING( m_iszPlay ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOL CBaseMonster :: CineCleanup( )
|
|
{
|
|
CCineMonster *pOldCine = m_pCine;
|
|
|
|
// am I linked to a cinematic?
|
|
if (m_pCine)
|
|
{
|
|
// okay, reset me to what it thought I was before
|
|
m_pCine->m_hTargetEnt = NULL;
|
|
pev->movetype = m_pCine->m_saved_movetype;
|
|
pev->solid = m_pCine->m_saved_solid;
|
|
pev->effects = m_pCine->m_saved_effects;
|
|
}
|
|
else
|
|
{
|
|
// arg, punt
|
|
pev->movetype = MOVETYPE_STEP;// this is evil
|
|
pev->solid = SOLID_SLIDEBOX;
|
|
}
|
|
m_pCine = NULL;
|
|
m_hTargetEnt = NULL;
|
|
m_pGoalEnt = NULL;
|
|
if (pev->deadflag == DEAD_DYING)
|
|
{
|
|
// last frame of death animation?
|
|
pev->health = 0;
|
|
pev->framerate = 0.0;
|
|
pev->solid = SOLID_NOT;
|
|
SetState( MONSTERSTATE_DEAD );
|
|
pev->deadflag = DEAD_DEAD;
|
|
UTIL_SetSize( pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2) );
|
|
|
|
if ( pOldCine && FBitSet( pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE ) )
|
|
{
|
|
SetUse( NULL ); // BUGBUG -- This doesn't call Killed()
|
|
SetThink( NULL ); // This will probably break some stuff
|
|
SetTouch( NULL );
|
|
}
|
|
else
|
|
SUB_StartFadeOut(); // SetThink( SUB_DoNothing );
|
|
// This turns off animation & physics in case their origin ends up stuck in the world or something
|
|
StopAnimation();
|
|
pev->movetype = MOVETYPE_NONE;
|
|
pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place
|
|
return FALSE;
|
|
}
|
|
|
|
// If we actually played a sequence
|
|
if ( pOldCine && pOldCine->m_iszPlay )
|
|
{
|
|
if ( !(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT) )
|
|
{
|
|
// reset position
|
|
Vector new_origin, new_angle;
|
|
GetBonePosition( 0, new_origin, new_angle );
|
|
|
|
// Figure out how far they have moved
|
|
// We can't really solve this problem because we can't query the movement of the origin relative
|
|
// to the sequence. We can get the root bone's position as we do here, but there are
|
|
// cases where the root bone is in a different relative position to the entity's origin
|
|
// before/after the sequence plays. So we are stuck doing this:
|
|
|
|
// !!!HACKHACK: Float the origin up and drop to floor because some sequences have
|
|
// irregular motion that can't be properly accounted for.
|
|
|
|
// UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE.
|
|
Vector oldOrigin = pev->origin;
|
|
|
|
// UNDONE: ugly hack. Don't move monster if they don't "seem" to move
|
|
// this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly
|
|
// being set, so animations that really do move won't be caught.
|
|
if ((oldOrigin - new_origin).Length2D() < 8.0)
|
|
new_origin = oldOrigin;
|
|
|
|
pev->origin.x = new_origin.x;
|
|
pev->origin.y = new_origin.y;
|
|
pev->origin.z += 1;
|
|
|
|
pev->flags |= FL_ONGROUND;
|
|
int drop = DROP_TO_FLOOR( ENT(pev) );
|
|
|
|
// Origin in solid? Set to org at the end of the sequence
|
|
if ( drop < 0 )
|
|
pev->origin = oldOrigin;
|
|
else if ( drop == 0 ) // Hanging in air?
|
|
{
|
|
pev->origin.z = new_origin.z;
|
|
pev->flags &= ~FL_ONGROUND;
|
|
}
|
|
// else entity hit floor, leave there
|
|
|
|
// pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this
|
|
|
|
UTIL_SetOrigin( pev, pev->origin );
|
|
pev->effects |= EF_NOINTERP;
|
|
}
|
|
|
|
// We should have some animation to put these guys in, but for now it's idle.
|
|
// Due to NOINTERP above, there won't be any blending between this anim & the sequence
|
|
m_Activity = ACT_RESET;
|
|
}
|
|
// set them back into a normal state
|
|
pev->enemy = NULL;
|
|
if ( pev->health > 0 )
|
|
m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState;
|
|
else
|
|
{
|
|
// Dropping out because he got killed
|
|
// Can't call killed() no attacker and weirdness (late gibbing) may result
|
|
m_IdealMonsterState = MONSTERSTATE_DEAD;
|
|
SetConditions( bits_COND_LIGHT_DAMAGE );
|
|
pev->deadflag = DEAD_DYING;
|
|
FCheckAITrigger();
|
|
pev->deadflag = DEAD_NO;
|
|
}
|
|
|
|
|
|
// SetAnimation( m_MonsterState );
|
|
ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
class CScriptedSentence : public CBaseToggle
|
|
{
|
|
public:
|
|
void Spawn( void );
|
|
void KeyValue( KeyValueData *pkvd );
|
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
|
|
void EXPORT FindThink( void );
|
|
void EXPORT DelayThink( void );
|
|
int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); }
|
|
|
|
virtual int Save( CSave &save );
|
|
virtual int Restore( CRestore &restore );
|
|
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
|
|
CBaseMonster *FindEntity( void );
|
|
BOOL AcceptableSpeaker( CBaseMonster *pMonster );
|
|
BOOL StartSentence( CBaseMonster *pTarget );
|
|
|
|
|
|
private:
|
|
int m_iszSentence; // string index for idle animation
|
|
int m_iszEntity; // entity that is wanted for this sentence
|
|
float m_flRadius; // range to search
|
|
float m_flDuration; // How long the sentence lasts
|
|
float m_flRepeat; // repeat rate
|
|
float m_flAttenuation;
|
|
float m_flVolume;
|
|
BOOL m_active;
|
|
int m_iszListener; // name of entity to look at while talking
|
|
};
|
|
|
|
#define SF_SENTENCE_ONCE 0x0001
|
|
#define SF_SENTENCE_FOLLOWERS 0x0002 // only say if following player
|
|
#define SF_SENTENCE_INTERRUPT 0x0004 // force talking except when dead
|
|
#define SF_SENTENCE_CONCURRENT 0x0008 // allow other people to keep talking
|
|
|
|
TYPEDESCRIPTION CScriptedSentence::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD( CScriptedSentence, m_iszSentence, FIELD_STRING ),
|
|
DEFINE_FIELD( CScriptedSentence, m_iszEntity, FIELD_STRING ),
|
|
DEFINE_FIELD( CScriptedSentence, m_flRadius, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CScriptedSentence, m_flDuration, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CScriptedSentence, m_flRepeat, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CScriptedSentence, m_flAttenuation, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CScriptedSentence, m_flVolume, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CScriptedSentence, m_active, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ),
|
|
};
|
|
|
|
|
|
IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseToggle );
|
|
|
|
LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence );
|
|
|
|
void CScriptedSentence :: KeyValue( KeyValueData *pkvd )
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "sentence"))
|
|
{
|
|
m_iszSentence = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "entity"))
|
|
{
|
|
m_iszEntity = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "duration"))
|
|
{
|
|
m_flDuration = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "radius"))
|
|
{
|
|
m_flRadius = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "refire"))
|
|
{
|
|
m_flRepeat = atof( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if(FStrEq(pkvd->szKeyName, "attenuation"))
|
|
{
|
|
pev->impulse = atoi( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if(FStrEq(pkvd->szKeyName, "volume"))
|
|
{
|
|
m_flVolume = atof( pkvd->szValue ) * 0.1;
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "listener"))
|
|
{
|
|
m_iszListener = ALLOC_STRING( pkvd->szValue );
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
CBaseToggle::KeyValue( pkvd );
|
|
}
|
|
|
|
|
|
void CScriptedSentence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
if ( !m_active )
|
|
return;
|
|
// ALERT( at_console, "Firing sentence: %s\n", STRING(m_iszSentence) );
|
|
SetThink( FindThink );
|
|
pev->nextthink = gpGlobals->time;
|
|
}
|
|
|
|
|
|
void CScriptedSentence :: Spawn( void )
|
|
{
|
|
pev->solid = SOLID_NOT;
|
|
|
|
m_active = TRUE;
|
|
// if no targetname, start now
|
|
if ( !pev->targetname )
|
|
{
|
|
SetThink( FindThink );
|
|
pev->nextthink = gpGlobals->time + 1.0;
|
|
}
|
|
|
|
switch( pev->impulse )
|
|
{
|
|
case 1: // Medium radius
|
|
m_flAttenuation = ATTN_STATIC;
|
|
break;
|
|
|
|
case 2: // Large radius
|
|
m_flAttenuation = ATTN_NORM;
|
|
break;
|
|
|
|
case 3: //EVERYWHERE
|
|
m_flAttenuation = ATTN_NONE;
|
|
break;
|
|
|
|
default:
|
|
case 0: // Small radius
|
|
m_flAttenuation = ATTN_IDLE;
|
|
break;
|
|
}
|
|
pev->impulse = 0;
|
|
|
|
// No volume, use normal
|
|
if ( m_flVolume <= 0 )
|
|
m_flVolume = 1.0;
|
|
}
|
|
|
|
|
|
void CScriptedSentence :: FindThink( void )
|
|
{
|
|
CBaseMonster *pMonster = FindEntity();
|
|
if ( pMonster )
|
|
{
|
|
StartSentence( pMonster );
|
|
if ( pev->spawnflags & SF_SENTENCE_ONCE )
|
|
UTIL_Remove( this );
|
|
SetThink( DelayThink );
|
|
pev->nextthink = gpGlobals->time + m_flDuration + m_flRepeat;
|
|
m_active = FALSE;
|
|
// ALERT( at_console, "%s: found monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) );
|
|
}
|
|
else
|
|
{
|
|
// ALERT( at_console, "%s: can't find monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) );
|
|
pev->nextthink = gpGlobals->time + m_flRepeat + 0.5;
|
|
}
|
|
}
|
|
|
|
|
|
void CScriptedSentence :: DelayThink( void )
|
|
{
|
|
m_active = TRUE;
|
|
if ( !pev->targetname )
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
SetThink( FindThink );
|
|
}
|
|
|
|
|
|
BOOL CScriptedSentence :: AcceptableSpeaker( CBaseMonster *pMonster )
|
|
{
|
|
if ( pMonster )
|
|
{
|
|
if ( pev->spawnflags & SF_SENTENCE_FOLLOWERS )
|
|
{
|
|
if ( pMonster->m_hTargetEnt == NULL || !FClassnameIs(pMonster->m_hTargetEnt->pev, "player") )
|
|
return FALSE;
|
|
}
|
|
BOOL override;
|
|
if ( pev->spawnflags & SF_SENTENCE_INTERRUPT )
|
|
override = TRUE;
|
|
else
|
|
override = FALSE;
|
|
if ( pMonster->CanPlaySentence( override ) )
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
CBaseMonster *CScriptedSentence :: FindEntity( void )
|
|
{
|
|
edict_t *pentTarget;
|
|
CBaseMonster *pMonster;
|
|
|
|
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity));
|
|
pMonster = NULL;
|
|
|
|
while (!FNullEnt(pentTarget))
|
|
{
|
|
pMonster = GetMonsterPointer( pentTarget );
|
|
if ( pMonster != NULL )
|
|
{
|
|
if ( AcceptableSpeaker( pMonster ) )
|
|
return pMonster;
|
|
// ALERT( at_console, "%s (%s), not acceptable\n", STRING(pMonster->pev->classname), STRING(pMonster->pev->targetname) );
|
|
}
|
|
pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity));
|
|
}
|
|
|
|
CBaseEntity *pEntity = NULL;
|
|
while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL)
|
|
{
|
|
if (FClassnameIs( pEntity->pev, STRING(m_iszEntity)))
|
|
{
|
|
if ( FBitSet( pEntity->pev->flags, FL_MONSTER ))
|
|
{
|
|
pMonster = pEntity->MyMonsterPointer( );
|
|
if ( AcceptableSpeaker( pMonster ) )
|
|
return pMonster;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
BOOL CScriptedSentence :: StartSentence( CBaseMonster *pTarget )
|
|
{
|
|
if ( !pTarget )
|
|
{
|
|
ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING(m_iszSentence) );
|
|
return NULL;
|
|
}
|
|
|
|
BOOL bConcurrent = FALSE;
|
|
if ( !(pev->spawnflags & SF_SENTENCE_CONCURRENT) )
|
|
bConcurrent = TRUE;
|
|
|
|
CBaseEntity *pListener = NULL;
|
|
if (!FStringNull(m_iszListener))
|
|
{
|
|
float radius = m_flRadius;
|
|
|
|
if ( FStrEq( STRING(m_iszListener ), "player" ) )
|
|
radius = 4096; // Always find the player
|
|
|
|
pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius );
|
|
}
|
|
|
|
pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener );
|
|
ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING(m_iszSentence), m_flDuration );
|
|
SUB_UseTargets( NULL, USE_TOGGLE, 0 );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
|
|
//=========================================================
|
|
// Furniture - this is the cool comment I cut-and-pasted
|
|
//=========================================================
|
|
class CFurniture : public CBaseMonster
|
|
{
|
|
public:
|
|
void Spawn ( void );
|
|
void Die( void );
|
|
int Classify ( void );
|
|
virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); }
|
|
};
|
|
|
|
|
|
LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture );
|
|
|
|
|
|
//=========================================================
|
|
// Furniture is killed
|
|
//=========================================================
|
|
void CFurniture :: Die ( void )
|
|
{
|
|
SetThink ( SUB_Remove );
|
|
pev->nextthink = gpGlobals->time;
|
|
}
|
|
|
|
//=========================================================
|
|
// This used to have something to do with bees flying, but
|
|
// now it only initializes moving furniture in scripted sequences
|
|
//=========================================================
|
|
void CFurniture :: Spawn( )
|
|
{
|
|
PRECACHE_MODEL((char *)STRING(pev->model));
|
|
SET_MODEL(ENT(pev), STRING(pev->model));
|
|
|
|
pev->movetype = MOVETYPE_NONE;
|
|
pev->solid = SOLID_BBOX;
|
|
pev->health = 80000;
|
|
pev->takedamage = DAMAGE_AIM;
|
|
pev->effects = 0;
|
|
pev->yaw_speed = 0;
|
|
pev->sequence = 0;
|
|
pev->frame = 0;
|
|
|
|
// pev->nextthink += 1.0;
|
|
// SetThink (WalkMonsterDelay);
|
|
|
|
ResetSequenceInfo( );
|
|
pev->frame = 0;
|
|
MonsterInit();
|
|
}
|
|
|
|
//=========================================================
|
|
// ID's Furniture as neutral (noone will attack it)
|
|
//=========================================================
|
|
int CFurniture::Classify ( void )
|
|
{
|
|
return CLASS_NONE;
|
|
}
|
|
|
|
|