示例#1
0
// Look at the given point in space for the given duration (-1 means forever)
void CCSBot::SetLookAt(const char *desc, const Vector *pos, PriorityType pri, float duration, bool clearIfClose, float angleTolerance)
{
	if (pos == NULL)
		return;

	// if currently looking at a point in space with higher priority, ignore this request
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority > pri)
		return;

	// if already looking at this spot, just extend the time
	const float tolerance = 10.0f;
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && VectorsAreEqual(pos, &m_lookAtSpot, tolerance))
	{
		m_lookAtSpotDuration = duration;

		if (m_lookAtSpotPriority < pri)
			m_lookAtSpotPriority = pri;
	}
	else
	{
		// look at new spot
		m_lookAtSpot = *pos;
		m_lookAtSpotState = LOOK_TOWARDS_SPOT;
		m_lookAtSpotDuration = duration;
		m_lookAtSpotPriority = pri;
	}

	m_lookAtSpotAngleTolerance = angleTolerance;
	m_lookAtSpotClearIfClose = clearIfClose;
	m_lookAtDesc = desc;
}
示例#2
0
void C_SteamJet::UpdateLightingRamp()
{
	if( VectorsAreEqual( m_vLastRampUpdatePos, GetAbsOrigin(), 0.1 ) && 
		QAnglesAreEqual( m_vLastRampUpdateAngles, GetAbsAngles(), 0.1 ) )
	{
		return;
	}

	m_vLastRampUpdatePos = GetAbsOrigin();
	m_vLastRampUpdateAngles = GetAbsAngles();

	// Sample the world lighting where we think the particles will be.
	Vector forward, right, up;
	AngleVectors(GetAbsAngles(), &forward, &right, &up);

	// Legacy env_steamjet entities faced left instead of forward.
	if (m_bFaceLeft)
	{
		Vector temp = forward;
		forward = -right;
		right = temp;
	}

	Vector startPos = GetAbsOrigin();
	Vector endPos = GetAbsOrigin() + forward * (m_Speed * m_Lifetime);

	for(int iRamp=0; iRamp < STEAMJET_NUMRAMPS; iRamp++)
	{
		float t = (float)iRamp / (STEAMJET_NUMRAMPS-1);
		Vector vTestPos = startPos + (endPos - startPos) * t;
		
		Vector *pRamp = &m_Ramps[iRamp];
		*pRamp = WorldGetLightForPoint(vTestPos, false);
		
		if ( IsEmissive() )
		{
			pRamp->x += (m_clrRender->r/255.0f);
			pRamp->y += (m_clrRender->g/255.0f);
			pRamp->z += (m_clrRender->b/255.0f);

			pRamp->x = clamp( pRamp->x, 0.0f, 1.0f );
			pRamp->y = clamp( pRamp->y, 0.0f, 1.0f );
			pRamp->z = clamp( pRamp->z, 0.0f, 1.0f );
		}
		else
		{
			pRamp->x *= (m_clrRender->r/255.0f);
			pRamp->y *= (m_clrRender->g/255.0f);
			pRamp->z *= (m_clrRender->b/255.0f);
		}

		// Renormalize?
		float maxVal = max(pRamp->x, max(pRamp->y, pRamp->z));
		if(maxVal > 1)
		{
			*pRamp = *pRamp / maxVal;
		}
	}
}
static bool FindEdge( 
	CCoreDispInfo *pInfo,
	Vector const &vPoint1,
	Vector const &vPoint2,
	int &iEdge 
	)
{
	CCoreDispSurface *pSurface = pInfo->GetSurface();

	for( iEdge=0; iEdge < 4; iEdge++ )
	{
		if( VectorsAreEqual( vPoint1, pSurface->GetPoint( iEdge ), 0.01f ) &&
			VectorsAreEqual( vPoint2, pSurface->GetPoint( (iEdge+1) & 3), 0.01f ) )
		{
			return true;
		}
	}
	
	return false;
}
static void SetupCornerNeighbors( 
	const CCoreDispInfo *pListBase, 
	CCoreDispInfo *pMain, 
	CCoreDispInfo *pOther,
	int *nOverflows )
{
	if ( HasEdgeNeighbor( pMain, pOther-pListBase ) )
		return;

	// Do these two share a vertex?
	int nShared = 0;
	int iMainSharedCorner = -1;
	int iOtherSharedCorner = -1;

	for ( int iMainCorner=0; iMainCorner < 4; iMainCorner++ )
	{
		Vector const &vMainCorner = pMain->GetCornerPoint( iMainCorner );
		
		for ( int iOtherCorner=0; iOtherCorner < 4; iOtherCorner++ )
		{
			Vector const &vOtherCorner = pOther->GetCornerPoint( iOtherCorner );
		
			if ( VectorsAreEqual( vMainCorner, vOtherCorner, 0.001f ) )
			{
				iMainSharedCorner = iMainCorner;
				iOtherSharedCorner = iOtherCorner;
				++nShared;
			}
		}
	}

	if ( nShared == 1 )
	{
		CDispCornerNeighbors *pMainCorner = pMain->GetCornerNeighbors( iMainSharedCorner );
		CDispCornerNeighbors *pOtherCorner = pOther->GetCornerNeighbors( iOtherSharedCorner );

		if ( pMainCorner->m_nNeighbors < MAX_DISP_CORNER_NEIGHBORS &&
			pOtherCorner->m_nNeighbors < MAX_DISP_CORNER_NEIGHBORS )
		{
			pMainCorner->m_Neighbors[pMainCorner->m_nNeighbors++] = pOther - pListBase;
			pOtherCorner->m_Neighbors[pOtherCorner->m_nNeighbors++] = pMain - pListBase;
		}
		else
		{
			++(*nOverflows);
		}
	}
}
示例#5
0
static void PerformNewCustomEffects( const Vector &vecOrigin, trace_t &tr, const Vector &shotDir, int iMaterial, int iScale, int nFlags )
{
    bool bNoFlecks = !r_drawflecks.GetBool();
    if ( !bNoFlecks )
    {
        bNoFlecks = ( ( nFlags & FLAGS_CUSTIOM_EFFECTS_NOFLECKS ) != 0  );
    }

    // Compute the impact effect name
    const ImpactEffect_t &effect = s_pImpactEffect[ iMaterial - 'A' ];
    const char *pImpactName = effect.m_pName;
    if ( bNoFlecks && effect.m_pNameNoFlecks )
    {
        pImpactName = effect.m_pNameNoFlecks;
    }
    if ( !pImpactName )
        return;

    CSmartPtr<CNewParticleEffect> pEffect = CNewParticleEffect::Create( NULL, pImpactName );
    if ( !pEffect->IsValid() )
        return;

    Vector	vecReflect;
    float	flDot = DotProduct( shotDir, tr.plane.normal );
    VectorMA( shotDir, -2.0f * flDot, tr.plane.normal, vecReflect );

    Vector vecShotBackward;
    VectorMultiply( shotDir, -1.0f, vecShotBackward );

    Vector vecImpactPoint = ( tr.fraction != 1.0f ) ? tr.endpos : vecOrigin;
    // Other games round m_vOrigin to nearest integer, so I guess we can afford skipping this check.
#ifdef HL2_CLIENT_DLL
    Assert( VectorsAreEqual( vecOrigin, tr.endpos, 1e-1 ) );
#endif
    SetImpactControlPoint( pEffect.GetObject(), 0, vecImpactPoint, tr.plane.normal, tr.m_pEnt );
    SetImpactControlPoint( pEffect.GetObject(), 1, vecImpactPoint, vecReflect,		tr.m_pEnt );
    SetImpactControlPoint( pEffect.GetObject(), 2, vecImpactPoint, vecShotBackward,	tr.m_pEnt );
    pEffect->SetControlPoint( 3, Vector( iScale, iScale, iScale ) );
    if ( pEffect->m_pDef->ReadsControlPoint( 4 ) )
    {
        Vector vecColor;
        GetColorForSurface( &tr, &vecColor );
        pEffect->SetControlPoint( 4, vecColor );
    }
}
示例#6
0
bool fogparams_t::operator !=( const fogparams_t& other ) const
{
	if ( this->enable != other.enable ||
		this->blend != other.blend ||
		!VectorsAreEqual(this->dirPrimary, other.dirPrimary, 0.01f ) || 
		this->colorPrimary.Get() != other.colorPrimary.Get() ||
		this->colorSecondary.Get() != other.colorSecondary.Get() ||
		this->start != other.start ||
		this->end != other.end ||
		this->farz != other.farz ||
		this->maxdensity != other.maxdensity ||
		this->colorPrimaryLerpTo.Get() != other.colorPrimaryLerpTo.Get() ||
		this->colorSecondaryLerpTo.Get() != other.colorSecondaryLerpTo.Get() ||
		this->startLerpTo != other.startLerpTo ||
		this->endLerpTo != other.endLerpTo ||
		this->lerptime != other.lerptime ||
		this->duration != other.duration )
		return true;

	return false;
}
//-----------------------------------------------------------------------------
// Implement this if you want to know when the player collides during OnPlayerMove
//-----------------------------------------------------------------------------
void CTFGameMovementRecon::OnTryPlayerMoveCollision( trace_t &tr )
{
	if ( !m_bPerformingAirMove )
		return;
	
	// Only keep track of world collisions
	if ( tr.DidHitWorld() )
	{
		CTFMoveData *pTFMove = TFMove();
		if ( pTFMove )
		{
			if ( ( pTFMove->ReconData().m_flSuppressionJumpTime == TIME_WALL_INVALID ) &&
				 ( pTFMove->ReconData().m_flSuppressionImpactTime == TIME_WALL_INVALID ) )
			{
				// No walljumps off of mostly horizontal surfaces...
				if ( fabs( tr.plane.normal.z ) > 0.9f )
					return;
			
				// No walljumps off of the same plane as the last one...
				if ( (pTFMove->ReconData().m_flImpactDist == tr.plane.dist) && 
					(VectorsAreEqual(pTFMove->ReconData().m_vecImpactNormal, tr.plane.normal, 1e-2) ) )
				{
					return;
				}

				// If you hit a wall, no double jumps for you
				pTFMove->ReconData().m_nJumpCount = 2;
				
				// Play an impact sound
				MoveHelper()->StartSound( pTFMove->m_vecAbsOrigin, "Recon.WallJump" );

				pTFMove->ReconData().m_vecImpactNormal = tr.plane.normal;
				pTFMove->ReconData().m_flImpactDist = tr.plane.dist;

				pTFMove->ReconData().m_flActiveJumpTime = TIME_WALL_ACTIVATE_JUMP;
				pTFMove->ReconData().m_flSuppressionImpactTime = TIME_WALL_SUPPRESSION_IMPACT;
			}
		}
	}
}
bool CAI_TacticalServices::FindLateralLos( const Vector &vecThreat, Vector *pResult )
{
	AI_PROFILE_SCOPE( CAI_TacticalServices_FindLateralLos );

	if( !m_bAllowFindLateralLos )
	{
		return false;
	}

	MARK_TASK_EXPENSIVE();

	Vector	vecLeftTest;
	Vector	vecRightTest;
	Vector	vecStepRight;
	Vector  vecCheckStart;
	bool	bLookingForEnemy = GetEnemy() && VectorsAreEqual(vecThreat, GetEnemy()->EyePosition(), 0.1f);
	int		i;

	if(  !bLookingForEnemy || GetOuter()->HasCondition(COND_SEE_ENEMY) || GetOuter()->HasCondition(COND_HAVE_ENEMY_LOS) || 
		 GetOuter()->GetTimeScheduleStarted() == gpGlobals->curtime ) // Conditions get nuked before tasks run, assume should try
	{
		// My current position might already be valid.
		if ( TestLateralLos(vecThreat, GetLocalOrigin()) )
		{
			*pResult = GetLocalOrigin();
			return true;
		}
	}

	if( !ai_find_lateral_los.GetBool() )
	{
		// Allows us to turn off lateral LOS at the console. Allow the above code to run 
		// just in case the NPC has line of sight to begin with.
		return false;
	}

	int iChecks = COVER_CHECKS;
	int iDelta = COVER_DELTA;

	// If we're limited in how far we're allowed to move laterally, don't bother checking past it
	int iMaxLateralDelta = GetOuter()->GetMaxTacticalLateralMovement();
	if ( iMaxLateralDelta != MAXTACLAT_IGNORE && iMaxLateralDelta < iDelta )
	{
		iChecks = 1;
		iDelta = iMaxLateralDelta;
	}

	Vector right;
	AngleVectors( GetLocalAngles(), NULL, &right, NULL );
	vecStepRight = right * iDelta;
	vecStepRight.z = 0;

	vecLeftTest = vecRightTest = GetLocalOrigin();
 	vecCheckStart = vecThreat;

	for ( i = 0 ; i < iChecks; i++ )
	{
		vecLeftTest = vecLeftTest - vecStepRight;
		vecRightTest = vecRightTest + vecStepRight;

		if (TestLateralLos( vecCheckStart, vecLeftTest ))
		{
			*pResult = vecLeftTest;
			return true;
		}

		if (TestLateralLos( vecCheckStart, vecRightTest ))
		{
			*pResult = vecRightTest;
			return true;
		}
	}

	return false;
}
示例#9
0
bool CAI_LocalNavigator::MoveCalcDirect( AILocalMoveGoal_t *pMoveGoal, bool bOnlyCurThink, float *pDistClear, AIMoveResult_t *pResult )
{
	AI_PROFILE_SCOPE(CAI_LocalNavigator_MoveCalcDirect);

	bool bRetVal = false;
	
	if ( pMoveGoal->speed )
	{
		CAI_Motor *pMotor = GetOuter()->GetMotor();
		float  minCheckDist = pMotor->MinCheckDist();
		float  probeDist	= m_pPlaneSolver->CalcProbeDist( pMoveGoal->speed ); // having this match steering allows one fewer traces
		float  checkDist	= MAX( minCheckDist, probeDist );
		float  checkStepDist = MAX( 16.0, probeDist * 0.5 );

		if ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) )
		{
			// clamp checkDist to be no farther than MAX distance to goal
			checkDist = MIN( checkDist, pMoveGoal->maxDist );
		}

		if ( checkDist <= 0.0 )
		{
			*pResult = AIMR_OK;
			return true;
		}

		float moveThisInterval = pMotor->CalcIntervalMove();
		bool bExpectingArrival = (moveThisInterval >= checkDist);

		if ( !m_FullDirectTimer.Expired() )
		{
			if ( !m_fLastWasClear || 
				 ( !VectorsAreEqual(pMoveGoal->target, m_LastMoveGoal.target, 0.1) || 
				   !VectorsAreEqual(pMoveGoal->dir, m_LastMoveGoal.dir, 0.1) ) ||
				 bExpectingArrival )
			{
				m_FullDirectTimer.Force();
			}
		}

		if ( bOnlyCurThink ) // Outer code claims to have done a validation (probably a simplify operation)
		{
			m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] );
		}

		// First, check the probable move for this cycle
		bool bTraceClear = true;
		Vector testPos;

		if ( !bExpectingArrival )
		{
			testPos = GetLocalOrigin() + pMoveGoal->dir * moveThisInterval;
			bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos, 
													 GetOuter()->GetAITraceMask(), pMoveGoal->pMoveTarget, 
													 100.0, 
													 ( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT, 
													 &pMoveGoal->directTrace );

			if ( !bTraceClear )
			{
				// Adjust probe top match expected probe dist (relied on later in process)
				pMoveGoal->directTrace.flDistObstructed = (checkDist - moveThisInterval) + pMoveGoal->directTrace.flDistObstructed;

			}

			if ( !IsRetail() && ai_debug_directnavprobe.GetBool() )
			{
				if ( !bTraceClear )
				{
					DevMsg( GetOuter(), "Close obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed );
					NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 255, 0, 0, false, 0.1 );
					if ( pMoveGoal->directTrace.pObstruction )
						NDebugOverlay::Line( WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 255, 0, 255, false, 0.1 );

				}
				else
				{
					NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 0, 255, 0, false, 0.1 );
				}
			}

			pMoveGoal->thinkTrace = pMoveGoal->directTrace;
		}

		// Now project out for future obstructions
		if ( bTraceClear )
		{
			if ( m_FullDirectTimer.Expired() )
			{
				testPos = GetLocalOrigin() + pMoveGoal->dir * checkDist;
				float checkStepPct = (checkStepDist / checkDist) * 100.0;
				if ( checkStepPct > 100.0 )
					checkStepPct = 100.0;
				
				bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos, 
														 GetOuter()->GetAITraceMask(), pMoveGoal->pMoveTarget, 
														 checkStepPct, 
														 ( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT, 
														 &pMoveGoal->directTrace );
				if ( bExpectingArrival )
					pMoveGoal->thinkTrace = pMoveGoal->directTrace;

				if (ai_debug_directnavprobe.GetBool() )
				{
					if ( !bTraceClear )
					{
						NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 255, 0, 0, false, 0.1 );
						DevMsg( GetOuter(), "Obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed );
					}
					else
					{
						NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 0, 255, 0, false, 0.1 );
						DevMsg( GetOuter(), "No obstruction\n" );
					}
				}
			}
			else
			{
				if ( ai_debug_directnavprobe.GetBool() )
					DevMsg( GetOuter(), "No obstruction (Near probe only)\n" );
			}
		}

		pMoveGoal->bHasTraced = true;
	
		float distClear = checkDist - pMoveGoal->directTrace.flDistObstructed;
		if (distClear < 0.001)
			distClear = 0;
		
		if ( bTraceClear )
		{
			*pResult = AIMR_OK;
			bRetVal = true;
			m_fLastWasClear = true;
		}
		else if ( ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) ) && 
			 pMoveGoal->maxDist < distClear )
		{
			*pResult = AIMR_OK;
			bRetVal = true;
			m_fLastWasClear = true;
		}
		else
		{
			*pDistClear = distClear;
			m_fLastWasClear = false;
		}
	}
	else
	{
		// Should never end up in this function with speed of zero. Probably an activity problem.
		*pResult = AIMR_ILLEGAL;
		bRetVal = true;
	}

	m_LastMoveGoal = *pMoveGoal;
	if ( bRetVal && m_FullDirectTimer.Expired() )
		m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] );

	return bRetVal;
}
void C_FuncSmokeVolume::Update( float fTimeDelta )
{
	// Update our world space bbox if we've moved at all.
	// We do this manually because sometimes people make HUGE bboxes, and if they're constantly changing because their
	// particles wander outside the current bounds sometimes, it'll be linking them into all the leaves repeatedly.
	const Vector &curOrigin = GetAbsOrigin();
	const QAngle &curAngles = GetAbsAngles();
	if ( !VectorsAreEqual( curOrigin, m_vLastOrigin, 0.1 ) || 
		fabs( curAngles.x - m_vLastAngles.x ) > 0.1 || 
		fabs( curAngles.y - m_vLastAngles.y ) > 0.1 || 
		fabs( curAngles.z - m_vLastAngles.z ) > 0.1 ||
		m_bFirstUpdate )
	{
		m_bFirstUpdate = false;
		m_vLastAngles = curAngles;
		m_vLastOrigin = curOrigin;

		Vector vWorldMins, vWorldMaxs;
		CollisionProp()->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
		vWorldMins -= Vector( m_ParticleRadius, m_ParticleRadius, m_ParticleRadius );
		vWorldMaxs += Vector( m_ParticleRadius, m_ParticleRadius, m_ParticleRadius );

		m_ParticleEffect.SetBBox( vWorldMins, vWorldMaxs );
	}
		
	// lerp m_CurrentDensity towards m_Density at a rate of m_DensityRampSpeed
	if( m_CurrentDensity < m_Density )
	{
		m_CurrentDensity += m_DensityRampSpeed * fTimeDelta;
		if( m_CurrentDensity > m_Density )
		{
			m_CurrentDensity = m_Density;
		}
	}
	else if( m_CurrentDensity > m_Density )
	{
		m_CurrentDensity -= m_DensityRampSpeed * fTimeDelta;
		if( m_CurrentDensity < m_Density )
		{
			m_CurrentDensity = m_Density;
		}
	}

	if( m_CurrentDensity == 0.0f )
	{
		return;
	}
	
	// This is used to randomize the direction it chooses to move a particle in.

	int offsetLookup[3] = {-1,0,1};

	float tradeDurationMax = m_ParticleSpacingDistance / ( m_MovementSpeed + 0.1f );
	float tradeDurationMin = tradeDurationMax * 0.5f;

	if ( IS_NAN( tradeDurationMax ) || IS_NAN( tradeDurationMin ) )
		return;

//	Warning( "tradeDuration: [%f,%f]\n", tradeDurationMin, tradeDurationMax );
	
	// Update all the moving traders and establish new ones.
	int nTotal = m_xCount * m_yCount * m_zCount;
	for( int i=0; i < nTotal; i++ )
	{
		SmokeParticleInfo *pInfo = &m_pSmokeParticleInfos[i];

		if(!pInfo->m_pParticle)
			continue;
	
		if(pInfo->m_TradeIndex == -1)
		{
			pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha;
			pInfo->m_pParticle->m_Color[0] = pInfo->m_Color[0];
			pInfo->m_pParticle->m_Color[1] = pInfo->m_Color[1];
			pInfo->m_pParticle->m_Color[2] = pInfo->m_Color[2];

			// Is there an adjacent one that's not trading?
			int x, y, z;
			GetParticleInfoXYZ(i, x, y, z);

			int xCountOffset = rand();
			int yCountOffset = rand();
			int zCountOffset = rand();

			bool bFound = false;
			for(int xCount=0; xCount < 3 && !bFound; xCount++)
			{
				for(int yCount=0; yCount < 3 && !bFound; yCount++)
				{
					for(int zCount=0; zCount < 3; zCount++)
					{
						int testX = x + offsetLookup[(xCount+xCountOffset) % 3];
						int testY = y + offsetLookup[(yCount+yCountOffset) % 3];
						int testZ = z + offsetLookup[(zCount+zCountOffset) % 3];

						if(testX == x && testY == y && testZ == z)
							continue;

						if(IsValidXYZCoords(testX, testY, testZ))
						{
							SmokeParticleInfo *pOther = GetSmokeParticleInfo(testX, testY, testZ);
							if(pOther->m_pParticle && pOther->m_TradeIndex == -1)
							{
								// Ok, this one is looking to trade also.
								pInfo->m_TradeIndex = GetSmokeParticleIndex(testX, testY, testZ);
								pOther->m_TradeIndex = i;
								pInfo->m_TradeClock = pOther->m_TradeClock = 0;
								pOther->m_TradeDuration = pInfo->m_TradeDuration = FRand( tradeDurationMin, tradeDurationMax );
								
								bFound = true;
								break;
							}
						}
					}
				}
			}
		}
		else
		{
			SmokeParticleInfo *pOther = &m_pSmokeParticleInfos[pInfo->m_TradeIndex];
			assert(pOther->m_TradeIndex == i);
			
			// This makes sure the trade only gets updated once per frame.
			if(pInfo < pOther)
			{
				// Increment the trade clock..
				pInfo->m_TradeClock = (pOther->m_TradeClock += fTimeDelta);
				int x, y, z;
				GetParticleInfoXYZ(i, x, y, z);
				Vector myPos = GetSmokeParticlePos(x, y, z);
				
				int otherX, otherY, otherZ;
				GetParticleInfoXYZ(pInfo->m_TradeIndex, otherX, otherY, otherZ);
				Vector otherPos = GetSmokeParticlePos(otherX, otherY, otherZ);

				// Is the trade finished?
				if(pInfo->m_TradeClock >= pInfo->m_TradeDuration)
				{
					pInfo->m_TradeIndex = pOther->m_TradeIndex = -1;
					
					pInfo->m_pParticle->m_Pos = otherPos;
					pOther->m_pParticle->m_Pos = myPos;

					SmokeGrenadeParticle *temp = pInfo->m_pParticle;
					pInfo->m_pParticle = pOther->m_pParticle;
					pOther->m_pParticle = temp;
				}
				else
				{			
					// Ok, move them closer.
					float percent = (float)cos(pInfo->m_TradeClock * 2 * 1.57079632f / pInfo->m_TradeDuration);
					percent = percent * 0.5 + 0.5;
					
					pInfo->m_pParticle->m_FadeAlpha  = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * (1 - percent);
					pOther->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * percent;

					InterpColor(pInfo->m_pParticle->m_Color,  pInfo->m_Color, pOther->m_Color, 1-percent);
					InterpColor(pOther->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, percent);

					pInfo->m_pParticle->m_Pos  = myPos + (otherPos - myPos) * (1 - percent);
					pOther->m_pParticle->m_Pos = myPos + (otherPos - myPos) * percent;
				}
			}
		}
	}
}
//-----------------------------------------------------------------------------
// A version that simply accepts a ray (can work as a traceline or tracehull)
//-----------------------------------------------------------------------------
void CEngineTrace::TraceRay( const Ray_t &ray, unsigned int fMask, ITraceFilter *pTraceFilter, trace_t *pTrace )
{
	CTraceFilterHitAll traceFilter;
	if ( !pTraceFilter )
	{
		pTraceFilter = &traceFilter;
	}

	// Gather statistics.
	g_EngineStats.IncrementCountedStat( ENGINE_STATS_NUM_TRACE_LINES, 1 );
	MEASURE_TIMED_STAT( ENGINE_STATS_TRACE_LINE_TIME );
	
	CM_ClearTrace( pTrace );

	// Collide with the world.
	if ( pTraceFilter->GetTraceType() != TRACE_ENTITIES_ONLY )
	{
		ICollideable *pCollide = GetWorldCollideable();

		// Make sure the world entity is unrotated
		// FIXME: BAH! The !pCollide test here is because of
		// CStaticProp::PrecacheLighting.. it's occurring too early
		// need to fix that later
		Assert(!pCollide || pCollide->GetCollisionOrigin() == vec3_origin );
		Assert(!pCollide || pCollide->GetCollisionAngles() == vec3_angle );

		CM_BoxTrace( ray, 0, fMask, true, *pTrace );
		SetTraceEntity( pCollide, pTrace );

		// Blocked by the world.
		if ( pTrace->fraction == 0 )
			return;

		// Early out if we only trace against the world
		if ( pTraceFilter->GetTraceType() == TRACE_WORLD_ONLY )
			return;
	}

	// Save the world collision fraction.
	float flWorldFraction = pTrace->fraction;

	// Create a ray that extends only until we hit the world
	// and adjust the trace accordingly
	Ray_t entityRay = ray;
	entityRay.m_Delta *= pTrace->fraction;

	// We know this is safe because if pTrace->fraction == 0
	// we would have exited above
	pTrace->fractionleftsolid /= pTrace->fraction;
 	pTrace->fraction = 1.0;

	// Collide with entities along the ray
	// FIXME: Hitbox code causes this to be re-entrant for the IK stuff.
	// If we could eliminate that, this could be static and therefore
	// not have to reallocate memory all the time
	CEntitiesAlongRay enumerator;
	enumerator.Reset();
	SpatialPartition()->EnumerateElementsAlongRay( SpatialPartitionMask(), entityRay, false, &enumerator );

	bool bNoStaticProps = pTraceFilter->GetTraceType() == TRACE_ENTITIES_ONLY;
	bool bFilterStaticProps = pTraceFilter->GetTraceType() == TRACE_EVERYTHING_FILTER_PROPS;

	trace_t tr;
	ICollideable *pCollideable;
	const char *pDebugName;
	int nCount = enumerator.m_EntityHandles.Count();
	for ( int i = 0; i < nCount; ++i )
	{
		// Generate a collideable
		IHandleEntity *pHandleEntity = enumerator.m_EntityHandles[i];
		HandleEntityToCollideable( pHandleEntity, &pCollideable, &pDebugName );

		// Check for error condition
		if ( !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) )
		{
			char temp[1024];
			Q_snprintf(temp, sizeof( temp ), "%s in solid list (not solid)\n", pDebugName );
			Sys_Error (temp);
		}

		if ( !StaticPropMgr()->IsStaticProp( pHandleEntity ) )
		{
			if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) )
				continue;
		}
		else
		{
			// FIXME: Could remove this check here by
			// using a different spatial partition mask. Look into it
			// if we want more speedups here.
			if ( bNoStaticProps )
				continue;

			if ( bFilterStaticProps )
			{
				if ( !pTraceFilter->ShouldHitEntity( pHandleEntity, fMask ) )
					continue;
			}
		}

		ClipRayToCollideable( entityRay, fMask, pCollideable, &tr );

		// Make sure the ray is always shorter than it currently is
		ClipTraceToTrace( tr, pTrace );

		// Stop if we're in allsolid
		if (pTrace->allsolid)
			break;
	}

	// Fix up the fractions so they are appropriate given the original
	// unclipped-to-world ray
	pTrace->fraction *= flWorldFraction;
	pTrace->fractionleftsolid *= flWorldFraction;

