hlsdk-xash3d/dlls/scripted.cpp

1245 lines
34 KiB
C++
Raw Normal View History

2017-12-18 00:39:44 +01:00
/***
*
2016-06-04 15:24:23 +02:00
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
2017-12-18 00:39:44 +01:00
*
* 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"
#include "player.h"
2021-06-20 00:53:07 +02:00
#if !defined(ANIMATION_H)
2017-12-18 00:39:44 +01:00
#include "animation.h"
#endif
2021-06-20 00:53:07 +02:00
#if !defined(SAVERESTORE_H)
2017-12-18 00:39:44 +01:00
#include "saverestore.h"
#endif
#include "schedule.h"
#include "scripted.h"
#include "defaultai.h"
#include "movewith.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.
//
2016-07-31 15:48:50 +02:00
void CCineMonster::KeyValue( KeyValueData *pkvd )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( FStrEq( pkvd->szKeyName, "m_iszIdle" ) )
2017-12-18 00:39:44 +01:00
{
m_iszIdle = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "m_iszPlay" ) )
2017-12-18 00:39:44 +01:00
{
m_iszPlay = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "m_iszEntity" ) )
2017-12-18 00:39:44 +01:00
{
m_iszEntity = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_iszAttack"))
{
m_iszAttack = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_iszMoveTarget"))
{
m_iszMoveTarget = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_iszFireOnBegin"))
{
m_iszFireOnBegin = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "m_fMoveTo" ) )
2017-12-18 00:39:44 +01:00
{
m_fMoveTo = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_fTurnType"))
{
m_fTurnType = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_fAction"))
{
m_fAction = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
// LRC else if (FStrEq(pkvd->szKeyName, "m_flRepeat"))
// {
// m_flRepeat = atof( pkvd->szValue );
// pkvd->fHandled = TRUE;
// }
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "m_flRadius" ) )
2017-12-18 00:39:44 +01:00
{
m_flRadius = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_iRepeats"))
{
m_iRepeats = atoi( pkvd->szValue );
m_iRepeatsLeft = m_iRepeats;
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_fRepeatFrame"))
{
m_fRepeatFrame = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "m_iFinishSchedule" ) )
2017-12-18 00:39:44 +01:00
{
m_iFinishSchedule = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "m_iPriority"))
{
m_iPriority = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
{
CBaseMonster::KeyValue( pkvd );
}
}
TYPEDESCRIPTION CCineMonster::m_SaveData[] =
2017-12-18 00:39:44 +01:00
{
DEFINE_FIELD( CCineMonster, m_iState, FIELD_INTEGER ), //LRC
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_iszAttack, FIELD_STRING ), //LRC
DEFINE_FIELD( CCineMonster, m_iszMoveTarget, FIELD_STRING ), //LRC
DEFINE_FIELD( CCineMonster, m_iszFireOnBegin, FIELD_STRING ),
DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ),
DEFINE_FIELD( CCineMonster, m_fTurnType, FIELD_INTEGER ),
DEFINE_FIELD( CCineMonster, m_fAction, FIELD_INTEGER ),
//LRC- this is unused 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 ),
2016-07-31 15:48:50 +02:00
DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ),
DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ),
2017-12-18 00:39:44 +01:00
DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ),
DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ),
DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ),
//LRC
DEFINE_FIELD( CCineMonster, m_iRepeats, FIELD_INTEGER ),
DEFINE_FIELD( CCineMonster, m_iRepeatsLeft, FIELD_INTEGER ),
DEFINE_FIELD( CCineMonster, m_fRepeatFrame, FIELD_FLOAT ),
DEFINE_FIELD( CCineMonster, m_iPriority, FIELD_INTEGER ),
};
IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster )
2017-12-18 00:39:44 +01:00
LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster )
2017-12-18 21:47:12 +01:00
LINK_ENTITY_TO_CLASS( scripted_action, CCineMonster ) //LRC
2017-12-18 00:39:44 +01:00
2017-12-18 21:47:12 +01:00
LINK_ENTITY_TO_CLASS( aiscripted_sequence, CCineMonster ) //LRC - aiscripted sequences don't need to be seperate
2017-12-18 00:39:44 +01:00
2016-07-31 15:48:50 +02:00
void CCineMonster::Spawn( void )
2017-12-18 00:39:44 +01:00
{
// pev->solid = SOLID_TRIGGER;
2016-07-31 15:48:50 +02:00
// UTIL_SetSize( pev, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ) );
2017-12-18 00:39:44 +01:00
pev->solid = SOLID_NOT;
m_iState = STATE_OFF; //LRC
2019-11-10 22:59:56 +01:00
if( FStringNull(m_iszIdle) && FStringNull(pev->targetname) ) // if no targetname, start now
2017-12-18 00:39:44 +01:00
{
2016-06-04 15:24:23 +02:00
SetThink( &CCineMonster::CineThink );
2019-11-10 22:59:56 +01:00
SetNextThink( 1.0f );
2017-12-18 00:39:44 +01:00
}
2019-11-10 22:59:56 +01:00
else if( m_iszIdle )
2017-12-18 00:39:44 +01:00
{
2019-11-10 22:59:56 +01:00
SetThink( &CCineMonster::InitIdleThink );
SetNextThink( 1.0f );
2017-12-18 00:39:44 +01:00
}
2016-07-31 15:48:50 +02:00
if( pev->spawnflags & SF_SCRIPT_NOINTERRUPT )
2017-12-18 00:39:44 +01:00
m_interruptable = FALSE;
else
m_interruptable = TRUE;
//LRC - the only difference between AI and normal sequences
2019-11-10 22:59:56 +01:00
if( FClassnameIs(pev, "aiscripted_sequence") || pev->spawnflags & SF_SCRIPT_OVERRIDESTATE )
{
2017-12-18 00:39:44 +01:00
m_iPriority |= SS_INTERRUPT_ANYSTATE;
2019-11-10 22:59:56 +01:00
}
2017-12-18 00:39:44 +01:00
}
//
// CineStart
//
2016-07-31 15:48:50 +02:00
void CCineMonster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
2017-12-18 00:39:44 +01:00
{
// do I already know who I should use
2016-07-31 15:48:50 +02:00
CBaseEntity *pEntity = m_hTargetEnt;
CBaseMonster *pTarget = NULL;
2017-12-18 00:39:44 +01:00
2016-07-31 15:48:50 +02:00
if( pEntity )
2017-12-18 00:39:44 +01:00
pTarget = pEntity->MyMonsterPointer();
2016-07-31 15:48:50 +02:00
if( pTarget )
2017-12-18 00:39:44 +01:00
{
// am I already playing the script?
2016-07-31 15:48:50 +02:00
if( pTarget->m_scriptState == SCRIPT_PLAYING )
2017-12-18 00:39:44 +01:00
return;
2019-11-10 22:59:56 +01:00
m_startTime = gpGlobals->time + 0.05f; //why the delay? -- LRC
2017-12-18 00:39:44 +01:00
}
else
{
// ALERT(at_console, "Sequence \"%s\" triggered, can't find target; searching\n", STRING(pev->targetname));
m_hActivator = pActivator;
// if not, try finding them
2016-06-04 15:24:23 +02:00
SetThink( &CCineMonster::CineThink );
2017-12-18 00:39:44 +01:00
// SetNextThink( 0 );
CineThink(); //LRC
}
}
// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events
2016-07-31 15:48:50 +02:00
void CCineMonster::Blocked( CBaseEntity *pOther )
2017-12-18 00:39:44 +01:00
{
}
2016-07-31 15:48:50 +02:00
void CCineMonster::Touch( CBaseEntity *pOther )
2017-12-18 00:39:44 +01:00
{
/*
ALERT( at_aiconsole, "Cine Touch\n" );
2016-07-31 15:48:50 +02:00
if( m_pentTarget && OFFSET( pOther->pev ) == OFFSET( m_pentTarget ) )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
CBaseMonster *pTarget = GetClassPtr( (CBaseMonster *)VARS( m_pentTarget ) );
2017-12-18 00:39:44 +01:00
pTarget->m_monsterState == MONSTERSTATE_SCRIPT;
}
*/
}
/*
entvars_t *pevOther = VARS( gpGlobals->other );
2016-07-31 15:48:50 +02:00
if( !FBitSet( pevOther->flags, FL_MONSTER ) )
{
// touched by a non-monster.
2017-12-18 00:39:44 +01:00
return;
}
pevOther->origin.z += 1;
2016-07-31 15:48:50 +02:00
if( FBitSet( pevOther->flags, FL_ONGROUND ) )
{
// clear the onground so physics don't bitch
2017-12-18 00:39:44 +01:00
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 **********
//
2016-07-31 15:48:50 +02:00
void CCineMonster::Die( void )
2017-12-18 00:39:44 +01:00
{
SetThink(&CCineMonster :: SUB_Remove );
}
//
// ********** Cinematic PAIN **********
//
2016-07-31 15:48:50 +02:00
void CCineMonster::Pain( void )
2017-12-18 00:39:44 +01:00
{
}
//
// ********** Cinematic Think **********
//
//LRC: now redefined... find a viable entity with the given name, and return it (or NULL if not found).
CBaseMonster* CCineMonster :: FindEntity( const char* sName, CBaseEntity *pActivator )
{
CBaseEntity *pEntity;
pEntity = UTIL_FindEntityByTargetname(NULL, sName, pActivator);
//m_hTargetEnt = NULL;
CBaseMonster *pMonster = NULL;
while (pEntity)
{
if ( FBitSet( pEntity->pev->flags, FL_MONSTER ))
{
pMonster = pEntity->MyMonsterPointer( );
if ( pMonster && pMonster->CanPlaySequence( m_iPriority | SS_INTERRUPT_ALERT ) )
{
return pMonster;
}
2017-12-18 21:47:12 +01:00
ALERT( at_console, "Found %s, but can't play!\n", sName );
2017-12-18 00:39:44 +01:00
}
pEntity = UTIL_FindEntityByTargetname(pEntity, sName, pActivator);
pMonster = NULL;
}
2017-12-18 00:39:44 +01:00
// couldn't find something with the given targetname; assume it's a classname instead.
if ( !pMonster )
{
pEntity = NULL;
2016-07-31 15:48:50 +02:00
while( ( pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius ) ) != NULL )
2017-12-18 00:39:44 +01:00
{
if (FClassnameIs( pEntity->pev, sName))
{
2016-07-31 15:48:50 +02:00
if( FBitSet( pEntity->pev->flags, FL_MONSTER ) )
2017-12-18 00:39:44 +01:00
{
pMonster = pEntity->MyMonsterPointer( );
if ( pMonster && pMonster->CanPlaySequence( m_iPriority ) )
{
return pMonster;
}
}
}
}
}
return NULL;
}
// make the entity enter a scripted sequence
2016-07-31 15:48:50 +02:00
void CCineMonster::PossessEntity( void )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
CBaseEntity *pEntity = m_hTargetEnt;
CBaseMonster *pTarget = NULL;
if( pEntity )
2017-12-18 00:39:44 +01:00
pTarget = pEntity->MyMonsterPointer();
2016-07-31 15:48:50 +02:00
if( pTarget )
2017-12-18 00:39:44 +01:00
{
if (pTarget->m_pCine)
{
pTarget->m_pCine->CancelScript();
}
pTarget->m_pCine = this;
if (m_iszAttack)
{
// anything with that name?
pTarget->m_hTargetEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszAttack), m_hActivator);
2018-02-12 16:22:43 +01:00
if( pTarget->m_hTargetEnt == 0 )
2017-12-18 00:39:44 +01:00
{ // nothing. Anything with that classname?
while ((pTarget->m_hTargetEnt = UTIL_FindEntityInSphere( pTarget->m_hTargetEnt, pev->origin, m_flRadius )) != NULL)
2018-02-12 16:22:43 +01:00
{
2017-12-18 00:39:44 +01:00
if (FClassnameIs( pTarget->m_hTargetEnt->pev, STRING(m_iszAttack))) break;
2018-02-12 16:22:43 +01:00
}
2017-12-18 00:39:44 +01:00
}
2018-02-12 16:22:43 +01:00
if (pTarget->m_hTargetEnt == 0)
2017-12-18 00:39:44 +01:00
{ // nothing. Oh well.
2017-12-18 21:47:12 +01:00
ALERT(at_console,"%s %s has a missing \"turn target\": %s\n",STRING(pev->classname),STRING(pev->targetname),STRING(m_iszAttack));
2017-12-18 00:39:44 +01:00
pTarget->m_hTargetEnt = this;
2018-02-12 16:22:43 +01:00
}
}
2017-12-18 00:39:44 +01:00
else
2018-02-12 16:22:43 +01:00
{
2017-12-18 00:39:44 +01:00
pTarget->m_hTargetEnt = this;
}
if (m_iszMoveTarget)
2018-02-12 16:22:43 +01:00
{
2017-12-18 00:39:44 +01:00
// anything with that name?
pTarget->m_pGoalEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszMoveTarget), m_hActivator);
2018-02-12 16:22:43 +01:00
if( pTarget->m_pGoalEnt == 0 )
2017-12-18 00:39:44 +01:00
{ // nothing. Oh well.
2017-12-18 21:47:12 +01:00
ALERT(at_console,"%s %s has a missing \"move target\": %s\n",STRING(pev->classname),STRING(pev->targetname),STRING(m_iszMoveTarget));
2017-12-18 00:39:44 +01:00
pTarget->m_pGoalEnt = this;
}
}
else
{
pTarget->m_pGoalEnt = this;
}
// if (IsAction())
// pTarget->PushEnemy(this,pev->origin);
m_saved_movetype = pTarget->pev->movetype;
m_saved_solid = pTarget->pev->solid;
m_saved_effects = pTarget->pev->effects;
pTarget->pev->effects |= pev->effects;
// ALERT(at_console, "script. IsAction = %d",IsAction());
m_iState = STATE_ON; // LRC: assume we'll set it to 'on', unless proven otherwise...
2016-07-31 15:48:50 +02:00
switch( m_fMoveTo )
2017-12-18 00:39:44 +01:00
{
case 1:
case 2:
DelayStart( 1 );
m_iState = STATE_TURN_ON;
// fall through...
case 0:
2016-06-04 15:24:23 +02:00
case 4:
2017-12-18 00:39:44 +01:00
case 5:
case 6:
2016-06-04 15:24:23 +02:00
pTarget->m_scriptState = SCRIPT_WAIT;
2017-12-18 00:39:44 +01:00
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)
// {
// ALERT(at_console, "Possess: Play idle sequence\n");
// StartSequence( pTarget, m_iszIdle, FALSE );
// if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay)))
// {
// pTarget->pev->framerate = 0;
// }
// }
// ALERT(at_console, "Finished PossessEntity, ms %d, ims %d\n", pTarget->m_MonsterState, pTarget->m_IdealMonsterState);
2017-12-18 21:47:12 +01:00
}
2017-12-18 00:39:44 +01:00
}
// at the beginning of the level, set up the idle animation. --LRC
void CCineMonster :: InitIdleThink( void )
2018-02-12 16:22:43 +01:00
{
2017-12-18 00:39:44 +01:00
if ((m_hTargetEnt = FindEntity(STRING(m_iszEntity), NULL)) != NULL)
2016-06-04 15:24:23 +02:00
{
2017-12-18 00:39:44 +01:00
PossessEntity( );
2020-01-07 13:53:03 +01:00
m_startTime = gpGlobals->time + (float)1E6;
2017-12-18 00:39:44 +01:00
ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) );
2016-06-04 15:24:23 +02:00
}
2017-12-18 00:39:44 +01:00
else
2016-06-04 15:24:23 +02:00
{
2017-12-18 00:39:44 +01:00
CancelScript( );
ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) );
2020-01-07 13:53:03 +01:00
SetNextThink( 1.0f );
2017-12-18 00:39:44 +01:00
}
}
2016-07-31 15:48:50 +02:00
void CCineMonster::CineThink( void )
2017-12-18 00:39:44 +01:00
{
// ALERT(at_console, "Sequence think, activator %s\n", STRING(m_hActivator->pev->targetname));
if ((m_hTargetEnt = FindEntity(STRING(m_iszEntity),m_hActivator)) != NULL)
{
// ALERT(at_console, "Sequence found %s \"%s\"\n", STRING(m_hTargetEnt->pev->classname), STRING(m_hTargetEnt->pev->targetname));
2016-07-31 15:48:50 +02:00
PossessEntity();
2017-12-18 00:39:44 +01:00
ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) );
}
else
{
2016-07-31 15:48:50 +02:00
CancelScript();
2017-12-18 00:39:44 +01:00
ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) );
2019-11-10 22:59:56 +01:00
SetNextThink( 1.0f );
2017-12-18 00:39:44 +01:00
}
}
// lookup a sequence name and setup the target monster to play it
2016-07-31 15:48:50 +02:00
BOOL CCineMonster::StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( !iszSeq && completeOnEmpty )
2017-12-18 00:39:44 +01:00
{
SequenceDone( pTarget );
return FALSE;
}
pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) );
2016-07-31 15:48:50 +02:00
if( pTarget->pev->sequence == -1 )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq ) );
2017-12-18 00:39:44 +01:00
pTarget->pev->sequence = 0;
// return FALSE;
}
#if 0
char *s;
2016-07-31 15:48:50 +02:00
if( pev->spawnflags & SF_SCRIPT_NOINTERRUPT )
2017-12-18 00:39:44 +01:00
s = "No";
else
s = "Yes";
2016-07-31 15:48:50 +02:00
ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq ), s );
2017-12-18 00:39:44 +01:00
#endif
pTarget->pev->frame = 0;
2016-07-31 15:48:50 +02:00
pTarget->ResetSequenceInfo();
2017-12-18 00:39:44 +01:00
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.
//=========================================================
2016-07-31 15:48:50 +02:00
void CCineMonster::SequenceDone( CBaseMonster *pMonster )
2017-12-18 00:39:44 +01:00
{
m_iRepeatsLeft = m_iRepeats; //LRC - reset the repeater count
m_iState = STATE_OFF; // we've finished.
// ALERT( at_console, "Sequence %s finished\n", STRING(pev->targetname));//STRING( m_pCine->m_iszPlay ) );
2016-07-31 15:48:50 +02:00
if( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) )
2017-12-18 00:39:44 +01:00
{
SetThink(&CCineMonster :: SUB_Remove );
2019-11-10 22:59:56 +01:00
SetNextThink( 0.1f );
2017-12-18 00:39:44 +01:00
}
2017-12-18 00:39:44 +01:00
// This is done so that another sequence can take over the monster when triggered by the first
2017-12-18 00:39:44 +01:00
pMonster->CineCleanup();
FixScriptMonsterSchedule( pMonster );
2017-12-18 00:39:44 +01:00
// 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.
//
// or select a specific AMBUSH schedule, regardless of state. //LRC
//=========================================================
2016-07-31 15:48:50 +02:00
void CCineMonster::FixScriptMonsterSchedule( CBaseMonster *pMonster )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD )
2017-12-18 00:39:44 +01:00
pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE;
// pMonster->ClearSchedule();
switch ( m_iFinishSchedule )
{
case SCRIPT_FINISHSCHED_DEFAULT:
pMonster->ClearSchedule();
break;
case SCRIPT_FINISHSCHED_AMBUSH:
pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_AMBUSH ) );
break;
default:
2016-07-31 15:48:50 +02:00
ALERT( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" );
2017-12-18 00:39:44 +01:00
pMonster->ClearSchedule();
break;
}
}
2016-07-31 15:48:50 +02:00
BOOL CBaseMonster::ExitScriptedSequence()
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( pev->deadflag == DEAD_DYING )
2017-12-18 00:39:44 +01:00
{
// is this legal?
// BUGBUG -- This doesn't call Killed()
m_IdealMonsterState = MONSTERSTATE_DEAD;
return FALSE;
}
2016-07-31 15:48:50 +02:00
if( m_pCine )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
m_pCine->CancelScript();
2017-12-18 00:39:44 +01:00
}
return TRUE;
}
void CCineMonster::AllowInterrupt( BOOL fAllow )
{
2016-07-31 15:48:50 +02:00
if( pev->spawnflags & SF_SCRIPT_NOINTERRUPT )
2017-12-18 00:39:44 +01:00
return;
m_interruptable = fAllow;
}
BOOL CCineMonster::CanInterrupt( void )
{
2016-07-31 15:48:50 +02:00
if( !m_interruptable )
2017-12-18 00:39:44 +01:00
return FALSE;
CBaseEntity *pTarget = m_hTargetEnt;
2016-07-31 15:48:50 +02:00
if( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO )
2017-12-18 00:39:44 +01:00
return TRUE;
return FALSE;
}
int CCineMonster::IgnoreConditions( void )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( CanInterrupt() )
2017-12-18 00:39:44 +01:00
return 0;
// Big fat BUG: This is an IgnoreConditions function - we need to return the conditions
// that _shouldn't_ be able to break the script, instead of the conditions that _should_!!
return SCRIPT_BREAK_CONDITIONS;
}
void ScriptEntityCancel( edict_t *pentCine )
{
// make sure they are a scripted_sequence
if (FClassnameIs( pentCine, "scripted_sequence" ) || FClassnameIs( pentCine, "scripted_action" ))
{
2016-07-31 15:48:50 +02:00
CCineMonster *pCineTarget = GetClassPtr( (CCineMonster *)VARS( pentCine ) );
2023-10-07 04:01:40 +02:00
pCineTarget->m_iState = STATE_OFF;
2017-12-18 00:39:44 +01:00
// make sure they have a monster in mind for the script
2016-07-31 15:48:50 +02:00
CBaseEntity *pEntity = pCineTarget->m_hTargetEnt;
CBaseMonster *pTarget = NULL;
if( pEntity )
2017-12-18 00:39:44 +01:00
pTarget = pEntity->MyMonsterPointer();
2016-07-31 15:48:50 +02:00
if( pTarget )
2017-12-18 00:39:44 +01:00
{
// make sure their monster is actually playing a script
if( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT || pTarget->m_IdealMonsterState == MONSTERSTATE_SCRIPT )
2017-12-18 00:39:44 +01:00
{
// tell them do die
pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP;
// do it now
2016-07-31 15:48:50 +02:00
pTarget->CineCleanup();
2017-12-18 00:39:44 +01:00
//LRC - clean up so that if another script is starting immediately, the monster will notice it.
pTarget->ClearSchedule( );
}
}
}
}
// find all the cinematic entities with my targetname and stop them from playing
2016-07-31 15:48:50 +02:00
void CCineMonster::CancelScript( void )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
ALERT( at_aiconsole, "Cancelling script: %s\n", STRING( m_iszPlay ) );
2016-07-31 15:48:50 +02:00
if( !pev->targetname )
2017-12-18 00:39:44 +01:00
{
ScriptEntityCancel( edict() );
return;
}
CBaseEntity *pCineTarget = UTIL_FindEntityByTargetname(NULL, STRING(pev->targetname));
while (pCineTarget)
{
ScriptEntityCancel( ENT(pCineTarget->pev) );
pCineTarget = UTIL_FindEntityByTargetname(pCineTarget, STRING(pev->targetname));
}
}
// find all the cinematic entities with my targetname and tell them whether to wait before starting
2016-07-31 15:48:50 +02:00
void CCineMonster::DelayStart( int state )
2017-12-18 00:39:44 +01:00
{
CBaseEntity *pCine = UTIL_FindEntityByTargetname(NULL, STRING(pev->targetname));
while ( pCine )
{
if (FClassnameIs( pCine->pev, "scripted_sequence" ) || FClassnameIs( pCine->pev, "scripted_action" ))
{
CCineMonster *pTarget = GetClassPtr((CCineMonster *)(pCine->pev));
2016-07-31 15:48:50 +02:00
if( state )
2017-12-18 00:39:44 +01:00
{
// ALERT(at_console, "Delaying start\n");
pTarget->m_iDelay++;
}
else
{
pTarget->m_iDelay--;
2017-07-23 23:24:55 +02:00
if( pTarget->m_iDelay <= 0 )
2017-12-18 00:39:44 +01:00
{
pTarget->m_iState = STATE_ON; //LRC
FireTargets(STRING(m_iszFireOnBegin), this, this, USE_TOGGLE, 0); //LRC
2020-01-07 13:53:03 +01:00
pTarget->m_startTime = gpGlobals->time + 0.05f; // why the delay? -- LRC
2017-12-18 00:39:44 +01:00
}
}
}
pCine = UTIL_FindEntityByTargetname(pCine, STRING(pev->targetname));
}
}
// Find an entity that I'm interested in and precache the sounds he'll need in the sequence.
2016-07-31 15:48:50 +02:00
void CCineMonster::Activate( void )
2017-12-18 00:39:44 +01:00
{
CBaseEntity *pEntity;
2016-07-31 15:48:50 +02:00
CBaseMonster *pTarget;
2017-12-18 00:39:44 +01:00
// The entity name could be a target name or a classname
// Check the targetname
pEntity = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity));
pTarget = NULL;
while (!pTarget && pEntity)
{
if ( FBitSet( pEntity->pev->flags, FL_MONSTER ))
{
pTarget = pEntity->MyMonsterPointer( );
}
pEntity = UTIL_FindEntityByTargetname(pEntity, STRING(m_iszEntity));
}
2017-12-18 00:39:44 +01:00
// If no entity with that targetname, check the classname
if ( !pTarget )
{
pEntity = UTIL_FindEntityByClassname(NULL, STRING(m_iszEntity));
while (!pTarget && pEntity)
{
pTarget = pEntity->MyMonsterPointer( );
pEntity = UTIL_FindEntityByClassname(pEntity, STRING(m_iszEntity));
}
}
2017-12-18 00:39:44 +01:00
// Found a compatible entity
if ( pTarget )
{
void *pmodel;
pmodel = GET_MODEL_PTR( pTarget->edict() );
2016-07-31 15:48:50 +02:00
if( pmodel )
2017-12-18 00:39:44 +01:00
{
// Look through the event list for stuff to precache
SequencePrecache( pmodel, STRING( m_iszIdle ) );
SequencePrecache( pmodel, STRING( m_iszPlay ) );
}
}
CBaseMonster::Activate();
}
2016-07-31 15:48:50 +02:00
BOOL CBaseMonster::CineCleanup()
2017-12-18 00:39:44 +01:00
{
CCineMonster *pOldCine = m_pCine;
// am I linked to a cinematic?
2016-07-31 15:48:50 +02:00
if( m_pCine )
2017-12-18 00:39:44 +01:00
{
// okay, reset me to what it thought I was before
m_pCine->m_hTargetEnt = NULL;
pev->movetype = m_pCine->m_saved_movetype;
// LRC - why mess around with this? Solidity isn't changed by sequences!
// pev->solid = m_pCine->m_saved_solid;
if (m_pCine->pev->spawnflags & SF_SCRIPT_STAYDEAD)
pev->deadflag = DEAD_DYING;
}
else
{
// arg, punt
pev->movetype = MOVETYPE_STEP;// this is evil
pev->solid = SOLID_SLIDEBOX;
}
m_pCine = NULL;
m_hTargetEnt = NULL;
m_pGoalEnt = NULL;
2016-07-31 15:48:50 +02:00
if( pev->deadflag == DEAD_DYING )
2017-12-18 00:39:44 +01:00
{
// last frame of death animation?
2016-07-31 15:48:50 +02:00
pev->health = 0;
pev->framerate = 0.0;
pev->solid = SOLID_NOT;
2017-12-18 00:39:44 +01:00
SetState( MONSTERSTATE_DEAD );
pev->deadflag = DEAD_DEAD;
2016-07-31 15:48:50 +02:00
UTIL_SetSize( pev, pev->mins, Vector( pev->maxs.x, pev->maxs.y, pev->mins.z + 2 ) );
2017-12-18 00:39:44 +01:00
2016-07-31 15:48:50 +02:00
if( pOldCine && FBitSet( pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE ) )
2017-12-18 00:39:44 +01:00
{
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
2016-07-31 15:48:50 +02:00
if( pOldCine && pOldCine->m_iszPlay )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( !( pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT ) )
2017-12-18 00:39:44 +01:00
{
// 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.
2019-10-13 13:49:25 +02:00
if( ( oldOrigin - new_origin).Length2D() < 8.0f )
2017-12-18 00:39:44 +01:00
new_origin = oldOrigin;
pev->origin.x = new_origin.x;
pev->origin.y = new_origin.y;
pev->origin.z += 1;
pev->flags |= FL_ONGROUND;
2016-07-31 15:48:50 +02:00
int drop = DROP_TO_FLOOR( ENT( pev ) );
2017-12-18 00:39:44 +01:00
// Origin in solid? Set to org at the end of the sequence
2016-07-31 15:48:50 +02:00
if( drop < 0 )
2017-12-18 00:39:44 +01:00
pev->origin = oldOrigin;
2016-07-31 15:48:50 +02:00
else if( drop == 0 ) // Hanging in air?
2017-12-18 00:39:44 +01:00
{
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( this, 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;
2016-07-31 15:48:50 +02:00
if( pev->health > 0 )
2017-12-18 00:39:44 +01:00
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 );
//LRC- removed, was never implemented. 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 );
void EXPORT DurationThink( void );
2016-07-31 15:48:50 +02:00
int ObjectCaps( void ) { return ( CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION ); }
2017-12-18 00:39:44 +01:00
STATE GetState() { return m_playing?STATE_ON:STATE_OFF; }
2016-07-31 15:48:50 +02:00
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
2017-12-18 00:39:44 +01:00
2016-07-31 15:48:50 +02:00
static TYPEDESCRIPTION m_SaveData[];
2017-12-18 00:39:44 +01:00
2024-02-22 21:46:33 +01:00
CBaseToggle *FindEntity( CBaseEntity *pActivator );
BOOL AcceptableSpeaker( CBaseToggle *pTarget );
BOOL StartSentence( CBaseToggle *pTarget );
2017-12-18 00:39:44 +01:00
private:
int m_iszSentence; // string index for idle animation
int m_iszEntity; // entity that is wanted for this sentence
2016-07-31 15:48:50 +02:00
float m_flRadius; // range to search
float m_flDuration; // How long the sentence lasts
2017-12-18 00:39:44 +01:00
float m_flRepeat; // maximum repeat rate
2016-07-31 15:48:50 +02:00
float m_flAttenuation;
float m_flVolume;
2017-12-18 00:39:44 +01:00
BOOL m_active; // is the sentence enabled? (for m_flRepeat)
BOOL m_playing; //LRC- is the sentence playing? (for GetState)
int m_iszListener; // name of entity to look at while talking
};
2016-07-31 15:48:50 +02:00
#define SF_SENTENCE_ONCE 0x0001
2017-12-18 00:39:44 +01:00
#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[] =
2017-12-18 00:39:44 +01:00
{
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_playing, FIELD_BOOLEAN ),
DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ),
};
IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseToggle )
2017-12-18 00:39:44 +01:00
LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence )
2017-12-18 00:39:44 +01:00
2016-07-31 15:48:50 +02:00
void CScriptedSentence::KeyValue( KeyValueData *pkvd )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( FStrEq( pkvd->szKeyName, "sentence" ) )
2017-12-18 00:39:44 +01:00
{
m_iszSentence = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "entity" ) )
2017-12-18 00:39:44 +01:00
{
m_iszEntity = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "duration" ) )
2017-12-18 00:39:44 +01:00
{
m_flDuration = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "radius" ) )
2017-12-18 00:39:44 +01:00
{
m_flRadius = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "refire") )
2017-12-18 00:39:44 +01:00
{
m_flRepeat = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "attenuation" ) )
2017-12-18 00:39:44 +01:00
{
pev->impulse = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "volume" ) )
2017-12-18 00:39:44 +01:00
{
2019-10-13 13:49:25 +02:00
m_flVolume = atof( pkvd->szValue ) * 0.1f;
2017-12-18 00:39:44 +01:00
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "listener" ) )
2017-12-18 00:39:44 +01:00
{
m_iszListener = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
CBaseToggle::KeyValue( pkvd );
}
2016-07-31 15:48:50 +02:00
void CScriptedSentence::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( !m_active )
2017-12-18 00:39:44 +01:00
return;
2016-07-31 15:48:50 +02:00
//ALERT( at_console, "Firing sentence: %s\n", STRING( m_iszSentence ) );
2017-12-18 00:39:44 +01:00
m_hActivator = pActivator;
2016-06-04 15:24:23 +02:00
SetThink( &CScriptedSentence::FindThink );
2017-12-18 00:39:44 +01:00
SetNextThink( 0 );
}
2016-07-31 15:48:50 +02:00
void CScriptedSentence::Spawn( void )
2017-12-18 00:39:44 +01:00
{
pev->solid = SOLID_NOT;
2017-12-18 00:39:44 +01:00
m_active = TRUE;
m_playing = FALSE; //LRC
// if no targetname, start now
2016-07-31 15:48:50 +02:00
if( !pev->targetname )
2017-12-18 00:39:44 +01:00
{
2016-06-04 15:24:23 +02:00
SetThink( &CScriptedSentence::FindThink );
2019-11-10 22:59:56 +01:00
SetNextThink( 1.0f );
2017-12-18 00:39:44 +01:00
}
switch( pev->impulse )
{
2016-07-31 15:48:50 +02:00
case 1:
// Medium radius
2017-12-18 00:39:44 +01:00
m_flAttenuation = ATTN_STATIC;
break;
2016-07-31 15:48:50 +02:00
case 2:
// Large radius
2017-12-18 00:39:44 +01:00
m_flAttenuation = ATTN_NORM;
break;
2016-07-31 15:48:50 +02:00
case 3:
//EVERYWHERE
2017-12-18 00:39:44 +01:00
m_flAttenuation = ATTN_NONE;
break;
default:
2016-07-31 15:48:50 +02:00
case 0:
// Small radius
2017-12-18 00:39:44 +01:00
m_flAttenuation = ATTN_IDLE;
break;
}
pev->impulse = 0;
// No volume, use normal
2019-10-13 13:49:25 +02:00
if( m_flVolume <= 0.0f )
m_flVolume = 1.0f;
2017-12-18 00:39:44 +01:00
}
2016-07-31 15:48:50 +02:00
void CScriptedSentence::FindThink( void )
2017-12-18 00:39:44 +01:00
{
if (!m_iszEntity) //LRC- no target monster given: speak through HEV
{
CBasePlayer* pPlayer = (CBasePlayer*)UTIL_FindEntityByClassname( NULL, "player" );
if (pPlayer)
{
m_playing = TRUE;
if ((STRING(m_iszSentence))[0] == '!')
2017-12-18 21:47:12 +01:00
pPlayer->SetSuitUpdate(STRING(m_iszSentence),FALSE,0);
2017-12-18 00:39:44 +01:00
else
2017-12-18 21:47:12 +01:00
pPlayer->SetSuitUpdate(STRING(m_iszSentence),TRUE,0);
2017-12-18 00:39:44 +01:00
if ( pev->spawnflags & SF_SENTENCE_ONCE )
UTIL_Remove( this );
SetThink(&CScriptedSentence :: DurationThink );
SetNextThink( m_flDuration );
m_active = FALSE;
}
else
2017-12-18 21:47:12 +01:00
ALERT( at_console, "ScriptedSentence: can't find \"player\" to play HEV sentence!?\n");
2017-12-18 00:39:44 +01:00
return;
}
2024-02-22 21:46:33 +01:00
CBaseToggle *pTarget = FindEntity( m_hActivator );
if( pTarget )
2017-12-18 00:39:44 +01:00
{
m_playing = TRUE;
StartSentence( pTarget );
2016-07-31 15:48:50 +02:00
if( pev->spawnflags & SF_SENTENCE_ONCE )
2017-12-18 00:39:44 +01:00
UTIL_Remove( this );
SetThink(&CScriptedSentence :: DurationThink );
SetNextThink( m_flDuration );
m_active = FALSE;
//ALERT( at_console, "%s: found target %s\n", STRING( m_iszSentence ), STRING( m_iszEntity ) );
2017-12-18 00:39:44 +01:00
}
else
{
//ALERT( at_console, "%s: can't find target %s\n", STRING( m_iszSentence ), STRING( m_iszEntity ) );
2019-11-10 22:59:56 +01:00
SetNextThink( m_flRepeat + 0.5f );
2017-12-18 00:39:44 +01:00
}
}
//LRC
void CScriptedSentence :: DurationThink( void )
{
m_playing = FALSE;
SetNextThink( m_flRepeat );
SetThink(&CScriptedSentence :: DelayThink );
}
2016-07-31 15:48:50 +02:00
void CScriptedSentence::DelayThink( void )
2017-12-18 00:39:44 +01:00
{
m_active = TRUE;
2016-07-31 15:48:50 +02:00
if( !pev->targetname )
2019-11-10 22:59:56 +01:00
SetNextThink( 0.1f );
2016-06-04 15:24:23 +02:00
SetThink( &CScriptedSentence::FindThink );
2017-12-18 00:39:44 +01:00
}
BOOL CScriptedSentence::AcceptableSpeaker( CBaseToggle *pTarget )
2017-12-18 00:39:44 +01:00
{
CBaseMonster *pMonster;
EHANDLE hTarget;
if( pTarget )
2017-12-18 00:39:44 +01:00
{
hTarget = pTarget->MyMonsterPointer();
if( hTarget != 0 )
2017-12-18 00:39:44 +01:00
{
CBaseMonster *pMonster = (CBaseMonster*)( (CBaseEntity*)hTarget );
if( pev->spawnflags & SF_SENTENCE_FOLLOWERS )
{
if( pMonster->m_hTargetEnt == 0 || !pMonster->m_hTargetEnt->IsPlayer() )
return FALSE;
}
2019-09-24 00:00:37 +02:00
BOOL override;
2019-09-24 00:00:37 +02:00
if( pev->spawnflags & SF_SENTENCE_INTERRUPT )
override = TRUE;
else
override = FALSE;
2019-09-24 00:00:37 +02:00
if( pMonster->CanPlaySentence( override ) )
return TRUE;
}
else
return pTarget->IsAllowedToSpeak();
2017-12-18 00:39:44 +01:00
}
2017-12-18 00:39:44 +01:00
return FALSE;
}
2024-02-22 21:46:33 +01:00
CBaseToggle *CScriptedSentence :: FindEntity( CBaseEntity *pActivator )
2017-12-18 00:39:44 +01:00
{
CBaseEntity *pTarget;
2024-02-22 21:46:33 +01:00
CBaseToggle *peTarget;
2017-12-18 00:39:44 +01:00
pTarget = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity), pActivator);
2024-02-22 21:46:33 +01:00
peTarget = NULL;
2017-12-18 00:39:44 +01:00
while ( pTarget )
{
2024-02-22 21:46:33 +01:00
peTarget = pTarget->MyTogglePointer( );
if( peTarget != NULL )
2017-12-18 00:39:44 +01:00
{
2024-02-22 21:46:33 +01:00
if( AcceptableSpeaker( peTarget ) )
return peTarget;
//ALERT( at_console, "%s (%s), not acceptable\n", STRING( pTarget->pev->classname ), STRING( pTarget->pev->targetname ) );
2017-12-18 00:39:44 +01:00
}
pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(m_iszEntity), pActivator);
}
2016-07-31 15:48:50 +02:00
2017-12-18 00:39:44 +01:00
pTarget = NULL;
while ((pTarget = UTIL_FindEntityInSphere( pTarget, pev->origin, m_flRadius )) != NULL)
{
if (FClassnameIs( pTarget->pev, STRING(m_iszEntity)))
{
if ( FBitSet( pTarget->pev->flags, FL_MONSTER ))
{
2024-02-22 21:46:33 +01:00
peTarget = pTarget->MyTogglePointer( );
if( AcceptableSpeaker( peTarget ) )
return peTarget;
2017-12-18 00:39:44 +01:00
}
}
}
return NULL;
}
BOOL CScriptedSentence::StartSentence( CBaseToggle *pTarget )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
if( !pTarget )
2017-12-18 00:39:44 +01:00
{
2016-07-31 15:48:50 +02:00
ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING( m_iszSentence ) );
2017-06-29 15:56:03 +02:00
return FALSE;
2017-12-18 00:39:44 +01:00
}
BOOL bConcurrent = FALSE;
//LRC: Er... if the "concurrent" flag is NOT set, we make bConcurrent true!?
2016-07-31 15:48:50 +02:00
if( !( pev->spawnflags & SF_SENTENCE_CONCURRENT ) )
2017-12-18 00:39:44 +01:00
bConcurrent = TRUE;
CBaseEntity *pListener = NULL;
2016-07-31 15:48:50 +02:00
if( !FStringNull( m_iszListener ) )
2017-12-18 00:39:44 +01:00
{
float radius = m_flRadius;
2016-07-31 15:48:50 +02:00
if( FStrEq( STRING( m_iszListener ), "player" ) )
2017-12-18 00:39:44 +01:00
radius = 4096; // Always find the player
pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius );
}
2016-07-31 15:48:50 +02:00
pTarget->PlayScriptedSentence( STRING( m_iszSentence ), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener );
2019-10-13 13:49:25 +02:00
ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING( m_iszSentence ), (double)m_flDuration );
2017-12-18 00:39:44 +01:00
SUB_UseTargets( NULL, USE_TOGGLE, 0 );
return TRUE;
}
//=========================================================
// Furniture - this is the cool comment I cut-and-pasted
//=========================================================
class CFurniture : public CBaseMonster
{
public:
2016-07-31 15:48:50 +02:00
void Spawn( void );
2017-12-18 00:39:44 +01:00
void Die( void );
2016-07-31 15:48:50 +02:00
int Classify( void );
virtual int ObjectCaps( void ) { return (CBaseMonster::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); }
2017-12-18 00:39:44 +01:00
};
LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture )
2017-12-18 00:39:44 +01:00
//=========================================================
// Furniture is killed
//=========================================================
2016-07-31 15:48:50 +02:00
void CFurniture::Die( void )
2017-12-18 00:39:44 +01:00
{
2016-06-04 15:24:23 +02:00
SetThink( &CBaseEntity::SUB_Remove );
pev->nextthink = gpGlobals->time;
2017-12-18 00:39:44 +01:00
}
//=========================================================
// This used to have something to do with bees flying, but
// now it only initializes moving furniture in scripted sequences
//=========================================================
2016-07-31 15:48:50 +02:00
void CFurniture::Spawn()
2017-12-18 00:39:44 +01:00
{
2017-07-23 23:24:55 +02:00
PRECACHE_MODEL( STRING( pev->model ) );
2016-07-31 15:48:50 +02:00
SET_MODEL( ENT( pev ), STRING( pev->model ) );
2017-12-18 00:39:44 +01:00
2016-07-31 15:48:50 +02:00
pev->movetype = MOVETYPE_NONE;
pev->solid = SOLID_BBOX;
pev->health = 80000;
2017-12-18 00:39:44 +01:00
pev->takedamage = DAMAGE_AIM;
2016-07-31 15:48:50 +02:00
pev->effects = 0;
pev->yaw_speed = 0;
pev->sequence = 0;
pev->frame = 0;
2017-12-18 00:39:44 +01:00
2019-10-13 13:49:25 +02:00
//pev->nextthink += 1.0f;
2016-07-31 15:48:50 +02:00
//SetThink( &WalkMonsterDelay );
2017-12-18 00:39:44 +01:00
2016-07-31 15:48:50 +02:00
ResetSequenceInfo();
2017-12-18 00:39:44 +01:00
pev->frame = 0;
MonsterInit();
}
//=========================================================
// ID's Furniture as neutral (noone will attack it)
//=========================================================
2016-07-31 15:48:50 +02:00
int CFurniture::Classify( void )
2017-12-18 00:39:44 +01:00
{
return m_iClass?m_iClass:CLASS_NONE;
}