Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
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.

541 lines
14 KiB

9 years ago
/***
*
* 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);
9 years ago
}
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)
9 years ago
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)
9 years ago
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[] =
9 years ago
{
DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ),
DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ),
};
IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity )
9 years ago
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 )
9 years ago
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 );
9 years ago
// 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 );
}
9 years ago
// 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[] =
9 years ago
{
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 )
9 years ago
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");
9 years ago
m_vecFinalDest = vecDest;
// Already there?
if (vecDest == pev->origin)
{
LinearMoveDone();
return;
}
9 years ago
// set destdelta to the vector needed to move
Vector vecDestDelta = vecDest - pev->origin;
9 years ago
// 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");
9 years ago
m_vecFinalAngle = vecDestAngle;
// Already there?
if (vecDestAngle == pev->angles)
{
AngularMoveDone();
return;
}
9 years ago
// set destdelta to the vector needed to move
Vector vecDestDelta = vecDestAngle - pev->angles;
9 years ago
// 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;
9 years ago
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)
{
9 years ago
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);
9 years ago
if (tr.fInOpen && tr.fInWater)
return FALSE; // sight line crossed contents
if (tr.flFraction == 1)
return TRUE;
return FALSE;
}