#ifdef _DEBUG
	Vector vecOffset, vecEndTest;
	VectorAdd( ray.m_Start, ray.m_StartOffset, vecOffset );
	VectorMA( vecOffset, pTrace->fractionleftsolid, ray.m_Delta, vecEndTest );
	Assert( VectorsAreEqual( vecEndTest, pTrace->startpos, 0.1f ) );
	VectorMA( vecOffset, pTrace->fraction, ray.m_Delta, vecEndTest );
	Assert( VectorsAreEqual( vecEndTest, pTrace->endpos, 0.1f ) );
//	Assert( !ray.m_IsRay || pTrace->allsolid || pTrace->fraction >= pTrace->fractionleftsolid );
#endif

	if ( !ray.m_IsRay )
	{
		// Make sure no fractionleftsolid can be used with box sweeps
		VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
		pTrace->fractionleftsolid = 0;

#ifdef _DEBUG
		pTrace->fractionleftsolid = VEC_T_NAN;
#endif
	}
}
//-----------------------------------------------------------------------------
// Traces a ray against a particular edict
//-----------------------------------------------------------------------------
void CEngineTrace::ClipRayToCollideable( const Ray_t &ray, unsigned int fMask, ICollideable *pEntity, trace_t *pTrace )
{
	CM_ClearTrace( pTrace );
	VectorAdd( ray.m_Start, ray.m_StartOffset, pTrace->startpos );
	VectorAdd( pTrace->startpos, ray.m_Delta, pTrace->endpos );

	const model_t *pModel = pEntity->GetCollisionModel();
	bool bIsStudioModel = pModel && pModel->type == mod_studio;

	// Cull if the collision mask isn't set + we're not testing hitboxes.
	if ( bIsStudioModel && (( fMask & CONTENTS_HITBOX ) == 0) )
	{
		studiohdr_t *pStudioHdr = ( studiohdr_t * )modelloader->GetExtraData( (model_t*)pModel );
		if ( ( fMask & pStudioHdr->contents ) == 0)
			return;
	}

	bool bTraced = false;
	bool bCustomPerformed = false;
	if ( ShouldPerformCustomRayTest( ray, pEntity ) )
	{
		ClipRayToCustom( ray, fMask, pEntity, pTrace );
		bTraced = true;
		bCustomPerformed = true;
	}
	else
	{
		bTraced = ClipRayToVPhysics( ray, fMask, pEntity, pTrace );	
	}

	if ( !bTraced )
	{
		bTraced = ClipRayToBSP( ray, fMask, pEntity, pTrace );
	}

	// Hitboxes..
	bool bTracedHitboxes = false;
	if ( bIsStudioModel && (fMask & CONTENTS_HITBOX) )
	{
		// Until hitboxes are no longer implemented as custom raytests,
		// don't bother to do the work twice
		if (!bCustomPerformed)
		{
			bTraced = ClipRayToHitboxes( ray, fMask, pEntity, pTrace );
			if ( bTraced )
			{
				// Hitboxes will set the surface properties
				bTracedHitboxes = true;
			}
		}
	}

	if ( !bTraced )
	{
		ClipRayToBBox( ray, fMask, pEntity, pTrace );
	}

	if ( bIsStudioModel && !bTracedHitboxes && pTrace->DidHit() )
	{
		studiohdr_t *pStudioHdr = ( studiohdr_t * )modelloader->GetExtraData( (model_t*)pModel );
		pTrace->contents = pStudioHdr->contents;
		// use the default surface properties
		pTrace->surface.name = "**studio**";
		pTrace->surface.flags = 0;
		pTrace->surface.surfaceProps = physprop->GetSurfaceIndex( pStudioHdr->pszSurfaceProp() );
	}

	if (pTrace->DidHit())
	{
		SetTraceEntity( pEntity, pTrace );
	}

#ifdef _DEBUG
	Vector vecOffset, vecEndTest;
	VectorAdd( ray.m_Start, ray.m_StartOffset, vecOffset );
	VectorMA( vecOffset, pTrace->fractionleftsolid, ray.m_Delta, vecEndTest );
	Assert( VectorsAreEqual( vecEndTest, pTrace->startpos, 0.1f ) );
	VectorMA( vecOffset, pTrace->fraction, ray.m_Delta, vecEndTest );
	Assert( VectorsAreEqual( vecEndTest, pTrace->endpos, 0.1f ) );
#endif
}