You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
559 lines
14 KiB
559 lines
14 KiB
/*** |
|
* |
|
* 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; // 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 ) |
|
{ |
|
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; |
|
} |
|
|
|
|
|
/* |
|
============= |
|
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; |
|
} |
|
|
|
|
|
|