void CAISenseRecorderAbstract::CopySenseRecord(AISenseRecord* pOrigSenseRecord)
{
	if( !pOrigSenseRecord )
	{
		ASSERT( pOrigSenseRecord != LTNULL );
		return;
	}

	// Find sense record for stimulus.
	AISENSE_RECORD_MAP::iterator it = m_mapSenseRecords.find( pOrigSenseRecord->eSenseType );
	ASSERT(it != m_mapSenseRecords.end());

	AISenseRecord* pSenseRecord = it->second;

	// If sense was already updated this cycle, do not copy over it.
	if( pSenseRecord->nCycle == pOrigSenseRecord->nCycle )
	{
		return;
	}

	// Mark the sense as being updated this cycle.  The recorder can look at the 
	// cycle stamp later to determine which senses were updated during one cycle.
	pSenseRecord->nCycle = pOrigSenseRecord->nCycle;

	// Keep a pointer to the bute file entry for the stimulus.
	pSenseRecord->pAIBM_Last_Stimulus = pOrigSenseRecord->pAIBM_Last_Stimulus;

	// Mark the timestamp of the stimulating sense.
	pSenseRecord->fLastStimulationTime = pOrigSenseRecord->fLastStimulationTime;

	// Record info about stimulus.
	pSenseRecord->hLastStimulusSource		= pOrigSenseRecord->hLastStimulusSource;
	pSenseRecord->hLastStimulusTarget		= pOrigSenseRecord->hLastStimulusTarget;
	pSenseRecord->eLastTargetMatchID		= pOrigSenseRecord->eLastTargetMatchID;
	pSenseRecord->vLastStimulusPos			= pOrigSenseRecord->vLastStimulusPos;
	pSenseRecord->vLastStimulusDir			= pOrigSenseRecord->vLastStimulusDir;
	pSenseRecord->nLastStimulusAlarmLevel	= pOrigSenseRecord->nLastStimulusAlarmLevel;
	pSenseRecord->eLastStimulusID			= pOrigSenseRecord->eLastStimulusID;

	// Randomize reaction delay time.
	// Do not reset the timer if stimulation already started.
	if( pSenseRecord->fCurStimulation <= 0.f )
	{
		pSenseRecord->fReactionDelayTime		= GetRandom( pSenseRecord->pAIBM_Last_Stimulus->rngReactionDelay.GetMin(), pSenseRecord->pAIBM_Last_Stimulus->rngReactionDelay.GetMax() );
		pSenseRecord->fReactionDelayTimer		= 0.f;
	}

	// Returns true if sense has reached full stimulation.
	// Force full stimulation.
	IncreaseStimulation(pSenseRecord, 9999999.99f );
}
LTBOOL CAISenseHearEnemyFootstep::Update(HOBJECT hStimulus, LTFLOAT fTimeDelta)
{
	if ( !IsCharacter(hStimulus) ) return LTFALSE;

    CCharacter* pChar = (CCharacter*)g_pLTServer->HandleToObject(hStimulus);

	CharMoveInfo info;
	pChar->GetLastMoveInfo(info);

	if ( info.fTime > m_fStimulationTime &&
         g_pLTServer->GetTime() > info.fTime &&
         g_pLTServer->GetTime() < info.fTime + 0.50f )
	{
        LTVector vMovementPos;
        g_pLTServer->GetObjectPos(hStimulus, &vMovementPos);

        LTFLOAT fDistance = VEC_DIST(vMovementPos, m_pAI->GetPosition());
        LTFLOAT fMovementNoiseDistance = g_pAIButeMgr->GetSenses()->fEnemyMovementNoiseDistance;
		fMovementNoiseDistance *= info.fVolume;

		if ( fDistance < (m_fDistance + fMovementNoiseDistance) )
		{
			IncreaseStimulation(fTimeDelta);

			// Record the timestamp

			m_fTimestamp = info.fTime;

			// Record the stimulus position

			m_vStimulusPosition = vMovementPos;

            return LTTRUE;
		}
	}

    return LTFALSE;
}
LTBOOL CAISenseSeeEnemyFlashlight::Update(HOBJECT hStimulus, LTFLOAT fTimeDelta)
{
	if ( !IsPlayer(hStimulus) ) return LTFALSE;

    CPlayerObj* pPlayer = (CPlayerObj*)g_pLTServer->HandleToObject(hStimulus);

	if ( pPlayer->IsFlashlightOn() )
	{
        const LTVector& vPos = pPlayer->GetFlashlightPos();
        const static LTFLOAT fRadiusSqr = 40000.0f;

        LTFLOAT fDistanceSqr = VEC_DISTSQR(m_pAI->GetPosition(), vPos);

		if ( fDistanceSqr < (fRadiusSqr) )
		{
            LTFLOAT fRateModifier = (1.0f - fDistanceSqr/m_fDistanceSqr);
			IncreaseStimulation(fTimeDelta, (fRateModifier));

            return LTTRUE;
		}
	}

    return LTFALSE;
}
LTBOOL CAISenseRecorderAbstract::UpdateSenseRecord(CAIStimulusRecord* pStimulusRecord, uint32 nCycle)
{
	if( !pStimulusRecord )
	{
		ASSERT(pStimulusRecord != LTNULL);
		return LTFALSE;
	}

	// Find sense record for stimulus.
	AISENSE_RECORD_MAP::iterator it = m_mapSenseRecords.find(pStimulusRecord->m_pAIBM_Stimulus->eSenseType);
	ASSERT(it != m_mapSenseRecords.end());

	AISenseRecord* pSenseRecord = it->second;

	// Only sense more recent stimuli, if flag is set to ignore old stimulus.

	if( pStimulusRecord->m_pAIBM_Stimulus->bIgnoreOldStimulus &&
		( pSenseRecord->fLastStimulationTime >= pStimulusRecord->m_fTimeStamp ) )
	{
		return LTFALSE;
	}


	LTFLOAT fRateModifier = 1.0f;

	// Do additional expensive checks here. (e.g. Ray casts).
	if(!HandleSpecificStimuli(pStimulusRecord, &fRateModifier))
	{
		return LTFALSE;
	}

	// Mark the sense as being updated this cycle.  The recorder can look at the 
	// cycle stamp later to determine which senses were updated during one cycle.
	pSenseRecord->nCycle = nCycle;

	// Keep a pointer to the bute file entry for the stimulus.
	AIBM_Stimulus* pAIBM_Stimulus = pStimulusRecord->m_pAIBM_Stimulus;
	pSenseRecord->pAIBM_Last_Stimulus = pAIBM_Stimulus;

	// Mark the timestamp of the stimulating sense.
	pSenseRecord->fLastStimulationTime = pStimulusRecord->m_fTimeStamp;

	// Record info about stimulus.
	pSenseRecord->hLastStimulusSource		= ( HOBJECT )pStimulusRecord->m_hStimulusSource;
	pSenseRecord->hLastStimulusTarget		= ( HOBJECT )pStimulusRecord->m_hStimulusTarget;
	pSenseRecord->eLastTargetMatchID		= pStimulusRecord->m_eTargetMatchID;
	pSenseRecord->vLastStimulusPos			= pStimulusRecord->m_vStimulusPos;
	pSenseRecord->vLastStimulusDir			= pStimulusRecord->m_vStimulusDir;
	pSenseRecord->nLastStimulusAlarmLevel	= pStimulusRecord->m_nStimulusAlarmLevel;
	pSenseRecord->eLastStimulusID			= pStimulusRecord->m_eStimulusID;

	// Randomize reaction delay time.
	// Do not reset the timer if stimulation already started.
	if( pSenseRecord->fCurStimulation <= 0.f )
	{
		pSenseRecord->fReactionDelayTime		= GetRandom( pAIBM_Stimulus->rngReactionDelay.GetMin(), pAIBM_Stimulus->rngReactionDelay.GetMax() );
		pSenseRecord->fReactionDelayTimer		= 0.f;
	}

	// Returns true if sense has reached full stimulation.
	return IncreaseStimulation(pSenseRecord, fRateModifier);
}
LTBOOL CAISenseSeeEnemy::Update(HOBJECT hStimulus, LTFLOAT fTimeDelta)
{
	if ( !IsCharacter(hStimulus) ) return LTFALSE;

	if ( m_pAI->GetCharacterClass() == NEUTRAL )
	{
		// If we're innocent, we only consider someone an enemy if they have a gun

        CCharacter* pCharacter = (CCharacter*)g_pLTServer->HandleToObject(hStimulus);
		if ( !pCharacter->HasDangerousWeapon() )
		{
            return LTFALSE;
		}
	}

	// Instead of looking right at the center of the target, we at a grid of points.
	// The grid is a plane with normal equal to the forward vector of the object,
	// in the center of the object, clipped to the objects dims. We scan the grid
	// at a given resolution and simply advance our scan col/row every frame. Note
	// that the grid is aligned with the objects rotation, not the bounding boxes,
	// since all the bounding boxes are axis aligned.

	int nXRange = m_rngGridX.GetMax() - m_rngGridX.GetMin();
	int nYRange = m_rngGridY.GetMax() - m_rngGridY.GetMin();

    LTVector vDims;
    g_pLTServer->GetObjectDims(hStimulus, &vDims);

    LTFLOAT fX = vDims.x * ((LTFLOAT)m_nGridX/(LTFLOAT)nXRange);
    LTFLOAT fY = vDims.y * ((LTFLOAT)m_nGridY/(LTFLOAT)nYRange);

    LTVector vPosition;
    g_pLTServer->GetObjectPos(hStimulus, &vPosition);

    LTRotation rRot;
    g_pLTServer->GetObjectRotation(hStimulus, &rRot);

    LTVector vUp, vRight, vForward;
    g_pLTServer->GetRotationVectors(&rRot, &vUp, &vRight, &vForward);

	vPosition += vRight*fX;
	vPosition += vUp*fY;

	// Update the point

    LTFLOAT fDistanceSqr;
    LTBOOL bVisible;

	if ( m_pAI->CanSeeThrough() )
	{
        bVisible = m_pAI->IsObjectPositionVisibleFromEye(CAI::SeeThroughFilterFn, CAI::SeeThroughPolyFilterFn, hStimulus, vPosition, (m_fDistanceSqr), LTTRUE, &fDistanceSqr);
	}
	else
	{
        bVisible = m_pAI->IsObjectPositionVisibleFromEye(CAI::DefaultFilterFn, NULL, hStimulus, vPosition, (m_fDistanceSqr), LTTRUE, &fDistanceSqr);
	}

	if ( bVisible )
	{
		if ( fDistanceSqr > g_pAIButeMgr->GetSenses()->fInstantSeeDistanceSqr )
		{
			LTFLOAT fRateModifier = (1.0f - fDistanceSqr/m_fDistanceSqr);
			IncreaseStimulation(fTimeDelta, (fRateModifier));
		}
		else
		{
			IncreaseStimulation(fTimeDelta, 99999999.0f);
		}
	}

	// Update our grid col/row values

	if ( ++m_nGridX > m_rngGridX.GetMax() )
	{
		m_nGridX = m_rngGridX.GetMin();

		if ( ++m_nGridY > m_rngGridY.GetMax() )
		{
			m_nGridY = m_rngGridY.GetMin();
		}
	}

	return bVisible;
}