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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; } }
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); }