hlsdk-xash3d/dlls/subs.cpp

567 lines
14 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.
*
* Use, distribution, and modification of this source code and/or resulting
* object code is restricted to non-commercial enhancements to products from
* Valve LLC. All other use, distribution, or modification is prohibited
* without written permission from Valve LLC.
*
****/
/*
===== subs.cpp ========================================================
frequently used global functions
*/
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "saverestore.h"
#include "nodes.h"
#include "doors.h"
extern CGraph WorldGraph;
extern BOOL FEntIsVisible( entvars_t *pev, entvars_t *pevTarget );
extern DLL_GLOBAL int g_iSkillLevel;
// Landmark class
void CPointEntity::Spawn( void )
{
pev->solid = SOLID_NOT;
//UTIL_SetSize( pev, g_vecZero, g_vecZero );
}
class CNullEntity : public CBaseEntity
{
public:
void Spawn( void );
};
// Null Entity, remove on startup
void CNullEntity::Spawn( void )
{
REMOVE_ENTITY( ENT( pev ) );
}
LINK_ENTITY_TO_CLASS( info_null, CNullEntity )
class CBaseDMStart : public CPointEntity
{
public:
void KeyValue( KeyValueData *pkvd );
BOOL IsTriggered( CBaseEntity *pEntity );
private:
};
// These are the new entry points to entities.
LINK_ENTITY_TO_CLASS( info_player_deathmatch, CBaseDMStart )
LINK_ENTITY_TO_CLASS( info_player_start, CPointEntity )
LINK_ENTITY_TO_CLASS( info_landmark, CPointEntity )
void CBaseDMStart::KeyValue( KeyValueData *pkvd )
{
if( FStrEq( pkvd->szKeyName, "master" ) )
{
pev->netname = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
CPointEntity::KeyValue( pkvd );
}
BOOL CBaseDMStart::IsTriggered( CBaseEntity *pEntity )
{
BOOL master = UTIL_IsMasterTriggered( pev->netname, pEntity );
return master;
}
// This updates global tables that need to know about entities being removed
void CBaseEntity::UpdateOnRemove( void )
{
int i;
if( FBitSet( pev->flags, FL_GRAPHED ) )
{
// this entity was a LinkEnt in the world node graph, so we must remove it from
// the graph since we are removing it from the world.
for( i = 0; i < WorldGraph.m_cLinks; i++ )
{
if( WorldGraph.m_pLinkPool[i].m_pLinkEnt == pev )
{
// if this link has a link ent which is the same ent that is removing itself, remove it!
WorldGraph.m_pLinkPool[i].m_pLinkEnt = NULL;
}
}
}
if( pev->globalname )
gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD );
}
// Convenient way to delay removing oneself
void CBaseEntity::SUB_Remove( void )
{
UpdateOnRemove();
if( pev->health > 0 )
{
// this situation can screw up monsters who can't tell their entity pointers are invalid.
pev->health = 0;
ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n" );
}
REMOVE_ENTITY( ENT( pev ) );
}
// Convenient way to explicitly do nothing (passed to functions that require a method)
void CBaseEntity::SUB_DoNothing( void )
{
}
// Global Savedata for Delay
TYPEDESCRIPTION CBaseDelay::m_SaveData[] =
{
DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ),
DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ),
};
IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity )
void CBaseDelay::KeyValue( KeyValueData *pkvd )
{
if( FStrEq( pkvd->szKeyName, "delay" ) )
{
m_flDelay = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "killtarget" ) )
{
m_iszKillTarget = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
{
CBaseEntity::KeyValue( pkvd );
}
}
/*
==============================
SUB_UseTargets
If self.delay is set, a DelayedUse entity will be created that will actually
do the SUB_UseTargets after that many seconds have passed.
Removes all entities with a targetname that match self.killtarget,
and removes them, so some events can remove other triggers.
Search for (string)targetname in all entities that
match (string)self.target and call their .use function (if they have one)
==============================
*/
void CBaseEntity::SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value )
{
//
// fire targets
//
if( !FStringNull( pev->target ) )
{
FireTargets( STRING( pev->target ), pActivator, this, useType, value );
}
}
void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
edict_t *pentTarget = NULL;
if( !targetName )
return;
ALERT( at_aiconsole, "Firing: (%s)\n", targetName );
for( ; ; )
{
pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, targetName );
if( FNullEnt( pentTarget ) )
break;
CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget );
if( pTarget && !( pTarget->pev->flags & FL_KILLME ) ) // Don't use dying ents
{
ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING( pTarget->pev->classname ), targetName );
pTarget->Use( pActivator, pCaller, useType, value );
}
}
}
LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay )
void CBaseDelay::SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value )
{
//
// exit immediatly if we don't have a target or kill target
//
if( FStringNull( pev->target ) && !m_iszKillTarget )
return;
//
// check for a delay
//
if( m_flDelay != 0 )
{
// create a temp object to fire at a later time
CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL );
pTemp->pev->classname = MAKE_STRING( "DelayedUse" );
pTemp->pev->nextthink = gpGlobals->time + m_flDelay;
pTemp->SetThink( &CBaseDelay::DelayThink );
// Save the useType
pTemp->pev->button = (int)useType;
pTemp->m_iszKillTarget = m_iszKillTarget;
pTemp->m_flDelay = 0.0f; // prevent "recursion"
pTemp->pev->target = pev->target;
// HACKHACK
// This wasn't in the release build of Half-Life. We should have moved m_hActivator into this class
// but changing member variable hierarchy would break save/restore without some ugly code.
// This code is not as ugly as that code
if( pActivator && pActivator->IsPlayer() ) // If a player activates, then save it
{
pTemp->pev->owner = pActivator->edict();
}
else
{
pTemp->pev->owner = NULL;
}
return;
}
//
// kill the killtargets
//
if( m_iszKillTarget )
{
edict_t *pentKillTarget = NULL;
ALERT( at_aiconsole, "KillTarget: %s\n", STRING( m_iszKillTarget ) );
pentKillTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_iszKillTarget ) );
while( !FNullEnt(pentKillTarget) )
{
UTIL_Remove( CBaseEntity::Instance( pentKillTarget ) );
ALERT( at_aiconsole, "killing %s\n", STRING( pentKillTarget->v.classname ) );
pentKillTarget = FIND_ENTITY_BY_TARGETNAME( pentKillTarget, STRING( m_iszKillTarget ) );
}
}
//
// fire targets
//
if( !FStringNull( pev->target ) )
{
FireTargets( STRING( pev->target ), pActivator, this, useType, value );
}
}
/*
void CBaseDelay::SUB_UseTargetsEntMethod( void )
{
SUB_UseTargets( pev );
}
*/
/*
QuakeEd only writes a single float for angles (bad idea), so up and down are
just constant angles.
*/
void SetMovedir( entvars_t *pev )
{
if( pev->angles == Vector( 0, -1, 0 ) )
{
pev->movedir = Vector( 0, 0, 1 );
}
else if (pev->angles == Vector( 0, -2, 0 ) )
{
pev->movedir = Vector( 0, 0, -1 );
}
else
{
UTIL_MakeVectors( pev->angles );
pev->movedir = gpGlobals->v_forward;
}
pev->angles = g_vecZero;
}
void CBaseDelay::DelayThink( void )
{
CBaseEntity *pActivator = NULL;
if( pev->owner != NULL ) // A player activated this on delay
{
pActivator = CBaseEntity::Instance( pev->owner );
}
// The use type is cached (and stashed) in pev->button
SUB_UseTargets( pActivator, (USE_TYPE)pev->button, 0 );
REMOVE_ENTITY( ENT( pev ) );
}
// Global Savedata for Toggle
TYPEDESCRIPTION CBaseToggle::m_SaveData[] =
{
DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ),
DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ),
DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ),
DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ),
DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ),
DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ),
DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ),
DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle?
DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle?
DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ),
DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ),
DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ),
DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ),
DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ),
DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING),
DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted
};
IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating )
void CBaseToggle::KeyValue( KeyValueData *pkvd )
{
if( FStrEq(pkvd->szKeyName, "lip" ) )
{
m_flLip = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "wait" ) )
{
m_flWait = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "master" ) )
{
m_sMaster = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "distance" ) )
{
m_flMoveDistance = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
CBaseDelay::KeyValue( pkvd );
}
/*
=============
LinearMove
calculate pev->velocity and pev->nextthink to reach vecDest from
pev->origin traveling at flSpeed
===============
*/
void CBaseToggle::LinearMove( Vector vecDest, float flSpeed )
{
ASSERTSZ( flSpeed != 0, "LinearMove: no speed is defined!" );
//ASSERTSZ( m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined" );
m_vecFinalDest = vecDest;
// Already there?
if( vecDest == pev->origin )
{
LinearMoveDone();
return;
}
// set destdelta to the vector needed to move
Vector vecDestDelta = vecDest - pev->origin;
// divide vector length by speed to get time to reach dest
float flTravelTime = vecDestDelta.Length() / flSpeed;
// set nextthink to trigger a call to LinearMoveDone when dest is reached
pev->nextthink = pev->ltime + flTravelTime;
SetThink( &CBaseToggle::LinearMoveDone );
// scale the destdelta vector by the time spent traveling to get velocity
pev->velocity = vecDestDelta / flTravelTime;
}
/*
============
After moving, set origin to exact final destination, call "move done" function
============
*/
void CBaseToggle::LinearMoveDone( void )
{
Vector delta = m_vecFinalDest - pev->origin;
float error = delta.Length();
if( error > 0.03125f )
{
LinearMove( m_vecFinalDest, 100 );
return;
}
UTIL_SetOrigin( pev, m_vecFinalDest );
pev->velocity = g_vecZero;
pev->nextthink = -1;
if( m_pfnCallWhenMoveDone )
( this->*m_pfnCallWhenMoveDone )();
}
BOOL CBaseToggle::IsLockedByMaster( void )
{
if( m_sMaster && !UTIL_IsMasterTriggered( m_sMaster, m_hActivator ) )
return TRUE;
else
return FALSE;
}
/*
=============
AngularMove
calculate pev->velocity and pev->nextthink to reach vecDest from
pev->origin traveling at flSpeed
Just like LinearMove, but rotational.
===============
*/
void CBaseToggle::AngularMove( Vector vecDestAngle, float flSpeed )
{
ASSERTSZ( flSpeed != 0, "AngularMove: no speed is defined!" );
//ASSERTSZ( m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined" );
m_vecFinalAngle = vecDestAngle;
// Already there?
if( vecDestAngle == pev->angles )
{
AngularMoveDone();
return;
}
// set destdelta to the vector needed to move
Vector vecDestDelta = vecDestAngle - pev->angles;
// divide by speed to get time to reach dest
float flTravelTime = vecDestDelta.Length() / flSpeed;
// set nextthink to trigger a call to AngularMoveDone when dest is reached
pev->nextthink = pev->ltime + flTravelTime;
SetThink( &CBaseToggle::AngularMoveDone );
// scale the destdelta vector by the time spent traveling to get velocity
pev->avelocity = vecDestDelta / flTravelTime;
}
/*
============
After rotating, set angle to exact final angle, call "move done" function
============
*/
void CBaseToggle::AngularMoveDone( void )
{
pev->angles = m_vecFinalAngle;
pev->avelocity = g_vecZero;
pev->nextthink = -1;
if( m_pfnCallWhenMoveDone )
( this->*m_pfnCallWhenMoveDone )();
}
float CBaseToggle::AxisValue( int flags, const Vector &angles )
{
if( FBitSet( flags, SF_DOOR_ROTATE_Z ) )
return angles.z;
if( FBitSet( flags, SF_DOOR_ROTATE_X ) )
return angles.x;
return angles.y;
}
void CBaseToggle::AxisDir( entvars_t *pev )
{
if( FBitSet( pev->spawnflags, SF_DOOR_ROTATE_Z ) )
pev->movedir = Vector( 0, 0, 1 ); // around z-axis
else if( FBitSet( pev->spawnflags, SF_DOOR_ROTATE_X ) )
pev->movedir = Vector( 1, 0, 0 ); // around x-axis
else
pev->movedir = Vector( 0, 1, 0 ); // around y-axis
}
float CBaseToggle::AxisDelta( int flags, const Vector &angle1, const Vector &angle2 )
{
if( FBitSet( flags, SF_DOOR_ROTATE_Z ) )
return angle1.z - angle2.z;
if( FBitSet( flags, SF_DOOR_ROTATE_X ) )
return angle1.x - angle2.x;
return angle1.y - angle2.y;
}
void CBaseToggle::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation )
{
if( pszSentence && IsAllowedToSpeak())
{
if( pszSentence[0] == '!' )
EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, PITCH_NORM );
else
SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, PITCH_NORM );
}
}
void CBaseToggle::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener )
{
PlaySentence( pszSentence, duration, volume, attenuation );
}
void CBaseToggle::SentenceStop( void )
{
EMIT_SOUND( edict(), CHAN_VOICE, "common/null.wav", 1.0, ATTN_IDLE );
}
/*
=============
FEntIsVisible
returns TRUE if the passed entity is visible to caller, even if not infront ()
=============
*/
BOOL FEntIsVisible( entvars_t *pev, entvars_t *pevTarget)
{
Vector vecSpot1 = pev->origin + pev->view_ofs;
Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs;
TraceResult tr;
UTIL_TraceLine( vecSpot1, vecSpot2, ignore_monsters, ENT( pev ), &tr );
if( tr.fInOpen && tr.fInWater )
return FALSE; // sight line crossed contents
if( tr.flFraction == 1 )
return TRUE;
return FALSE;
}