/*** * * 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" #include "movewith.h" #include "player.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; } 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); LINK_ENTITY_TO_CLASS(info_texlights,CNullEntity); // don't complain about Merl's new info entities LINK_ENTITY_TO_CLASS(info_compile_parameters,CNullEntity); class CBaseDMStart : public CPointEntity { public: void KeyValue( KeyValueData *pkvd ); STATE GetState( 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 ); } STATE CBaseDMStart::GetState( CBaseEntity *pEntity ) { if (UTIL_IsMasterTriggered( pev->netname, pEntity )) return STATE_ON; else return STATE_OFF; } // This updates global tables that need to know about entities being removed void CBaseEntity::UpdateOnRemove( void ) { int i; CBaseEntity* pTemp; if (!g_pWorld) { ALERT(at_debug, "UpdateOnRemove has no AssistList!\n"); return; } //LRC - remove this from the AssistList. for (pTemp = g_pWorld; pTemp->m_pAssistLink != NULL; pTemp = pTemp->m_pAssistLink) { if (this == pTemp->m_pAssistLink) { // ALERT(at_console,"REMOVE: %s removed from the Assist List.\n", STRING(pev->classname)); pTemp->m_pAssistLink = this->m_pAssistLink; this->m_pAssistLink = NULL; break; } } //LRC if (m_pMoveWith) { // if I'm moving with another entity, take me out of the list. (otherwise things crash!) pTemp = m_pMoveWith->m_pChildMoveWith; if (pTemp == this) { m_pMoveWith->m_pChildMoveWith = this->m_pSiblingMoveWith; } else { while (pTemp->m_pSiblingMoveWith) { if (pTemp->m_pSiblingMoveWith == this) { pTemp->m_pSiblingMoveWith = this->m_pSiblingMoveWith; break; } pTemp = pTemp->m_pSiblingMoveWith; } } // ALERT(at_console,"REMOVE: %s removed from the %s ChildMoveWith list.\n", STRING(pev->classname), STRING(m_pMoveWith->pev->targetname)); } //LRC - do the same thing if another entity is moving with _me_. if (m_pChildMoveWith) { CBaseEntity* pCur = m_pChildMoveWith; CBaseEntity* pNext; while (pCur != NULL) { pNext = pCur->m_pSiblingMoveWith; // bring children to a stop UTIL_SetMoveWithVelocity(pCur, g_vecZero, 100); UTIL_SetMoveWithAvelocity(pCur, g_vecZero, 100); pCur->m_pMoveWith = NULL; pCur->m_pSiblingMoveWith = NULL; pCur = pNext; } } 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 ) { // if (pev->ltime) // ALERT(at_console, "Doing Nothing %f\n", pev->ltime); // else // ALERT(at_console, "Doing Nothing %f\n", gpGlobals->time); } // Global Savedata for Delay TYPEDESCRIPTION CBaseDelay::m_SaveData[] = { DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), DEFINE_FIELD( CBaseDelay, m_hActivator, FIELD_EHANDLE ), //LRC }; 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 ) { const char *inputTargetName = targetName; CBaseEntity *inputActivator = pActivator; CBaseEntity *pTarget = NULL; int i,j, found = false; char szBuf[80]; if ( !targetName ) return; if (useType == USE_NOT) return; //LRC - allow changing of usetype if (targetName[0] == '+') { targetName++; useType = USE_ON; } else if (targetName[0] == '-') { targetName++; useType = USE_OFF; } ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); pTarget = UTIL_FindEntityByTargetname(pTarget, targetName, pActivator); if( !pTarget ) { // it's not an entity name; check for a locus specifier, e.g: "fadein(mywall)" for (i = 0; targetName[i]; i++) { if (targetName[i] == '(') { i++; for (j = i; targetName[j]; j++) { if (targetName[j] == ')') { strncpy(szBuf, targetName+i, j-i); szBuf[j-i] = 0; pActivator = UTIL_FindEntityByTargetname(NULL, szBuf, inputActivator); if (!pActivator) { //ALERT(at_console, "Missing activator \"%s\"\n", szBuf); return; // it's a locus specifier, but the locus is invalid. } //ALERT(at_console, "Found activator \"%s\"\n", STRING(pActivator->pev->targetname)); found = true; break; } } if (!found) ALERT(at_error, "Missing ')' in target value \"%s\"", inputTargetName); break; } } if (!found) return; // no, it's not a locus specifier. strncpy(szBuf, targetName, i-1); szBuf[i-1] = 0; targetName = szBuf; pTarget = UTIL_FindEntityByTargetname(NULL, targetName, inputActivator); if (!pTarget) return; // it's a locus specifier all right, but the target's invalid. } do // start firing targets { if ( !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents { if (useType == USE_KILL) { ALERT( at_aiconsole, "Use_kill on %s\n", STRING( pTarget->pev->classname ) ); UTIL_Remove( pTarget ); } else { ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); pTarget->Use( pActivator, pCaller, useType, value ); } } pTarget = UTIL_FindEntityByTargetname(pTarget, targetName, inputActivator); } while (pTarget); //LRC- Firing has finished, aliases can now reflect their new values. UTIL_FlushAliases(); } 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->SetNextThink( 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; //LRC - Valve had a hacked thing here to avoid breaking // save/restore. In Spirit that's not a problem. // I've moved m_hActivator into this class, for the "elegant" fix. pTemp->m_hActivator = pActivator; return; } // // kill the killtargets // if ( m_iszKillTarget ) { edict_t *pentKillTarget = NULL; ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); //LRC- now just USE_KILLs its killtarget, for consistency. FireTargets( STRING(m_iszKillTarget), pActivator, this, USE_KILL, 0); } // // 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 ) { pev->movedir = GetMovedir(pev->angles); pev->angles = g_vecZero; } Vector GetMovedir( Vector vecAngles ) { if (vecAngles == Vector(0, -1, 0)) { return Vector(0, 0, 1); } else if (vecAngles == Vector(0, -2, 0)) { return Vector(0, 0, -1); } else { UTIL_MakeVectors(vecAngles); return gpGlobals->v_forward; } } void CBaseDelay::DelayThink( void ) { CBaseEntity *pActivator = NULL; // The use type is cached (and stashed) in pev->button //LRC - now using m_hActivator. SUB_UseTargets( m_hActivator, (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_flLinearMoveSpeed, FIELD_FLOAT ), DEFINE_FIELD( CBaseToggle, m_flAngularMoveSpeed, FIELD_FLOAT ), //LRC 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 ); } /* //void CBaseToggle :: LinearMove( Vector vecInput, float flSpeed) //{ // LinearMove(vecInput, flSpeed); //} ============= LinearMove calculate pev->velocity and pev->nextthink to reach vecDest from pev->origin traveling at flSpeed =============== */ void CBaseToggle :: LinearMove( Vector vecInput, float flSpeed )//, BOOL bNow ) { // ALERT(at_console, "LMove %s: %f %f %f, speed %f\n", STRING(pev->targetname), vecInput.x, vecInput.y, vecInput.z, flSpeed); ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); // ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); m_flLinearMoveSpeed = flSpeed; m_vecFinalDest = vecInput; // if ((m_pMoveWith || m_pChildMoveWith))// && !bNow) // { // ALERT(at_console,"Setting LinearMoveNow to happen after %f\n",gpGlobals->time); SetThink(&CBaseToggle :: LinearMoveNow ); UTIL_DesiredThink( this ); //pev->nextthink = pev->ltime + 0.01; // } // else // { // LinearMoveNow(); // starring Martin Sheen and Marlon Brando // } } void CBaseToggle :: LinearMoveNow( void ) { // ALERT(at_console, "LMNow %s\n", STRING(pev->targetname)); Vector vecDest; // if (m_pMoveWith || m_pChildMoveWith ) // ALERT(at_console,"THINK: LinearMoveNow happens at %f, speed %f\n",gpGlobals->time, m_flLinearMoveSpeed); if (m_pMoveWith) { vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; } else vecDest = m_vecFinalDest; // ALERT(at_console,"LinearMoveNow: Destination is (%f %f %f), finalDest was (%f %f %f)\n", // vecDest.x,vecDest.y,vecDest.z, // m_vecFinalDest.x,m_vecFinalDest.y,m_vecFinalDest.z // ); // 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() / m_flLinearMoveSpeed; // set nextthink to trigger a call to LinearMoveDone when dest is reached SetNextThink( flTravelTime, TRUE ); SetThink(&CBaseToggle :: LinearMoveDone ); // scale the destdelta vector by the time spent traveling to get velocity // pev->velocity = vecDestDelta / flTravelTime; UTIL_SetVelocity( this, vecDestDelta / flTravelTime ); // ALERT(at_console, "LMNow \"%s\": Vel %f %f %f, think %f\n", STRING(pev->targetname), pev->velocity.x, pev->velocity.y, pev->velocity.z, pev->nextthink); } /* ============ After moving, set origin to exact final destination, call "move done" function ============ */ /*void CBaseToggle :: LinearMoveDone( void ) { Vector vecDiff; if (m_pMoveWith) vecDiff = (m_vecFinalDest + m_pMoveWith->pev->origin) - pev->origin; else vecDiff = m_vecFinalDest - pev->origin; if (vecDiff.Length() > 0.05) //pev->velocity.Length()) { // HACK: not there yet, try waiting one more frame. ALERT(at_console,"Rejecting difference %f\n",vecDiff.Length()); SetThink(&CBaseToggle ::LinearMoveFinalDone); pev->nextthink = gpGlobals->time + 0.01; } else { LinearMoveFinalDone(); } }*/ void CBaseToggle :: LinearMoveDone( void ) { SetThink(&CBaseToggle ::LinearMoveDoneNow); // ALERT(at_console, "LMD: desiredThink %s\n", STRING(pev->targetname)); UTIL_DesiredThink( this ); } void CBaseToggle :: LinearMoveDoneNow( void ) { Vector vecDest; // ALERT(at_console, "LMDone %s\n", STRING(pev->targetname)); UTIL_SetVelocity(this, g_vecZero);//, TRUE); // pev->velocity = g_vecZero; if (m_pMoveWith) { vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; // ALERT(at_console, "LMDone %s: p.origin = %f %f %f, origin = %f %f %f. Set it to %f %f %f\n", STRING(pev->targetname), m_pMoveWith->pev->origin.x, m_pMoveWith->pev->origin.y, m_pMoveWith->pev->origin.z, pev->origin.x, pev->origin.y, pev->origin.z, vecDest.x, vecDest.y, vecDest.z); } else { vecDest = m_vecFinalDest; // ALERT(at_console, "LMDone %s: origin = %f %f %f. Set it to %f %f %f\n", STRING(pev->targetname), pev->origin.x, pev->origin.y, pev->origin.z, vecDest.x, vecDest.y, vecDest.z); } UTIL_AssignOrigin(this, vecDest); DontThink(); //LRC //pev->nextthink = -1; if ( m_pfnCallWhenMoveDone ) (this->*m_pfnCallWhenMoveDone)(); } BOOL CBaseToggle :: IsLockedByMaster( void ) { if (UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) return FALSE; else return TRUE; } //LRC- mapping toggle-states to global states STATE CBaseToggle :: GetState ( void ) { switch (m_toggle_state) { case TS_AT_TOP: return STATE_ON; case TS_AT_BOTTOM: return STATE_OFF; case TS_GOING_UP: return STATE_TURN_ON; case TS_GOING_DOWN: return STATE_TURN_OFF; default: return STATE_OFF; // This should never happen. } }; /* ============= 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; m_flAngularMoveSpeed = flSpeed; // if ((m_pMoveWith || m_pChildMoveWith))// && !bNow) // { // ALERT(at_console,"Setting AngularMoveNow to happen after %f\n",gpGlobals->time); SetThink(&CBaseToggle :: AngularMoveNow ); UTIL_DesiredThink( this ); // ExternalThink( 0.01 ); // pev->nextthink = pev->ltime + 0.01; // } // else // { // AngularMoveNow(); // starring Martin Sheen and Marlon Brando // } } void CBaseToggle :: AngularMoveNow() { // ALERT(at_console, "AngularMoveNow %f\n", pev->ltime); Vector vecDestAngle; if (m_pMoveWith) vecDestAngle = m_vecFinalAngle + m_pMoveWith->pev->angles; else vecDestAngle = m_vecFinalAngle; // 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() / m_flAngularMoveSpeed; // set nextthink to trigger a call to AngularMoveDone when dest is reached SetNextThink( flTravelTime, TRUE ); SetThink(&CBaseToggle :: AngularMoveDone ); // scale the destdelta vector by the time spent traveling to get velocity UTIL_SetAvelocity(this, vecDestDelta / flTravelTime ); } void CBaseToggle :: AngularMoveDone( void ) { SetThink(&CBaseToggle ::AngularMoveDoneNow); // ALERT(at_console, "LMD: desiredThink %s\n", STRING(pev->targetname)); UTIL_DesiredThink( this ); } /* ============ After rotating, set angle to exact final angle, call "move done" function ============ */ void CBaseToggle :: AngularMoveDoneNow( void ) { // ALERT(at_console, "AngularMoveDone %f\n", pev->ltime); UTIL_SetAvelocity(this, g_vecZero); if (m_pMoveWith) { UTIL_SetAngles(this, m_vecFinalAngle + m_pMoveWith->pev->angles); } else { UTIL_SetAngles(this, m_vecFinalAngle); } DontThink(); if ( m_pfnCallWhenMoveDone ) (this->*m_pfnCallWhenMoveDone)(); } // this isn't currently used. Otherwise I'd fix it to use movedir the way it should... 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 ( pev->movedir != g_vecZero) //LRC return; 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; } //========================================================= // LRC - info_movewith, the first entity I've made which // truly doesn't fit ANY preexisting category. //========================================================= #define SF_IMW_INACTIVE 1 #define SF_IMW_BLOCKABLE 2 class CInfoMoveWith : public CBaseEntity { public: void Spawn( void ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } STATE GetState() { return (pev->spawnflags & SF_IMW_INACTIVE)?STATE_OFF:STATE_ON; } }; LINK_ENTITY_TO_CLASS(info_movewith, CInfoMoveWith); void CInfoMoveWith :: Spawn( void ) { if (pev->spawnflags & SF_IMW_INACTIVE) m_MoveWith = pev->netname; else m_MoveWith = pev->target; if (pev->spawnflags & SF_IMW_BLOCKABLE) { pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_FLY; } // and allow InitMoveWith to set things up as usual. } void CInfoMoveWith :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { CBaseEntity *pSibling; if (!ShouldToggle(useType)) return; if (m_pMoveWith) { // remove this from the old parent's list of children pSibling = m_pMoveWith->m_pChildMoveWith; if (pSibling == this) m_pMoveWith->m_pChildMoveWith = this->m_pSiblingMoveWith; else { while (pSibling->m_pSiblingMoveWith && pSibling->m_pSiblingMoveWith != this) { pSibling = pSibling->m_pSiblingMoveWith; } if (pSibling->m_pSiblingMoveWith == this) { pSibling->m_pSiblingMoveWith = this->m_pSiblingMoveWith; } else { // failed to find myself in the list, complain ALERT(at_error, "info_movewith can't find itself\n"); return; } } m_pMoveWith = NULL; m_pSiblingMoveWith = NULL; } if (pev->spawnflags & SF_IMW_INACTIVE) { pev->spawnflags &= ~SF_IMW_INACTIVE; m_MoveWith = pev->target; } else { pev->spawnflags |= SF_IMW_INACTIVE; m_MoveWith = pev->netname; } // set things up for the new m_MoveWith value if (!m_MoveWith) { UTIL_SetVelocity(this, g_vecZero); // come to a stop return; } m_pMoveWith = UTIL_FindEntityByTargetname(NULL, STRING(m_MoveWith)); if (!m_pMoveWith) { ALERT(at_debug,"Missing movewith entity %s\n", STRING(m_MoveWith)); return; } pSibling = m_pMoveWith->m_pChildMoveWith; while (pSibling) // check that this entity isn't already in the list of children { if (pSibling == this) return; pSibling = pSibling->m_pSiblingMoveWith; } // add this entity to the list of children m_pSiblingMoveWith = m_pMoveWith->m_pChildMoveWith; // may be null: that's fine by me. m_pMoveWith->m_pChildMoveWith = this; m_vecMoveWithOffset = pev->origin - m_pMoveWith->pev->origin; UTIL_SetVelocity(this, g_vecZero); // match speed with the new entity }