示例#1
0
AINodeSearch* AIRegion::FindNearestSearchNode(const LTVector& vPos, LTFLOAT fCurTime) const
{
	_ASSERT(IsSearchable());

    LTFLOAT  fMinDistanceSqr = (float)INT_MAX;
    AINodeSearch* pClosestNode = LTNULL;

    for ( uint32 iSearchNode = 0 ; iSearchNode < m_cSearchNodes ; iSearchNode++ )
	{
		if ( m_apSearchNodes[iSearchNode]->GetType() != kNode_Search ) 
			continue;

		AINodeSearch* pNode = m_apSearchNodes[iSearchNode];
		if ( pNode->IsLockedDisabledOrTimedOut() )
			continue;

		LTFLOAT fDistanceSqr = VEC_DISTSQR(vPos, pNode->GetPos());

		if ( fDistanceSqr < fMinDistanceSqr )
		{
			if ( kStatus_Ok == pNode->GetStatus(vPos, LTNULL) )
			{
				fMinDistanceSqr = fDistanceSqr;
				pClosestNode = pNode;
			}
		}
	}

	return pClosestNode;
}
示例#2
0
LTBOOL RotatingDoor::TestObjectCollision(HOBJECT hTest, LTVector vTestPos,
            LTRotation rTestRot, HOBJECT* pCollisionObj)
{
	// Door::TestObjectCollision(hTest, vTestPos, rTestRot);

    HOBJECT hCollisionObj = LTNULL;

	if (Door::TestObjectCollision(hTest, vTestPos, rTestRot, &hCollisionObj))
	{
		// If the test object is the activate object, allow the door to
		// collide if it is moving away from the activate object...

		if (hTest || m_hActivateObj == hCollisionObj)
		{
            LTVector vObjPos, vDoorCurPos;
            g_pLTServer->GetObjectPos(m_hActivateObj, &vObjPos);
            g_pLTServer->GetObjectPos(m_hObject, &vDoorCurPos);

			// If the door's new position is farther away from the touch object
			// then its current position, we'll assume there can't be a collision.

            LTFLOAT fCurDist  = VEC_DISTSQR(vDoorCurPos, vObjPos);
            LTFLOAT fTestDist = VEC_DISTSQR(vTestPos, vObjPos);

            //g_pLTServer->CPrint("Activate object collision!");
            //g_pLTServer->CPrint("CurDist <= TestDist == %s", (fCurDist <= fTestDist) ? "TRUE" : "FALSE");

			if (fCurDist <= fTestDist)
			{
                return LTFALSE;
			}
			else
			{
                return LTTRUE;
			}
		}
		else
		{
            return LTTRUE;
		}
	}

    return LTFALSE;
}
示例#3
0
LTBOOL CAIGoalCharge::HandleGoalSenseTrigger(AISenseRecord* pSenseRecord)
{
	if( super::HandleGoalSenseTrigger(pSenseRecord) )
	{
		LTFLOAT fTargetDistanceSqr = VEC_DISTSQR( pSenseRecord->vLastStimulusPos, m_pAI->GetPosition());
		if( fTargetDistanceSqr > m_fStopDistanceSqr )
		{
			return LTTRUE;
		}
	}

	return LTFALSE;
}
//----------------------------------------------------------------------------
//              
//	ROUTINE:	CAIHumanStateObstruct::IsAICloseEnoughToNode()
//              
//	PURPOSE:	Returns true if the AI is within the prespecified range, false
//				if the player is not.
//              
//----------------------------------------------------------------------------
bool CAIHumanStateObstruct::IsAICloseEnoughToNode(void)
{
	if ( !m_hNodeToDoObstructAt )
		return false;

	LTVector vNodePos, vAIPos;
    g_pLTServer->GetObjectPos(m_hNodeToDoObstructAt, &vNodePos);
    g_pLTServer->GetObjectPos(GetAI()->m_hObject, &vAIPos);

	float ActualDistance = VEC_DISTSQR(vAIPos, vNodePos);

	// If the distance we must attain is less than the current distance..
	if ( ActualDistance < m_fCloseEnoughDistSqr )
		return true;

	return false;
}
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;
}
示例#6
0
DBOOL CMarkSFX::CreateObject(CClientDE *pClientDE)
{
	if (!CSpecialFX::CreateObject(pClientDE)) return DFALSE;

	CSFXMgr* psfxMgr = g_pBloodClientShell->GetSFXMgr();
	if (!psfxMgr) return DFALSE;


	// Before we create a new buillet hole see if there is already another
	// bullet hole close by that we could use instead...

	CSpecialFXList* pList = psfxMgr->GetBulletHoleFXList();
	if (!pList) return DFALSE;

	int nNumBulletHoles = pList->GetSize();

	HOBJECT hMoveObj		 = DNULL;
	HOBJECT hObj			 = DNULL;
	DFLOAT	fClosestMarkDist = REGION_DIAMETER;
	DBYTE	nNumInRegion	 = 0;
	DVector vPos;

	for (int i=0; i < nNumBulletHoles; i++)
	{
		if ((*pList)[i])
		{
			hObj = (*pList)[i]->GetObject();
			if (hObj)
			{
				pClientDE->GetObjectPos(hObj, &vPos);
				
				DFLOAT fDist = VEC_DISTSQR(vPos, m_Pos);
				if (fDist < REGION_DIAMETER)
				{
					if (fDist < fClosestMarkDist)
					{
						fClosestMarkDist = fDist;
						hMoveObj = hObj;
					}

					if (++nNumInRegion > MAX_MARKS_IN_REGION)
					{
						// Just move this bullet-hole to the correct pos, and
						// remove thyself...

						pClientDE->SetObjectPos(hMoveObj, &m_Pos);
						return DFALSE;
					}
				}
			}
		}
	}


	// Setup the mark...
	ObjectCreateStruct createStruct;
	INIT_OBJECTCREATESTRUCT(createStruct);

	createStruct.m_ObjectType = OT_SPRITE;
	_mbscpy((unsigned char*)createStruct.m_Filename, (const unsigned char*)m_pClientDE->GetStringData( m_hstrSprite ));
	createStruct.m_Flags	  = FLAG_VISIBLE | FLAG_ROTATEABLESPRITE;
	VEC_COPY(createStruct.m_Pos, m_Pos);
	ROT_COPY( createStruct.m_Rotation, m_Rotation );

	m_hObject = pClientDE->CreateObject(&createStruct);

	m_pClientDE->SetObjectScale(m_hObject, &m_vScale);


	// See what it hit
	DVector vU, vR;
	pClientDE->GetRotationVectors(&m_Rotation, &vU, &vR, &m_vForward);

	ClientIntersectQuery iq;
	ClientIntersectInfo  ii;

	iq.m_Flags = INTERSECT_OBJECTS | INTERSECT_HPOLY;

	VEC_COPY(iq.m_From, vPos);			// Get start point at the last known position.
	VEC_MULSCALAR(iq.m_To, m_vForward, -1.0f);
	VEC_ADD(iq.m_To, iq.m_To, iq.m_From);	// Get destination point slightly past where we should be

	// Hit something!  try to clip against it. (since this is only being used for bullet marks,
	if (pClientDE->IntersectSegment(&iq, &ii))
	{
		HPOLY hPoly = ii.m_hPoly;
		pClientDE->ClipSprite(m_hObject, hPoly);
	}
	m_pClientDE->SetObjectColor(m_hObject, 0.1f, 0.1f, 0.1f, 1.0f);
	
	return DTRUE;
}
示例#7
0
bool WorldModel::CreateServerMark(CLIENTWEAPONFX & theStruct)
{
	// See if we should create a mark, or simply move one of the GameBase's
	// marks.

	// If the GameBase has the max number of marks or this mark is very close
	// to a pre-existing mark, just move that mark to the new position.

    HOBJECT hMoveObj = LTNULL;
    HOBJECT hFarObj  = LTNULL;

    uint32 nNumMarks = m_MarkList.size( );

    LTFLOAT  fClosestMarkDist  = REGION_DIAMETER;
    LTFLOAT  fFarthestMarkDist = 0.0f;
    uint8   nNumInRegion      = 0;
    LTVector vPos;

	for( ObjRefNotifierList::iterator iter = m_MarkList.begin( ); iter != m_MarkList.end( ); iter++ )
	{
		HOBJECT hObj = *iter;
		if( !hObj )
			continue;

		HATTACHMENT hAttachment = NULL;
        if (LT_OK == g_pLTServer->FindAttachment( m_hObject, hObj, &hAttachment))
		{
			LTransform transform;
            g_pCommonLT->GetAttachmentTransform(hAttachment, transform, LTTRUE);

			vPos = transform.m_Pos;
		}

        LTFLOAT fDist = VEC_DISTSQR(vPos, theStruct.vPos);
		if (fDist < REGION_DIAMETER)
		{
			if (fDist < fClosestMarkDist)
			{
				fClosestMarkDist = fDist;
				hMoveObj = hObj;
			}

			if (++nNumInRegion > MAX_MARKS_IN_REGION)
			{
				// Just move this mark to the correct pos...
				hMoveObj = hMoveObj ? hMoveObj : hObj;
				break;
			}
		}

		if (fDist > fFarthestMarkDist)
		{
			fFarthestMarkDist = fDist;
			hFarObj = hObj;
		}
	}

	// If we've got the max number of marks on this object, just move
	// the closest one to the new position...

	if (nNumMarks >= MAX_MARKS_PER_OBJECT)
	{
		if( !hMoveObj )
		{
			if( hFarObj )
			{
				hMoveObj = hFarObj;
			}
			else
			{
				HOBJECT hFirstMark = *m_MarkList.begin( );
				hMoveObj = hFirstMark;
			}
		}
	}
	else
	{
        hMoveObj = LTNULL; // Need to create one...
	}

	// Re-setup the object to move it...

	if (hMoveObj && IsKindOf(hMoveObj, "CServerMark"))
	{
        CServerMark* pMoveMark = (CServerMark*) g_pLTServer->HandleToObject(hMoveObj);
		if (!pMoveMark)
			return false;

		// Since this mark is already attached to us, remove the attachment
		DetachObject( pMoveMark->m_hObject );

		if( !AttachServerMark( *pMoveMark, (CLIENTWEAPONFX)theStruct))
		{
			g_pLTServer->RemoveObject( pMoveMark->m_hObject );
			RemoveMarkFromList( pMoveMark->m_hObject );
			pMoveMark = NULL;
			return false;
		}

		return true;
	}


	// Okay, no luck, need to create a new mark...

	ObjectCreateStruct createStruct;
	INIT_OBJECTCREATESTRUCT(createStruct);

    LTFLOAT fScaleAdjust = 1.0f;
	if (!GetImpactSprite((SurfaceType)theStruct.nSurfaceType, fScaleAdjust,
		theStruct.nAmmoId, createStruct.m_Filename, ARRAY_LEN(createStruct.m_Filename)))
	{
		return false;
	}

	createStruct.m_ObjectType = OT_SPRITE;
	createStruct.m_Flags = FLAG_VISIBLE | FLAG_NOLIGHT | FLAG_ROTATEABLESPRITE;
	createStruct.m_Pos = theStruct.vPos;

	createStruct.m_Rotation = LTRotation(theStruct.vSurfaceNormal, LTVector(0.0f, 1.0f, 0.0f));

	AMMO const *pAmmo = g_pWeaponMgr->GetAmmo(theStruct.nAmmoId);
	if( !pAmmo ) 
		return false;

    static HCLASS hClass = g_pLTServer->GetClass("CServerMark");
    CServerMark* pMark = (CServerMark*) g_pLTServer->CreateObject(hClass, &createStruct);
	if (!pMark)
		return false;


	// Randomly adjust the mark's scale to add a bit o spice...

	if (pAmmo->pImpactFX)
	{
        LTFLOAT fScale = fScaleAdjust * pAmmo->pImpactFX->fMarkScale;

        LTVector vScale(fScale, fScale, fScale);
        g_pLTServer->ScaleObject(pMark->m_hObject, &vScale);
	}

	if( !AttachServerMark( *pMark, (CLIENTWEAPONFX)theStruct))
	{
		g_pLTServer->RemoveObject( pMark->m_hObject );
		pMark = NULL;
		return false;
	}

	AddMarkToList( pMark->m_hObject );

	return true;
}
示例#8
0
AINode* CAINodeMgr::FindNearestObjectNode(CAI* pAI, EnumAINodeType eNodeType, const LTVector& vPos, const char* szClass)
{
    LTFLOAT fMinDistanceSqr = (float)INT_MAX;
    AINode* pClosestNode = LTNULL;

	// Get AIs Path Knowledge.

	CAIPathKnowledgeMgr* pPathKnowledgeMgr = LTNULL;
	if( pAI && pAI->GetPathKnowledgeMgr() )
	{
		pPathKnowledgeMgr = pAI->GetPathKnowledgeMgr();
	}

	AINode* pNode;
	AINODE_MAP::iterator it;
	for(it = m_mapAINodes.lower_bound(eNodeType); it != m_mapAINodes.upper_bound(eNodeType); ++it)
	{
		pNode = it->second;

		// Skip nodes in unreachable volumes.

		if( pPathKnowledgeMgr && 
			( pPathKnowledgeMgr->GetPathKnowledge( pNode->GetNodeContainingVolume() ) == CAIPathMgr::kPath_NoPathFound ) )
		{
			continue;
		}

		// Skip nodes that are not in volumes.

		if( !pNode->GetNodeContainingVolume() )
		{
			continue;
		}

		// Skip node if required alignment does not match.

		if( ( pNode->GetRequiredRelationTemplateID() != -1 ) &&
			( pNode->GetRequiredRelationTemplateID() != pAI->GetRelationMgr()->GetTemplateID() ) )
		{
			continue;
		}

		if( !pNode->NodeTypeIsActive( eNodeType ) )
		{
			continue;
		}

		if ( !pNode->IsLockedDisabledOrTimedOut() && pNode->HasObject() )
		{
            LTFLOAT fDistanceSqr = VEC_DISTSQR(vPos, pNode->GetPos());

			if ( (fDistanceSqr < fMinDistanceSqr) && (fDistanceSqr < pNode->GetRadiusSqr()) )
			{
				HOBJECT hObject;
				if ( LT_OK == FindNamedObject(pNode->GetObject(), hObject) )
				{
                    HCLASS hClass = g_pLTServer->GetClass((char*)szClass);

                    if ( g_pLTServer->IsKindOf(g_pLTServer->GetObjectClass(hObject), hClass) )
					{
						fMinDistanceSqr = fDistanceSqr;
						pClosestNode = pNode;
					}
				}
			}
		}
	}

	// Ensure that AI can pathfind to the destination node.
	// Ideally, we would like to do this check for each node as we iterate,
	// but that could result in multiple runs of BuildVolumePath() which
	// is expensive.  So instead we just check the final returned node.
	// The calling code can call this function again later, and will not get
	// this node again.

	if( pAI && pClosestNode )
	{
		AIVolume* pVolumeDest = pClosestNode->GetNodeContainingVolume();
		if( !g_pAIPathMgr->HasPath( pAI, pVolumeDest ) )
		{
			return LTNULL;
		}
	}

	return pClosestNode;
}
示例#9
0
AINode* CAINodeMgr::FindNearestNodeFromThreat(CAI* pAI, EnumAINodeType eNodeType, const LTVector& vPos, HOBJECT hThreat, LTFLOAT fSearchFactor)
{
    LTFLOAT fMinDistance = (float)INT_MAX;
    AINode*	pClosestNode = LTNULL;

	// Get AIs Path Knowledge.

	CAIPathKnowledgeMgr* pPathKnowledgeMgr = LTNULL;
	if( pAI && pAI->GetPathKnowledgeMgr() )
	{
		pPathKnowledgeMgr = pAI->GetPathKnowledgeMgr();
	}

	AINode* pNode;
	AINODE_MAP::iterator it;
	for(it = m_mapAINodes.lower_bound(eNodeType); it != m_mapAINodes.upper_bound(eNodeType); ++it)
	{
		pNode = it->second;

		// Skip nodes in unreachable volumes.

		if( pPathKnowledgeMgr && 
			( pPathKnowledgeMgr->GetPathKnowledge( pNode->GetNodeContainingVolume() ) == CAIPathMgr::kPath_NoPathFound ) )
		{
			continue;
		}

		// Skip nodes that are not in volumes.

		if( !pNode->GetNodeContainingVolume() )
		{
			continue;
		}

		// Skip node if required alignment does not match.

		if( ( pNode->GetRequiredRelationTemplateID() != -1 ) &&
			( pNode->GetRequiredRelationTemplateID() != pAI->GetRelationMgr()->GetTemplateID() ) )
		{
			continue;
		}

		if( !pNode->NodeTypeIsActive( eNodeType ) )
		{
			continue;
		}

		if ( !pNode->IsLockedDisabledOrTimedOut() )
		{
			// Check of there is a SearchFactor, scaling the radius of the node.

			LTFLOAT fNodeRadiusSqr;
			if( fSearchFactor != 1.f )
			{
				fNodeRadiusSqr = pNode->GetRadius() * fSearchFactor;
				fNodeRadiusSqr *= fNodeRadiusSqr;
			}
			else {
				fNodeRadiusSqr = pNode->GetRadiusSqr();
			}

			LTFLOAT fDistanceSqr = VEC_DISTSQR(vPos, pNode->GetPos());

			if ( ( fDistanceSqr < fMinDistance ) && ( fDistanceSqr < fNodeRadiusSqr ) )
			{
				if ( kStatus_Ok == pNode->GetStatus(vPos, hThreat) )
				{
					fMinDistance = fDistanceSqr;
					pClosestNode = pNode;
				}
			}
		}
	}

	// Ensure that AI can pathfind to the destination node.
	// Ideally, we would like to do this check for each node as we iterate,
	// but that could result in multiple runs of BuildVolumePath() which
	// is expensive.  So instead we just check the final returned node.
	// The calling code can call this function again later, and will not get
	// this node again.

	if( pAI && pClosestNode )
	{
		AIVolume* pVolumeDest = pClosestNode->GetNodeContainingVolume();
		if( !g_pAIPathMgr->HasPath( pAI, pVolumeDest ) )
		{
			return LTNULL;
		}
	}

	return pClosestNode;
}
示例#10
0
AINodeTail* CAINodeMgr::FindTailNode(CAI* pAI, const LTVector& vTargetPos, const LTVector& vPos)
{
    LTFLOAT fTailedDistance;
    LTFLOAT fTailerDistance;
    LTFLOAT fMinTailedDistance = (float)INT_MAX;
    LTFLOAT fMinTailerDistance = (float)INT_MAX;
	AINode* pNode;
	AINode* pTailedNode = LTNULL;
	AINode* pTailerNode = LTNULL;
	AINODE_MAP::iterator it;

	// Get AIs Path Knowledge.

	CAIPathKnowledgeMgr* pPathKnowledgeMgr = LTNULL;
	if( pAI && pAI->GetPathKnowledgeMgr() )
	{
		pPathKnowledgeMgr = pAI->GetPathKnowledgeMgr();
	}

	// Find the node closest to the tailed object and to the tailer.

	for(it = m_mapAINodes.lower_bound(kNode_Tail); it != m_mapAINodes.upper_bound(kNode_Tail); ++it)
	{
		pNode = it->second;

		// Skip nodes in unreachable volumes.

		if( pPathKnowledgeMgr && 
			( pPathKnowledgeMgr->GetPathKnowledge( pNode->GetNodeContainingVolume() ) == CAIPathMgr::kPath_NoPathFound ) )
		{
			continue;
		}

		// Skip nodes that are not in volumes.

		if( !pNode->GetNodeContainingVolume() )
		{
			continue;
		}

		// Skip node if required alignment does not match.

		if( ( pNode->GetRequiredRelationTemplateID() != -1 ) &&
			( pNode->GetRequiredRelationTemplateID() != pAI->GetRelationMgr()->GetTemplateID() ) )
		{
			continue;
		}

        fTailedDistance = VEC_DISTSQR(vTargetPos, pNode->GetPos());
        fTailerDistance = VEC_DISTSQR(vPos, pNode->GetPos());

		if ( fTailedDistance < fMinTailedDistance )
		{
			pTailedNode = pNode;
			fMinTailedDistance = fTailedDistance;
		}

		if ( fTailerDistance < fMinTailerDistance )
		{
			pTailerNode = pNode;
			fMinTailerDistance = fTailerDistance;
		}

	}

	// Figure out what the tail node is based on these two nodes

	uint32 iTailedNode = GetNodeIndexFromName( pTailedNode );
	uint32 iTailerNode = GetNodeIndexFromName( pTailerNode );
	uint32 iTailNode = -1;

	// If the tailer is less than the tailed node, the tail is the tailed node minus 1

	if ( iTailerNode < iTailedNode )
	{
		iTailNode = Max<uint32>(0, iTailedNode-1);
	}

	// If the tailer is greater than the tailed node, the tail is the tailed node plus 1

	if ( iTailerNode > iTailedNode )
	{
		iTailNode = Min<uint32>( m_mapAINodes.count(kNode_Tail)-1, iTailedNode+1);
	}

	// If the tail node is equal to the tailednode, then there is no good node to go to.

	if ( iTailerNode == iTailedNode )
	{
		return LTNULL;
	}
	else
	{
		AINodeTail* pNodeTail = (AINodeTail*)FindNodeByIndex(kNode_Tail, iTailNode);

		// Ensure that AI can pathfind to the destination node.
		// Ideally, we would like to do this check for each node as we iterate,
		// but that could result in multiple runs of BuildVolumePath() which
		// is expensive.  So instead we just check the final returned node.
		// The calling code can call this function again later, and will not get
		// this node again.

		if( pAI && pNodeTail )
		{
			AIVolume* pVolumeDest = pNodeTail->GetNodeContainingVolume();
			if( !g_pAIPathMgr->HasPath( pAI, pVolumeDest ) )
			{
				return LTNULL;
			}
		}

		return pNodeTail;
	}
}
示例#11
0
AINode* CAINodeMgr::FindNearestOwnedNode(CAI* pAI, EnumAINodeType eNodeType, const LTVector& vPos, HOBJECT hOwner)
{
	// It is NOT OK for hOwner to be NULL.  Only return nodes that are owned by someone.

	if( !hOwner )
	{
		return LTNULL;
	}

	// Get AIs Path Knowledge.

	CAIPathKnowledgeMgr* pPathKnowledgeMgr = LTNULL;
	if( pAI && pAI->GetPathKnowledgeMgr() )
	{
		pPathKnowledgeMgr = pAI->GetPathKnowledgeMgr();
	}

    LTFLOAT fMinDistanceSqr = (float)INT_MAX;
    AINode* pClosestNode = LTNULL;

	AINode* pNode;
	AINODE_MAP::iterator it;
	for(it = m_mapAINodes.lower_bound(eNodeType); it != m_mapAINodes.upper_bound(eNodeType); ++it)
	{
		pNode = it->second;

		// Skip nodes in unreachable volumes.

		if( pPathKnowledgeMgr && 
			( pPathKnowledgeMgr->GetPathKnowledge( pNode->GetNodeContainingVolume() ) == CAIPathMgr::kPath_NoPathFound ) )
		{
			continue;
		}

		// Skip nodes that are not in volumes.

		if( !pNode->GetNodeContainingVolume() )
		{
			continue;
		}

		// Skip node if required alignment does not match.

		if( ( pNode->GetRequiredRelationTemplateID() != -1 ) &&
			( pNode->GetRequiredRelationTemplateID() != pAI->GetRelationMgr()->GetTemplateID() ) )
		{
			continue;
		}

		if( !pNode->NodeTypeIsActive( eNodeType ) )
		{
			continue;
		}

		if( pNode->GetNodeOwner() != hOwner )
		{
			continue;
		}

		// Owned nodes are locked by the owner, so just check for
		// disabled and timed out.

		if ( !( pNode->IsDisabled() || pNode->IsTimedOut() ) )
		{
	        LTFLOAT  fDistanceSqr = VEC_DISTSQR(vPos, pNode->GetPos());
			if ( fDistanceSqr < fMinDistanceSqr )
			{
				fMinDistanceSqr	= fDistanceSqr;
				pClosestNode	= pNode;
			}
		}
	}

	// Ensure that AI can pathfind to the destination node.
	// Ideally, we would like to do this check for each node as we iterate,
	// but that could result in multiple runs of BuildVolumePath() which
	// is expensive.  So instead we just check the final returned node.
	// The calling code can call this function again later, and will not get
	// this node again.

	if( pAI && pClosestNode )
	{
		AIVolume* pVolumeDest = pClosestNode->GetNodeContainingVolume();
		if( !g_pAIPathMgr->HasPath( pAI, pVolumeDest ) )
		{
			return LTNULL;
		}
	}

	return pClosestNode;
}
static void CreateServerMark(CLIENTWEAPONFX & theStruct)
{
	AMMO* pAmmo = g_pWeaponMgr->GetAmmo(theStruct.nAmmoId);
	if (!pAmmo) return;

	// If this isn't a GameBase object, return...

	if (!IsGameBase(theStruct.hObj)) return;

	// See if we should create a mark, or simply move one of the GameBase's
	// marks.

	// If the GameBase has the max number of marks or this mark is very close
	// to a pre-existing mark, just move that mark to the new position.

    GameBase* pObj = (GameBase*) g_pLTServer->HandleToObject(theStruct.hObj);
	if (!pObj) return;

    HOBJECT hMoveObj = LTNULL;
    HOBJECT hFarObj  = LTNULL;

	ObjectList* pMarkList = pObj->GetMarkList();
	if (pMarkList)
	{
        uint8 nNumMarks = pMarkList->m_nInList;
		ObjectLink* pLink = pMarkList->m_pFirstLink;

        LTFLOAT  fClosestMarkDist  = REGION_DIAMETER;
        LTFLOAT  fFarthestMarkDist = 0.0f;
        uint8   nNumInRegion      = 0;
        LTVector vPos;

		for (int i=0; i < nNumMarks && pLink; i++)
		{
			if (pLink->m_hObject)
			{
				HATTACHMENT hAttachment;
                if (LT_OK == g_pLTServer->FindAttachment(theStruct.hObj, pLink->m_hObject, &hAttachment))
				{
					LTransform transform;
                    g_pLTServer->Common()->GetAttachmentTransform(hAttachment, transform, LTTRUE);

					vPos = transform.m_Pos;
				}

                LTFLOAT fDist = VEC_DISTSQR(vPos, theStruct.vPos);
				if (fDist < REGION_DIAMETER)
				{
					if (fDist < fClosestMarkDist)
					{
						fClosestMarkDist = fDist;
						hMoveObj = pLink->m_hObject;
					}

					if (++nNumInRegion > MAX_MARKS_IN_REGION)
					{
						// Just move this mark to the correct pos...
						hMoveObj = hMoveObj ? hMoveObj : pLink->m_hObject;
						break;
					}
				}

				if (fDist > fFarthestMarkDist)
				{
					fFarthestMarkDist = fDist;
					hFarObj = pLink->m_hObject;
				}
			}

			pLink = pLink->m_pNext;
		}

		// If we've got the max number of marks on this object, just move
		// the closest one to the new position...

		if (nNumMarks >= MAX_MARKS_PER_OBJECT)
		{
			hMoveObj = hMoveObj ? hMoveObj : (hFarObj ? hFarObj : pMarkList->m_pFirstLink->m_hObject);
		}
		else
		{
            hMoveObj = LTNULL; // Need to create one...
		}
	}

	// Re-setup the object to move it...

	if (hMoveObj && IsKindOf(hMoveObj, "CServerMark"))
	{
        CServerMark* pMoveMark = (CServerMark*) g_pLTServer->HandleToObject(hMoveObj);
		if (!pMoveMark) return;

		// Since this mark is already attached to pObj, remove the attachment
		// (since CServerMark::Setup() will re-attach it)...

		HATTACHMENT hAttachment;
        if (LT_OK == g_pLTServer->FindAttachment(theStruct.hObj, hMoveObj, &hAttachment))
		{
            g_pLTServer->RemoveAttachment(hAttachment);
		}

        pMoveMark->Setup((CLIENTWEAPONFX)theStruct);
		return;
	}


	// Okay, no luck, need to create a new mark...

	ObjectCreateStruct createStruct;
	INIT_OBJECTCREATESTRUCT(createStruct);

    LTFLOAT fScaleAdjust = 1.0f;
	if (!GetImpactSprite((SurfaceType)theStruct.nSurfaceType, fScaleAdjust,
		theStruct.nAmmoId, createStruct.m_Filename, ARRAY_LEN(createStruct.m_Filename)))
	{
		return;
	}

	createStruct.m_ObjectType = OT_SPRITE;
	createStruct.m_Flags = FLAG_VISIBLE | FLAG_NOLIGHT | FLAG_ROTATEABLESPRITE;
	createStruct.m_Pos = theStruct.vPos;

    g_pLTServer->AlignRotation(&(createStruct.m_Rotation), &((LTVector)theStruct.vSurfaceNormal), LTNULL);


    HCLASS hClass = g_pLTServer->GetClass("CServerMark");
    CServerMark* pMark = (CServerMark*) g_pLTServer->CreateObject(hClass, &createStruct);
	if (!pMark) return;

	// Add the mark to the object...

	pObj->AddMark(pMark->m_hObject);


	// Randomly adjust the mark's scale to add a bit o spice...

	if (pAmmo->pImpactFX)
	{
        LTFLOAT fScale = fScaleAdjust * pAmmo->pImpactFX->fMarkScale;

        LTVector vScale;
		VEC_SET(vScale, fScale, fScale, fScale);
        g_pLTServer->ScaleObject(pMark->m_hObject, &vScale);
	}

	pMark->Setup((CLIENTWEAPONFX)theStruct);
}