//----------------------------------------------------------------------------- // Computes the link type //----------------------------------------------------------------------------- Navigation_t CAI_Pathfinder::ComputeWaypointType( CAI_Node **ppNodes, int parentID, int destID ) { Navigation_t navType = NAV_NONE; CAI_Node *pNode = ppNodes[parentID]; for (int link=0; link < pNode->NumLinks();link++) { if (pNode->GetLinkByIndex(link)->DestNodeID(parentID) == destID) { // BRJ 10/1/02 // FIXME: pNPC->CapabilitiesGet() is actually the mechanism by which fliers // filter out the bitfields in the waypoint type (most importantly, bits_MOVE_CAP_GROUND) // that would cause the waypoint distance to be computed in a 2D, as opposed to 3D fashion // This is a super-scary weak link if you ask me. int linkMoveTypeBits = pNode->GetLinkByIndex(link)->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( linkMoveTypeBits & CapabilitiesGet()); if ( !moveTypeBits && linkMoveTypeBits == bits_CAP_MOVE_JUMP ) { Assert( pNode->GetHint() && pNode->GetHint()->HintType() == HINT_JUMP_OVERRIDE ); ppNodes[destID]->Lock(0.3); moveTypeBits = linkMoveTypeBits; } Navigation_t linkType = MoveBitsToNavType( moveTypeBits ); // This will only trigger if the links disagree about their nav type Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType; break; } } // @TODO (toml 10-15-02): one would not expect to come out of the above logic // with NAV_NONE. However, if a graph is newly built, it can contain malformed // links that are referred to by the destination node, not the source node. // This has to be fixed if ( navType == NAV_NONE ) { pNode = ppNodes[destID]; for (int link=0; link < pNode->NumLinks();link++) { if (pNode->GetLinkByIndex(link)->DestNodeID(parentID) == destID) { int npcMoveBits = CapabilitiesGet(); int nodeMoveBits = pNode->GetLinkByIndex(link)->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( npcMoveBits & nodeMoveBits ); Navigation_t linkType = MoveBitsToNavType( moveTypeBits ); Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType; DevMsg( "Note: Strange link found between nodes in AI node graph\n" ); break; } } } AssertMsg( navType != NAV_NONE, "Pathfinder appears to have output a path with consecutive nodes thate are not actually connected\n" ); return navType; }
//----------------------------------------------------------------------------- // Purpose: For destroying nodes in wc edit mode // Input : // Output : //----------------------------------------------------------------------------- void NWCEdit::DestroyAINode( CBasePlayer *pPlayer ) { // ------------------------------------------------------------- // Check that WC is running with the right map version // ------------------------------------------------------------- if (!IsWCVersionValid()) { return; } if (!pPlayer) { return; } NodeType_e nNodeType = NODE_GROUND; if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode) { nNodeType = NODE_AIR; } CAI_Node* pAINode = FindPickerAINode(pPlayer, nNodeType); if (pAINode) { int status = Editor_DeleteNode(g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAINode->GetId()], false); if (status == Editor_BadCommand) { Msg( "Worldcraft failed on deletion...\n" ); } else if (status == Editor_OK) { // Mark this node as deleted and changed pAINode->SetType( NODE_DELETED ); pAINode->m_eNodeInfo |= bits_NODE_WC_CHANGED; // Note that network needs to be rebuild g_pAINetworkManager->GetEditOps()->SetRebuildFlags(); g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode = pAINode; // Now go through at delete any dynamic links that were attached to this node for (int link = 0; link < pAINode->NumLinks(); link++) { int nSrcID = pAINode->GetLinkByIndex(link)->m_iSrcID; int nDstID = pAINode->GetLinkByIndex(link)->m_iDestID; if (CAI_DynamicLink::GetDynamicLink(nSrcID, nDstID)) { int nWCSrcID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[nSrcID]; int nWCDstID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[nDstID]; int status = Editor_DeleteNodeLink(nWCSrcID, nWCDstID); if (status == Editor_BadCommand) { Msg( "Worldcraft failed on node link deletion...\n" ); } } } } } }
void CAI_DynamicLinkController::GenerateLinksFromVolume() { Assert( m_ControlledLinks.Count() == 0 ); int nNodes = g_pBigAINet->NumNodes(); CAI_Node **ppNodes = g_pBigAINet->AccessNodes(); const float MinDistCareSq = Square(MAX_NODE_LINK_DIST + 0.1); const Vector &origin = WorldSpaceCenter(); Vector vAbsMins, vAbsMaxs; CollisionProp()->WorldSpaceAABB( &vAbsMins, &vAbsMaxs ); vAbsMins -= Vector( 1, 1, 1 ); vAbsMaxs += Vector( 1, 1, 1 ); for ( int i = 0; i < nNodes; i++ ) { CAI_Node *pNode = ppNodes[i]; const Vector &nodeOrigin = pNode->GetOrigin(); if ( origin.DistToSqr(nodeOrigin) < MinDistCareSq ) { int nLinks = pNode->NumLinks(); for ( int j = 0; j < nLinks; j++ ) { CAI_Link *pLink = pNode->GetLinkByIndex( j ); int iLinkDest = pLink->DestNodeID( i ); if ( iLinkDest > i ) { const Vector &originOther = ppNodes[iLinkDest]->GetOrigin(); if ( origin.DistToSqr(originOther) < MinDistCareSq ) { if ( IsBoxIntersectingRay( vAbsMins, vAbsMaxs, nodeOrigin, originOther - nodeOrigin ) ) { Assert( IsBoxIntersectingRay( vAbsMins, vAbsMaxs, originOther, nodeOrigin - originOther ) ); CAI_DynamicLink *pLink = (CAI_DynamicLink *)CreateEntityByName( "info_node_link" ); pLink->m_nSrcID = i; pLink->m_nDestID = iLinkDest; pLink->m_nSrcEditID = g_pAINetworkManager->GetEditOps()->GetWCIdFromNodeId( pLink->m_nSrcID ); pLink->m_nDestEditID = g_pAINetworkManager->GetEditOps()->GetWCIdFromNodeId( pLink->m_nDestID ); pLink->m_nLinkState = m_nLinkState; pLink->m_strAllowUse = m_strAllowUse; pLink->m_bFixedUpIds = true; pLink->m_bNotSaved = true; pLink->Spawn(); m_ControlledLinks.AddToTail( pLink ); } } } } } } }
void CAI_RadialLinkController::ModifyNodeLinks( bool bMakeStale ) { int nNodes = g_pBigAINet->NumNodes(); CAI_Node **ppNodes = g_pBigAINet->AccessNodes(); VPROF_BUDGET("ModifyLinks", "ModifyLinks"); const float MinDistCareSq = Square( ai_radial_max_link_dist.GetFloat() + 0.1 ); for ( int i = 0; i < nNodes; i++ ) { CAI_Node *pNode = ppNodes[i]; const Vector &nodeOrigin = pNode->GetOrigin(); if ( m_vecAtRestOrigin.DistToSqr(nodeOrigin) < MinDistCareSq ) { int nLinks = pNode->NumLinks(); for ( int j = 0; j < nLinks; j++ ) { CAI_Link *pLink = pNode->GetLinkByIndex( j ); int iLinkDest = pLink->DestNodeID( i ); if ( iLinkDest > i ) { bool bQualify = true; if( ( (pLink->m_iAcceptedMoveTypes[HULL_HUMAN]||pLink->m_iAcceptedMoveTypes[HULL_WIDE_HUMAN]) & bits_CAP_MOVE_GROUND) == 0 ) { // Micro-optimization: Ignore any connection that's not a walking connection for humans.(sjb) bQualify = false; } const Vector &originOther = ppNodes[iLinkDest]->GetOrigin(); if ( bQualify && m_vecAtRestOrigin.DistToSqr(originOther) < MinDistCareSq ) { if ( IsRayIntersectingSphere(nodeOrigin, originOther - nodeOrigin, m_vecAtRestOrigin, m_flRadius) ) { if( bMakeStale ) { pLink->m_LinkInfo |= bits_LINK_STALE_SUGGESTED; pLink->m_timeStaleExpires = FLT_MAX; } else { pLink->m_LinkInfo &= ~bits_LINK_STALE_SUGGESTED; } } } } } } } }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------ CAI_Link *CAI_DynamicLink::FindLink() { CAI_Node * pSrcNode = g_pBigAINet->GetNode(m_nSrcID, false); if ( pSrcNode ) { int numLinks = pSrcNode->NumLinks(); for (int i=0;i<numLinks;i++) { CAI_Link* pLink = pSrcNode->GetLinkByIndex(i); if (((pLink->m_iSrcID == m_nSrcID )&& (pLink->m_iDestID == m_nDestID)) || ((pLink->m_iSrcID == m_nDestID)&& (pLink->m_iDestID == m_nSrcID )) ) { return pLink; } } } return NULL; }
AI_Waypoint_t *CAI_Pathfinder::FindBestPath(int startID, int endID) { if ( !GetNetwork()->NumNodes() ) return NULL; int nNodes = GetNetwork()->NumNodes(); CAI_Node **pAInode = GetNetwork()->AccessNodes(); CVarBitVec openBS(nNodes); CVarBitVec closeBS(nNodes); // ------------- INITIALIZE ------------------------ float* nodeG = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeH = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeF = (float *)stackalloc( nNodes * sizeof(float) ); int* nodeP = (int *)stackalloc( nNodes * sizeof(int) ); // Node parent for (int node=0;node<nNodes;node++) { nodeG[node] = FLT_MAX; nodeP[node] = -1; } nodeG[startID] = 0; nodeH[startID] = 0.1*(pAInode[startID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); // Don't want to over estimate nodeF[startID] = nodeG[startID] + nodeH[startID]; openBS.Set(startID); closeBS.Set( startID ); // --------------- FIND BEST PATH ------------------ while (!openBS.IsAllClear()) { int smallestID = CAI_Network::FindBSSmallest(&openBS,nodeF,nNodes); openBS.Clear(smallestID); CAI_Node *pSmallestNode = pAInode[smallestID]; if (GetOuter()->IsUnusableNode(smallestID, pSmallestNode->m_pHint)) continue; if (smallestID == endID) { AI_Waypoint_t* route = MakeRouteFromParents(&nodeP[0], endID); return route; } // Check this if the node is immediately in the path after the startNode // that it isn't blocked for (int link=0; link < pSmallestNode->NumLinks();link++) { CAI_Link *nodeLink = pSmallestNode->GetLinkByIndex(link); if (!IsLinkUsable(nodeLink,smallestID)) continue; // FIXME: the cost function should take into account Node costs (danger, flanking, etc). int moveType = nodeLink->m_iAcceptedMoveTypes[GetHullType()] & CapabilitiesGet(); int testID = nodeLink->DestNodeID(smallestID); Vector r1 = pSmallestNode->GetPosition(GetHullType()); Vector r2 = pAInode[testID]->GetPosition(GetHullType()); float dist = GetOuter()->GetNavigator()->MovementCost( moveType, r1, r2 ); // MovementCost takes ref parameters!! if ( dist == FLT_MAX ) continue; float new_g = nodeG[smallestID] + dist; if ( !closeBS.IsBitSet(testID) || (new_g < nodeG[testID]) ) { nodeP[testID] = smallestID; nodeG[testID] = new_g; nodeH[testID] = (pAInode[testID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); nodeF[testID] = nodeG[testID] + nodeH[testID]; closeBS.Set( testID ); openBS.Set( testID ); } } } return NULL; }
// heuristic to find reasonably open space - searches for areas with high node connectivity CASW_Open_Area* CASW_Spawn_Manager::FindNearbyOpenArea( const Vector &vecSearchOrigin, int nSearchHull ) { CBaseEntity *pStartEntity = gEntList.FindEntityByClassname( NULL, "info_player_start" ); int iNumNodes = g_pBigAINet->NumNodes(); CAI_Node *pHighestConnectivity = NULL; int nHighestLinks = 0; for ( int i=0 ; i<iNumNodes; i++ ) { CAI_Node *pNode = g_pBigAINet->GetNode( i ); if ( !pNode || pNode->GetType() != NODE_GROUND ) continue; Vector vecPos = pNode->GetOrigin(); float flDist = vecPos.DistTo( vecSearchOrigin ); if ( flDist > 400.0f ) continue; // discard if node is too near start location if ( pStartEntity && vecPos.DistTo( pStartEntity->GetAbsOrigin() ) < 1400.0f ) // NOTE: assumes all start points are clustered near one another continue; // discard if node is inside an escape area bool bInsideEscapeArea = false; for ( int d=0; d<m_EscapeTriggers.Count(); d++ ) { if ( m_EscapeTriggers[d]->CollisionProp()->IsPointInBounds( vecPos ) ) { bInsideEscapeArea = true; break; } } if ( bInsideEscapeArea ) continue; // count links that drones could follow int nLinks = pNode->NumLinks(); int nValidLinks = 0; for ( int k = 0; k < nLinks; k++ ) { CAI_Link *pLink = pNode->GetLinkByIndex( k ); if ( !pLink ) continue; if ( !( pLink->m_iAcceptedMoveTypes[nSearchHull] & bits_CAP_MOVE_GROUND ) ) continue; nValidLinks++; } if ( nValidLinks > nHighestLinks ) { nHighestLinks = nValidLinks; pHighestConnectivity = pNode; } if ( asw_director_debug.GetBool() ) { NDebugOverlay::Text( vecPos, UTIL_VarArgs( "%d", nValidLinks ), false, 10.0f ); } } if ( !pHighestConnectivity ) return NULL; // now, starting at the new node, find all nearby nodes with a minimum connectivity CASW_Open_Area *pArea = new CASW_Open_Area(); pArea->m_vecOrigin = pHighestConnectivity->GetOrigin(); pArea->m_pNode = pHighestConnectivity; int nMinLinks = nHighestLinks * 0.3f; nMinLinks = MAX( nMinLinks, 4 ); pArea->m_aAreaNodes.AddToTail( pHighestConnectivity ); if ( asw_director_debug.GetBool() ) { Msg( "minLinks = %d\n", nMinLinks ); } pArea->m_nTotalLinks = 0; for ( int i=0 ; i<iNumNodes; i++ ) { CAI_Node *pNode = g_pBigAINet->GetNode( i ); if ( !pNode || pNode->GetType() != NODE_GROUND ) continue; Vector vecPos = pNode->GetOrigin(); float flDist = vecPos.DistTo( pArea->m_vecOrigin ); if ( flDist > 400.0f ) continue; // discard if node is inside an escape area bool bInsideEscapeArea = false; for ( int d=0; d<m_EscapeTriggers.Count(); d++ ) { if ( m_EscapeTriggers[d]->CollisionProp()->IsPointInBounds( vecPos ) ) { bInsideEscapeArea = true; break; } } if ( bInsideEscapeArea ) continue; // count links that drones could follow int nLinks = pNode->NumLinks(); int nValidLinks = 0; for ( int k = 0; k < nLinks; k++ ) { CAI_Link *pLink = pNode->GetLinkByIndex( k ); if ( !pLink ) continue; if ( !( pLink->m_iAcceptedMoveTypes[nSearchHull] & bits_CAP_MOVE_GROUND ) ) continue; nValidLinks++; } if ( nValidLinks >= nMinLinks ) { pArea->m_aAreaNodes.AddToTail( pNode ); pArea->m_nTotalLinks += nValidLinks; } } // highlight and measure bounds Vector vecAreaMins = Vector( FLT_MAX, FLT_MAX, FLT_MAX ); Vector vecAreaMaxs = Vector( -FLT_MAX, -FLT_MAX, -FLT_MAX ); for ( int i = 0; i < pArea->m_aAreaNodes.Count(); i++ ) { vecAreaMins = VectorMin( vecAreaMins, pArea->m_aAreaNodes[i]->GetOrigin() ); vecAreaMaxs = VectorMax( vecAreaMaxs, pArea->m_aAreaNodes[i]->GetOrigin() ); if ( asw_director_debug.GetBool() ) { if ( i == 0 ) { NDebugOverlay::Cross3D( pArea->m_aAreaNodes[i]->GetOrigin(), 20.0f, 255, 255, 64, true, 10.0f ); } else { NDebugOverlay::Cross3D( pArea->m_aAreaNodes[i]->GetOrigin(), 10.0f, 255, 128, 0, true, 10.0f ); } } } Vector vecArea = ( vecAreaMaxs - vecAreaMins ); float flArea = vecArea.x * vecArea.y; if ( asw_director_debug.GetBool() ) { Msg( "area mins = %f %f %f\n", VectorExpand( vecAreaMins ) ); Msg( "area maxs = %f %f %f\n", VectorExpand( vecAreaMaxs ) ); NDebugOverlay::Box( vec3_origin, vecAreaMins, vecAreaMaxs, 255, 128, 128, 10, 10.0f ); Msg( "Total links = %d Area = %f\n", pArea->m_nTotalLinks, flArea ); } return pArea; }