//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "order_helpers.h" #include "tf_team.h" #include "tf_func_resource.h" #include "tf_obj.h" // ------------------------------------------------------------------------ // // CSortBase implementation. // ------------------------------------------------------------------------ // CSortBase::CSortBase() { m_pPlayer = 0; m_pTeam = 0; } CTFTeam* CSortBase::GetTeam() { if ( m_pTeam ) return m_pTeam; else return m_pPlayer->GetTFTeam(); } // ------------------------------------------------------------------------ // // Global functions. // ------------------------------------------------------------------------ // int SortFn_TeamPlayersByDistance( void *pUserData, int a, int b ) { CSortBase *p = (CSortBase*)pUserData; const Vector &vPlayer = p->m_pPlayer->GetAbsOrigin(); const Vector &va = p->m_pPlayer->GetTeam()->GetPlayer( a )->GetAbsOrigin(); const Vector &vb = p->m_pPlayer->GetTeam()->GetPlayer( b )->GetAbsOrigin(); return vPlayer.DistTo( va ) < vPlayer.DistTo( vb ); } // This is a generic function that takes a number of items and builds a sorted // list of the valid items. int BuildSortedActiveList( int *pList, // This is the list where the final data is placed. int nMaxItems, sortFn pSortFn, // Callbacks. isValidFn pIsValidFn, // This can be null, in which case all items are valid. void *pUserData, // Passed into the function pointers. int nItems // Number of items in the list to sort. ) { // First build the list of active items. if( nItems > nMaxItems ) nItems = nMaxItems; int nActive = 0; for( int i=0; i < nItems; i++ ) { if( pIsValidFn ) { if( !pIsValidFn( pUserData, i ) ) continue; } int j; for( j=0; j < nActive; j++ ) { Assert( pList[j] < nItems ); if( pSortFn( pUserData, i, pList[j] ) > 0 ) { break; } } // Slide everything up. if( nActive ) { Q_memmove( &pList[j+1], &pList[j], (nActive-j) * sizeof(int) ); } // Add the new item to the list. pList[j] = i; ++nActive; for (int l = 0; l < nActive ; ++l ) { Assert( pList[l] < nItems ); } } return nActive; } // Finds the closest resource zone without the specified object on it and // gives an order to the player to build the object. bool OrderCreator_ResourceZoneObject( CBaseTFPlayer *pPlayer, int objType, COrder *pOrder ) { // Can we even build a resource box? if ( pPlayer->CanBuild( objType ) != CB_CAN_BUILD ) return false; CTFTeam *pTeam = pPlayer->GetTFTeam(); if( !pTeam ) return false; // Let's have one near each resource zone that we own. CResourceZone *pClosest = 0; float flClosestDist = 100000000; CBaseEntity *pEntity = NULL; while( (pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL ) { CResourceZone *pZone = (CResourceZone*)pEntity; // Ignore empty zones and zones not captured by this team. if ( pZone->IsEmpty() || !pZone->GetActive() ) continue; Vector vZoneCenter = pZone->WorldSpaceCenter(); // Look for a resource pump on this zone. bool bPump = objType == OBJ_RESOURCEPUMP && pPlayer->NumPumpsOnResourceZone( pZone ) == 0; if ( bPump ) { // Make sure it's their preferred tech. float flTestDist = pPlayer->GetAbsOrigin().DistTo( vZoneCenter ); if ( flTestDist < flClosestDist ) { pClosest = pZone; flClosestDist = flTestDist; } } } if ( pClosest ) { // No pump here. Build one! pPlayer->GetTFTeam()->AddOrder( ORDER_BUILD, pClosest, pPlayer, 1e24, 60, pOrder ); return true; } else { return false; } } int SortFn_PlayerObjectsByDistance( void *pUserData, int a, int b ) { CSortBase *pSortBase = (CSortBase*)pUserData; CBaseObject* pObjA = pSortBase->m_pPlayer->GetObject(a); CBaseObject* pObjB = pSortBase->m_pPlayer->GetObject(b); if (!pObjA) return false; if (!pObjB) return true; const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin(); return v.DistTo( pObjA->GetAbsOrigin() ) < v.DistTo( pObjB->GetAbsOrigin() ); } int SortFn_TeamObjectsByDistance( void *pUserData, int a, int b ) { CSortBase *pSortBase = (CSortBase*)pUserData; CBaseObject *pObj1 = pSortBase->m_pPlayer->GetTFTeam()->GetObject( a ); CBaseObject *pObj2 = pSortBase->m_pPlayer->GetTFTeam()->GetObject( b ); const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin(); return v.DistTo( pObj1->GetAbsOrigin() ) < v.DistTo( pObj2->GetAbsOrigin() ); } int SortFn_PlayerEntitiesByDistance( void *pUserData, int a, int b ) { CSortBase *pSortBase = (CSortBase*)pUserData; CBaseEntity *pObj1 = CBaseEntity::Instance( engine->PEntityOfEntIndex( a+1 ) ); CBaseEntity *pObj2 = CBaseEntity::Instance( engine->PEntityOfEntIndex( b+1 ) ); const Vector &v = pSortBase->m_pPlayer->GetAbsOrigin(); return v.DistTo( pObj1->GetAbsOrigin() ) < v.DistTo( pObj2->GetAbsOrigin() ); } int SortFn_DistanceAndConcentration( void *pUserData, int a, int b ) { CSortBase *p = (CSortBase*)pUserData; // Compare distances. Each rope attachment to another ent // subtracts 200 inches, so the order is biased towards covering // groups of objects together. CBaseObject *pObjectA = p->GetTeam()->GetObject( a ); CBaseObject *pObjectB = p->GetTeam()->GetObject( b ); const Vector &vOrigin1 = pObjectA->GetAbsOrigin(); const Vector &vOrigin2 = p->GetTeam()->GetObject( b )->GetAbsOrigin(); float flScore1 = -p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin1 ); float flScore2 = -p->m_pPlayer->GetAbsOrigin().DistTo( vOrigin2 ); flScore1 += pObjectA->RopeCount() * 200; flScore2 += pObjectB->RopeCount() * 200; return flScore1 > flScore2; } bool IsValidFn_NearAndNotCovered( void *pUserData, int a ) { CSortBase *p = (CSortBase*)pUserData; CBaseObject *pObj = p->m_pPlayer->GetTFTeam()->GetObject( a ); // Is the object too far away to be covered? if ( p->m_pPlayer->GetAbsOrigin().DistTo( pObj->GetAbsOrigin() ) > p->m_flMaxDist ) return false; // Don't cover certain entities (like sentry guns, sand bags, etc). switch( p->m_ObjectType ) { case OBJ_SENTRYGUN_PLASMA: { if ( !pObj->WantsCoverFromSentryGun() ) return false; if ( p->m_pPlayer->GetTFTeam()->IsCoveredBySentryGun( pObj->GetAbsOrigin() ) ) return false; } break; case OBJ_SHIELDWALL: { if ( !pObj->WantsCover() ) return false; if ( p->m_pPlayer->GetTFTeam()->GetNumShieldWallsCoveringPosition( pObj->GetAbsOrigin() ) ) return false; } break; case OBJ_RESUPPLY: { if ( p->m_pPlayer->GetTFTeam()->GetNumResuppliesCoveringPosition( pObj->GetAbsOrigin() ) ) return false; } break; case OBJ_RESPAWN_STATION: { if ( p->m_pPlayer->GetTFTeam()->GetNumRespawnStationsCoveringPosition( pObj->GetAbsOrigin() ) ) return false; } break; default: { Assert( !"Unsupported object type" ); } break; } return true; } bool OrderCreator_GenericObject( CPlayerClass *pClass, int objectType, float flMaxDist, COrder *pOrder ) { // Can we build one? if ( pClass->CanBuild( objectType ) != CB_CAN_BUILD ) return false; CBaseTFPlayer *pPlayer = pClass->GetPlayer(); CTFTeam *pTeam = pClass->GetTeam(); // Sort nearby objects. CSortBase info; info.m_pPlayer = pPlayer; info.m_flMaxDist = flMaxDist; info.m_ObjectType = objectType; int sorted[MAX_TEAM_OBJECTS]; int nSorted = BuildSortedActiveList( sorted, // the sorted list of objects MAX_TEAM_OBJECTS, SortFn_DistanceAndConcentration, // sort on distance and entity concentration IsValidFn_NearAndNotCovered, // filter function &info, // user data pTeam->GetNumObjects() // number of objects to check ); if( nSorted ) { // Ok, make an order to cover the closest object with a sentry gun. CBaseEntity *pEnt = pTeam->GetObject( sorted[0] ); pTeam->AddOrder( ORDER_BUILD, pEnt, pPlayer, flMaxDist, 60, pOrder ); return true; } else { return false; } }