//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- boost::python::list GetNavAreasAtBB( const Vector &mins, const Vector &maxs ) { boost::python::list l; CNavArea *area; Extent extent; Vector vAbsMins2, vAbsMaxs2; // TODO: Use ForAllAreasOverlappingExtent? FOR_EACH_VEC( TheNavAreas, it ) { area = TheNavAreas[ it ]; area->GetExtent( &extent ); vAbsMins2 = extent.lo; vAbsMaxs2 = extent.hi; if( vAbsMins2[2] > vAbsMaxs2[2] ) { float z = vAbsMaxs2[2]; vAbsMaxs2[2] = vAbsMins2[2]; vAbsMins2[2] = z; } else if( vAbsMins2[2] == vAbsMaxs2[2] ) { vAbsMaxs2[2] += 1.0f; } // Does it intersects with our bounding box? if( IsBoxIntersectingBox( mins, maxs, vAbsMins2, vAbsMaxs2 ) ) { l.append( area->GetID() ); } }
/** * Escape from the bomb. */ void EscapeFromBombState::OnUpdate( CCSBot *me ) { const Vector *bombPos = me->GetGameState()->GetBombPosition(); // if we don't know where the bomb is, we shouldn't be in this state if (bombPos == NULL) { me->Idle(); return; } // grab our knife to move quickly me->EquipKnife(); // look around me->UpdateLookAround(); if (me->UpdatePathMovement() != CCSBot::PROGRESSING) { // we have no path, or reached the end of one - create a new path far away from the bomb FarAwayFromPositionFunctor func( *bombPos ); CNavArea *goalArea = FindMinimumCostArea( me->GetLastKnownArea(), func ); // if this fails, we'll try again next time me->ComputePath( goalArea->GetCenter(), FASTEST_ROUTE ); } }
/** * Build trivial path when start and goal are in the same nav area */ bool CNavPath::BuildTrivialPath( const Vector *start, const Vector *goal ) { m_segmentCount = 0; CNavArea *startArea = TheNavAreaGrid.GetNearestNavArea( start ); if (startArea == NULL) return false; CNavArea *goalArea = TheNavAreaGrid.GetNearestNavArea( goal ); if (goalArea == NULL) return false; m_segmentCount = 2; m_path[0].area = startArea; m_path[0].pos.x = start->x; m_path[0].pos.y = start->y; m_path[0].pos.z = startArea->GetZ( start ); m_path[0].ladder = NULL; m_path[0].how = NUM_TRAVERSE_TYPES; m_path[1].area = goalArea; m_path[1].pos.x = goal->x; m_path[1].pos.y = goal->y; m_path[1].pos.z = goalArea->GetZ( goal ); m_path[1].ladder = NULL; m_path[1].how = NUM_TRAVERSE_TYPES; return true; }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int GetActiveNavMesh() { CNavArea *pArea = TheNavMesh->GetSelectedArea(); if( !pArea ) return -1; return pArea->GetID(); }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int GetNavAreaAt( const Vector &pos, float beneathlimit ) { CNavArea *pArea = TheNavMesh->GetNavArea( pos, beneathlimit ); if( !pArea ) return -1; return pArea->GetID(); }
/* <4f169d> ../game_shared/bot/nav_file.cpp:811 */ void LoadLocationFile(const char *filename) { char locFilename[256]; Q_strcpy(locFilename, filename); char *dot = Q_strchr(locFilename, '.'); if (dot) { Q_strcpy(dot, ".loc"); int locDataLength; char *locDataFile = (char *)LOAD_FILE_FOR_ME(const_cast<char *>(locFilename), &locDataLength); char *locData = locDataFile; if (locData) { CONSOLE_ECHO("Loading legacy 'location file' '%s'\n", locFilename); // read directory locData = MP_COM_Parse(locData); int dirSize = Q_atoi(MP_COM_GetToken()); if (dirSize) { std::vector<unsigned int> directory; directory.reserve(dirSize); for (int i = 0; i < dirSize; ++i) { locData = MP_COM_Parse(locData); directory.push_back(TheBotPhrases->NameToID(MP_COM_GetToken())); } // read places for each nav area unsigned int areaID, locDirIndex; while (true) { locData = MP_COM_Parse(locData); if (locData == NULL) break; areaID = Q_atoi(MP_COM_GetToken()); locData = MP_COM_Parse(locData); locDirIndex = Q_atoi(MP_COM_GetToken()); CNavArea *area = TheNavAreaGrid.GetNavAreaByID(areaID); unsigned int place = (locDirIndex > 0) ? directory[locDirIndex - 1] : UNDEFINED_PLACE; if (area) area->SetPlace(place); } } FREE_FILE(locDataFile); } } }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vector NavMeshGetPositionNearestNavArea( const Vector &pos, float beneathlimit, bool bCheckBlocked ) { CNavArea *pArea; //pArea = TheNavMesh->GetNearestNavArea(pos, false, 64.0f); pArea = TheNavMesh->GetNavArea(pos, beneathlimit, bCheckBlocked); if( pArea ) { Vector vAreaPos(pos); vAreaPos.z = pArea->GetZ(pos); return vAreaPos; } return vec3_origin; }
//-------------------------------------------------------------------------------------------------------- void CFuncNavBlocker::UpdateBlocked() { NavAreaCollector collector( true ); Extent extent; extent.Init( this ); TheNavMesh->ForAllAreasOverlappingExtent( collector, extent ); for ( int i=0; i<collector.m_area.Count(); ++i ) { CNavArea *area = collector.m_area[i]; area->UpdateBlocked( true ); } }
// If we are not on the navigation mesh (m_currentArea == nullptr), // move towards last known area. // Return false if off mesh. bool CCSBot::StayOnNavMesh() { if (m_currentArea) return true; // move back onto the area map // if we have no lastKnownArea, we probably started off // of the nav mesh - find the closest nav area and use it CNavArea *goalArea; if (!m_currentArea && !m_lastKnownArea) { goalArea = TheNavAreaGrid.GetNearestNavArea(&pev->origin); PrintIfWatched("Started off the nav mesh - moving to closest nav area...\n"); } else { goalArea = m_lastKnownArea; PrintIfWatched("Getting out of NULL area...\n"); } if (goalArea) { Vector pos; goalArea->GetClosestPointOnArea(&pev->origin, &pos); // move point into area Vector to = pos - pev->origin; to.NormalizeInPlace(); // how far to "step into" an area - must be less than min area size const float stepInDist = 5.0f; pos = pos + (stepInDist * to); MoveTowardsPosition(&pos); } // if we're stuck, try to get un-stuck // do stuck movements last, so they override normal movement if (m_isStuck) { Wiggle(); } return false; }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vector RandomNavAreaPositionWithin( const Vector &mins, const Vector &maxs, float minimumarea, int maxtries ) { if( maxtries < 0 ) maxtries = 1; Vector random; CNavArea *pArea = NULL; Extent extent; for( int i = 0; i < maxtries; i++ ) { random.Init( mins.x + ((float)rand() / RAND_MAX) * (maxs.x - mins.x), mins.y + ((float)rand() / RAND_MAX) * (maxs.y - mins.y), maxs.z ); pArea = TheNavMesh->GetNearestNavArea( random, false, 10000.0f, false, false ); if( pArea ) { pArea->GetExtent( &extent ); if( extent.Area() >= minimumarea ) break; } // Reset pArea = NULL; } if( !pArea ) { if( g_pynavmesh_debug.GetBool() ) DevMsg("RandomNavAreaPosition: No area found within Mins: %f %f %f, Maxs: %f %f %f, Random: %f %f %f\n", mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, random.x, random.y, random.z); return vec3_origin; } Vector vRandomPoint = pArea->GetRandomPoint(); vRandomPoint.z += 32.0f; if( g_pynavmesh_debug.GetBool() ) DevMsg("RandomNavAreaPosition: Found position %f %f %f\n", vRandomPoint.x, vRandomPoint.y, vRandomPoint.z); return vRandomPoint; }
//----------------------------------------------------------------------------------------------------- int CFuncNavBlocker::DrawDebugTextOverlays( void ) { int offset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { CFmtStr str; // FIRST_GAME_TEAM skips TEAM_SPECTATOR and TEAM_UNASSIGNED, so we can print // useful team names in a non-game-specific fashion. for ( int i=FIRST_GAME_TEAM; i<FIRST_GAME_TEAM + MAX_NAV_TEAMS; ++i ) { if ( IsBlockingNav( i ) ) { CTeam *team = GetGlobalTeam( i ); if ( team ) { EntityText( offset++, str.sprintf( "blocking team %s", team->GetName() ), 0 ); } else { EntityText( offset++, str.sprintf( "blocking team %d", i ), 0 ); } } } NavAreaCollector collector( true ); Extent extent; extent.Init( this ); TheNavMesh->ForAllAreasOverlappingExtent( collector, extent ); for ( int i=0; i<collector.m_area.Count(); ++i ) { CNavArea *area = collector.m_area[i]; Extent areaExtent; area->GetExtent( &areaExtent ); debugoverlay->AddBoxOverlay( vec3_origin, areaExtent.lo, areaExtent.hi, vec3_angle, 0, 255, 0, 10, NDEBUG_PERSIST_TILL_NEXT_SERVER ); } } return offset; }
void CEventLog::FormatPlayer( CBaseEntity *ent, char *str, int len ) const { if ( !str || len <= 0 ) { return; } CBasePlayer *player = ToBasePlayer( ent ); const char *playerName = "Unknown"; int userID = 0; const char *networkIDString = ""; const char *teamName = ""; int areaID = 0; if ( player ) { playerName = player->GetPlayerName(); userID = player->GetUserID(); networkIDString = player->GetNetworkIDString(); CTeam *team = player->GetTeam(); if ( team ) { teamName = team->GetName(); } } #ifdef USE_NAV_MESH if ( ent && ent->MyCombatCharacterPointer() ) { CNavArea *area = ent->MyCombatCharacterPointer()->GetLastKnownArea(); if ( area ) { areaID = area->GetID(); } } #endif // USE_NAV_MESH V_snprintf( str, len, "\"%s<%i><%s><%s><Area %d>\"", playerName, userID, networkIDString, teamName, areaID ); }
// tell the other areas we are going away FOR_EACH_VEC( TheNavAreas, it ) { CNavArea *area = TheNavAreas[ it ]; area->OnDestroyNotify( this ); }
/* <4f19c7> ../game_shared/bot/nav_file.cpp:947 */ NavErrorType LoadNavigationMap() { // since the navigation map is destroyed on map change, // if it exists it has already been loaded for this map if (!TheNavAreaList.empty()) return NAV_OK; // nav filename is derived from map filename char filename[256]; Q_sprintf(filename, "maps\\%s.nav", STRING(gpGlobals->mapname)); // free previous navigation map data DestroyNavigationMap(); placeDirectory.Reset(); IMPL_CLASS(CNavArea, m_nextID) = 1; SteamFile navFile(filename); if (!navFile.IsValid()) return NAV_CANT_ACCESS_FILE; // check magic number bool result; unsigned int magic; result = navFile.Read(&magic, sizeof(unsigned int)); if (!result || magic != NAV_MAGIC_NUMBER) { CONSOLE_ECHO("ERROR: Invalid navigation file '%s'.\n", filename); return NAV_INVALID_FILE; } // read file version number unsigned int version; result = navFile.Read(&version, sizeof(unsigned int)); if (!result || version > 5) { CONSOLE_ECHO("ERROR: Unknown navigation file version.\n"); return NAV_BAD_FILE_VERSION; } if (version >= 4) { // get size of source bsp file and verify that the bsp hasn't changed unsigned int saveBspSize; navFile.Read(&saveBspSize, sizeof(unsigned int)); // verify size char *bspFilename = GetBspFilename(filename); if (bspFilename == NULL) return NAV_INVALID_FILE; unsigned int bspSize = (unsigned int)GET_FILE_SIZE(bspFilename); if (bspSize != saveBspSize) { // this nav file is out of date for this bsp file char *msg = "*** WARNING ***\nThe AI navigation data is from a different version of this map.\nThe CPU players will likely not perform well.\n"; HintMessageToAllPlayers(msg); CONSOLE_ECHO("\n-----------------\n"); CONSOLE_ECHO(msg); CONSOLE_ECHO("-----------------\n\n"); } } // load Place directory if (version >= 5) { placeDirectory.Load(&navFile); } // get number of areas unsigned int count; result = navFile.Read(&count, sizeof(unsigned int)); Extent extent; extent.lo.x = 9999999999.9f; extent.lo.y = 9999999999.9f; extent.hi.x = -9999999999.9f; extent.hi.y = -9999999999.9f; // load the areas and compute total extent for (unsigned int i = 0; i < count; ++i) { CNavArea *area = new CNavArea; area->Load(&navFile, version); TheNavAreaList.push_back(area); const Extent *areaExtent = area->GetExtent(); // check validity of nav area if (areaExtent->lo.x >= areaExtent->hi.x || areaExtent->lo.y >= areaExtent->hi.y) CONSOLE_ECHO("WARNING: Degenerate Navigation Area #%d at ( %g, %g, %g )\n", area->GetID(), area->m_center.x, area->m_center.y, area->m_center.z); if (areaExtent->lo.x < extent.lo.x) extent.lo.x = areaExtent->lo.x; if (areaExtent->lo.y < extent.lo.y) extent.lo.y = areaExtent->lo.y; if (areaExtent->hi.x > extent.hi.x) extent.hi.x = areaExtent->hi.x; if (areaExtent->hi.y > extent.hi.y) extent.hi.y = areaExtent->hi.y; } // add the areas to the grid TheNavAreaGrid.Initialize(extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y); NavAreaList::iterator iter; for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter) TheNavAreaGrid.AddNavArea(*iter); // allow areas to connect to each other, etc for (iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter) { CNavArea *area = *iter; area->PostLoad(); } // load legacy location file (Places) if (version < 5) { LoadLocationFile(filename); } // Set up all the ladders BuildLadders(); return NAV_OK; }
/* <4f3e47> ../game_shared/bot/nav_file.cpp:702 */ bool SaveNavigationMap(const char *filename) { if (filename == NULL) return false; // Store the NAV file COM_FixSlashes(const_cast<char *>(filename)); #ifdef WIN32 int fd = _open(filename, _O_BINARY | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE); #else int fd = creat(filename, S_IRUSR | S_IWUSR | S_IRGRP); #endif // WIN32 if (fd < 0) return false; // store "magic number" to help identify this kind of file unsigned int magic = NAV_MAGIC_NUMBER; Q_write(fd, &magic, sizeof(unsigned int)); // store version number of file // 1 = hiding spots as plain vector array // 2 = hiding spots as HidingSpot objects // 3 = Encounter spots use HidingSpot ID's instead of storing vector again // 4 = Includes size of source bsp file to verify nav data correlation // ---- Beta Release at V4 ----- // 5 = Added Place info unsigned int version = 5; Q_write(fd, &version, sizeof(unsigned int)); // get size of source bsp file and store it in the nav file // so we can test if the bsp changed since the nav file was made char *bspFilename = GetBspFilename(filename); if (bspFilename == NULL) return false; unsigned int bspSize = (unsigned int)GET_FILE_SIZE(bspFilename); CONSOLE_ECHO("Size of bsp file '%s' is %u bytes.\n", bspFilename, bspSize); Q_write(fd, &bspSize, sizeof(unsigned int)); // Build a directory of the Places in this map placeDirectory.Reset(); NavAreaList::iterator it; for (it = TheNavAreaList.begin(); it != TheNavAreaList.end(); ++it) { CNavArea *area = *it; Place place = area->GetPlace(); if (place) { placeDirectory.AddPlace(place); } } placeDirectory.Save(fd); // Store navigation areas // store number of areas unsigned int count = TheNavAreaList.size(); Q_write(fd, &count, sizeof(unsigned int)); // store each area for (it = TheNavAreaList.begin(); it != TheNavAreaList.end(); ++it) { CNavArea *area = *it; area->Save(fd, version); } Q_close(fd); #ifdef _WIN32 // output a simple Wavefront file to visualize the generated areas in 3DSMax FILE *fp = fopen("c:\\tmp\\nav.obj", "w"); if (fp) { for (NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter) { (*iter)->Save(fp); } fclose(fp); } #endif // _WIN32 return true; }
virtual void FrameUpdatePostEntityThink() override { static long frame = 0; ++frame; if (frame % 22 == 0) { Enhancement_NotifyDefendersAboutBombs(); } UpdateTrackers(); #if 0 /* remove me */ if (frame % 7 == 0) { CCaptureFlag *flag = GetClosestFlagToHatch(); if (flag != nullptr) { CNavArea *area = TheNavMesh->GetNavArea(flag->GetAbsOrigin()); if (area != nullptr) { float flag_dist = reinterpret_cast<CTFNavArea *>(area)->GetIncursionDistance(TF_TEAM_BLUE); constexpr float D_LOW = 0.0f; constexpr float D_HIGH = 300.0f; for (auto area : (CUtlVector<CTFNavArea *>&)TheNavAreas) { float dist = area->GetIncursionDistance(TF_TEAM_BLUE); if (dist < flag_dist - D_LOW) continue; if (dist > flag_dist + D_HIGH) continue; area->DrawFilled(0xff, 0xff, 0xff, 0x00, gpGlobals->interval_per_tick * 7, true, 0.0f); area->DrawFilled(0xff, 0xff, 0xff, 0x40, gpGlobals->interval_per_tick * 7, true, 0.0f); } } } } #endif #if 0 if (frame % 22 == 0) { float inc_min = FLT_MAX; float inc_max = FLT_MIN; for (auto area : (CUtlVector<CTFNavArea *>&)TheNavAreas) { float inc = area->GetIncursionDistance(TF_TEAM_BLUE); if (inc < 0.0f) continue; if (inc < inc_min) inc_min = inc; if (inc > inc_max) inc_max = inc; } for (auto area : (CUtlVector<CTFNavArea *>&)TheNavAreas) { float val = area->GetIncursionDistance(TF_TEAM_BLUE); float rat = RemapValClamped(val, 0.0f, inc_max, 0.0f, 1.0f); int r; int g; int b; if (rat < 0.25f) { r = 0xff; g = RemapValClamped(rat, 0.00f, 0.25f, 0.0f, 255.0f); b = 0x00; } else if (rat < 0.50f) { r = RemapValClamped(rat, 0.25f, 0.50f, 255.0f, 0.0f); g = 0xff; b = 0x00; } else if (rat < 0.75f) { r = 0x00; g = 0xff; b = RemapValClamped(rat, 0.50f, 0.75f, 0.0f, 255.0f); } else { r = 0x00; g = RemapValClamped(rat, 0.75f, 1.00f, 255.0f, 0.0f); b = 0xff; } area->DrawFilled(r, g, b, 0x00, 0.33f, true, 0.0f); area->DrawFilled(r, g, b, 0x80, 0.33f, true, 0.0f); NDebugOverlay::EntityTextAtPosition(area->GetCenter(), 0, CFmtStrN<64>("%.1f%%", rat * 100.0f), 0.33f, 0xff, 0xff, 0xff, 0xff); // NDebugOverlay::EntityTextAtPosition(area->GetCenter(), 1, // CFmtStrN<64>("%.0f HU", val), // 0.33f, 0xff, 0xff, 0xff, 0xff); } } #endif DrawOverlay_Ownership(frame); }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCSPlayerResource::UpdatePlayerData( void ) { int i; m_iPlayerC4 = 0; m_iPlayerVIP = 0; for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i ); if ( pPlayer && pPlayer->IsConnected() ) { if ( pPlayer->IsVIP() ) { // we should only have one VIP Assert( m_iPlayerVIP == 0 ); m_iPlayerVIP = i; } if ( pPlayer->HasC4() ) { // we should only have one bomb m_iPlayerC4 = i; } } } CBaseEntity *c4 = NULL; if ( m_iPlayerC4 == 0 ) { // no player has C4, update C4 position if ( g_C4s.Count() > 0 ) { c4 = g_C4s[0]; m_vecC4 = c4->GetAbsOrigin(); } else { m_vecC4.Init(); } } //int numHostages = g_Hostages.Count(); for ( i = 0; i < MAX_HOSTAGES; i++ ) { /*if ( i >= numHostages ) { // engine->Con_NPrintf( i, "Dead" ); m_bHostageAlive.Set( i, false ); m_isHostageFollowingSomeone.Set( i, false ); continue; } // CHostage* pHostage = g_Hostages[i]; //m_bHostageAlive.Set( i, pHostage->IsRescuable() ); /*if ( pHostage->IsValid() ) { m_iHostageX.Set( i, (int) pHostage->GetAbsOrigin().x ); m_iHostageY.Set( i, (int) pHostage->GetAbsOrigin().y ); m_iHostageZ.Set( i, (int) pHostage->GetAbsOrigin().z ); m_iHostageEntityIDs.Set( i, pHostage->entindex() ); //m_isHostageFollowingSomeone.Set( i, pHostage->IsFollowingSomeone() ); // engine->Con_NPrintf( i, "ID:%d Pos:(%.0f,%.0f,%.0f)", pHostage->entindex(), pHostage->GetAbsOrigin().x, pHostage->GetAbsOrigin().y, pHostage->GetAbsOrigin().z ); } else { // engine->Con_NPrintf( i, "Invalid" ); }*/ } if( !m_foundGoalPositions ) { // We only need to update these once a map, but we need the client to know about them. CBaseEntity* ent = NULL; while ( ( ent = gEntList.FindEntityByClassname( ent, "func_bomb_target" ) ) != NULL ) { const Vector &pos = ent->WorldSpaceCenter(); CNavArea *area = TheNavMesh->GetNearestNavArea( pos, true ); const char *placeName = (area) ? TheNavMesh->PlaceToName( area->GetPlace() ) : NULL; if ( placeName == NULL ) { // The bomb site has no area or place name, so just choose A then B if ( m_bombsiteCenterA.Get().IsZero() ) { m_bombsiteCenterA = pos; } else { m_bombsiteCenterB = pos; } } else { // The bomb site has a place name, so choose accordingly if( FStrEq( placeName, "BombsiteA" ) ) { m_bombsiteCenterA = pos; } else { m_bombsiteCenterB = pos; } } m_foundGoalPositions = true; } int hostageRescue = 0; while ( (( ent = gEntList.FindEntityByClassname( ent, "func_hostage_rescue" ) ) != NULL) && (hostageRescue < MAX_HOSTAGE_RESCUES) ) { const Vector &pos = ent->WorldSpaceCenter(); m_hostageRescueX.Set( hostageRescue, (int) pos.x ); m_hostageRescueY.Set( hostageRescue, (int) pos.y ); m_hostageRescueZ.Set( hostageRescue, (int) pos.z ); hostageRescue++; m_foundGoalPositions = true; } } bool bombSpotted = false; if ( c4 ) { Spotter spotter( c4, m_vecC4, TEAM_CT ); ForEachPlayer( spotter ); if ( spotter.Spotted() ) { bombSpotted = true; } } for ( int i=0; i < MAX_PLAYERS+1; i++ ) { CCSPlayer *target = ToCSPlayer( UTIL_PlayerByIndex( i ) ); if ( !target || !target->IsAlive() ) { m_bPlayerSpotted.Set( i, 0 ); continue; } Spotter spotter( target, target->EyePosition(), (target->GetTeamNumber()==TEAM_CT) ? TEAM_TERRORIST : TEAM_CT ); ForEachPlayer( spotter ); if ( spotter.Spotted() ) { if ( target->HasC4() ) { bombSpotted = true; } m_bPlayerSpotted.Set( i, 1 ); } else { m_bPlayerSpotted.Set( i, 0 ); } } if ( bombSpotted ) { m_bBombSpotted = true; } else { m_bBombSpotted = false; } BaseClass::UpdatePlayerData(); }
/* <36b780> ../cstrike/dlls/bot/cs_bot_manager.cpp:1109 */ void CCSBotManager::ValidateMapData(void) { if (IMPLEMENT_ARRAY(m_isMapDataLoaded) || !UTIL_IsGame("czero")) { return; } IMPLEMENT_ARRAY(m_isMapDataLoaded) = true; // TODO: Reverse me if (LoadNavigationMap()) { CONSOLE_ECHO("Failed to load navigation map.\n"); return; } CONSOLE_ECHO("Navigation map loaded.\n"); m_zoneCount = 0; m_gameScenario = SCENARIO_DEATHMATCH; // Search all entities in the map and set the game type and // store all zones (bomb target, etc). CBaseEntity *entity = NULL; int i; for (i = 1; i < gpGlobals->maxEntities; i++) { entity = CBaseEntity::Instance(INDEXENT(i)); if (entity == NULL) continue; bool found = false; bool isLegacy = false; if (FClassnameIs(entity->pev, "func_bomb_target")) { found = true; isLegacy = false; m_gameScenario = SCENARIO_DEFUSE_BOMB; } else if (FClassnameIs(entity->pev, "info_bomb_target")) { found = true; isLegacy = true; m_gameScenario = SCENARIO_DEFUSE_BOMB; } else if (FClassnameIs(entity->pev, "func_hostage_rescue")) { found = true; isLegacy = false; m_gameScenario = SCENARIO_RESCUE_HOSTAGES; } else if (FClassnameIs(entity->pev, "info_hostage_rescue")) { found = true; isLegacy = true; m_gameScenario = SCENARIO_RESCUE_HOSTAGES; } else if (FClassnameIs(entity->pev, "hostage_entity")) { // some very old maps (ie: cs_assault) use info_player_start // as rescue zones, so set the scenario if there are hostages // in the map m_gameScenario = SCENARIO_RESCUE_HOSTAGES; } else if (FClassnameIs(entity->pev, "func_vip_safetyzone")) { found = true; isLegacy = false; m_gameScenario = SCENARIO_ESCORT_VIP; } if (found) { if (m_zoneCount < MAX_ZONES) { if (isLegacy) m_zone[ m_zoneCount ].m_center = entity->pev->origin; else m_zone[ m_zoneCount ].m_center = (entity->pev->absmax + entity->pev->absmin) / 2.0f; m_zone[ m_zoneCount ].m_isLegacy = isLegacy; m_zone[ m_zoneCount ].m_index = m_zoneCount; m_zone[ m_zoneCount ].m_entity = entity; ++m_zoneCount; } else CONSOLE_ECHO("Warning: Too many zones, some will be ignored.\n"); } } // If there are no zones and the scenario is hostage rescue, // use the info_player_start entities as rescue zones. if (m_zoneCount == 0 && m_gameScenario == SCENARIO_RESCUE_HOSTAGES) { entity = NULL; while ((entity = UTIL_FindEntityByClassname(entity, "info_player_start")) != NULL) { if (FNullEnt(entity->edict())) break; if (m_zoneCount < MAX_ZONES) { m_zone[ m_zoneCount ].m_center = entity->pev->origin; m_zone[ m_zoneCount ].m_isLegacy = true; m_zone[ m_zoneCount ].m_index = m_zoneCount; m_zone[ m_zoneCount ].m_entity = entity; ++m_zoneCount; } else CONSOLE_ECHO("Warning: Too many zones, some will be ignored.\n"); } } // Collect nav areas that overlap each zone for (i = 0; i < m_zoneCount; i++) { Zone *zone = &m_zone[i]; if (zone->m_isLegacy) { const float legacyRange = 256.0f; zone->m_extent.lo.x = zone->m_center.x - legacyRange; zone->m_extent.lo.y = zone->m_center.y - legacyRange; zone->m_extent.lo.z = zone->m_center.z - legacyRange; zone->m_extent.hi.x = zone->m_center.x + legacyRange; zone->m_extent.hi.y = zone->m_center.y + legacyRange; zone->m_extent.hi.z = zone->m_center.z + legacyRange; } else { zone->m_extent.lo = zone->m_entity->pev->absmin; zone->m_extent.hi = zone->m_entity->pev->absmax; } // ensure Z overlap const float zFudge = 50.0f; zone->m_areaCount = 0; zone->m_extent.lo.z -= zFudge; zone->m_extent.hi.z += zFudge; // build a list of nav areas that overlap this zone for (NavAreaList::iterator iter = TheNavAreaList.begin(); iter != TheNavAreaList.end(); ++iter) { CNavArea *area = (*iter); const Extent *areaExtent = area->GetExtent(); if (areaExtent->hi.x >= zone->m_extent.lo.x && areaExtent->lo.x <= zone->m_extent.hi.x && areaExtent->hi.y >= zone->m_extent.lo.y && areaExtent->lo.y <= zone->m_extent.hi.y && areaExtent->hi.z >= zone->m_extent.lo.z && areaExtent->lo.z <= zone->m_extent.hi.z) { // area overlaps zone zone->m_area[ zone->m_areaCount++ ] = area; if (zone->m_areaCount == MAX_ZONE_NAV_AREAS) { break; } } } } }
//-------------------------------------------------------------------------------------------------------- static bool ReduceToComponentAreas( CNavArea *area, bool addToSelectedSet ) { if ( !area ) return false; bool splitAlongX; float splitEdge; const float minSplitSize = 2.0f; // ensure the first split is larger than this float sizeX = area->GetSizeX(); float sizeY = area->GetSizeY(); CNavArea *first = NULL; CNavArea *second = NULL; CNavArea *third = NULL; CNavArea *fourth = NULL; bool didSplit = false; if ( sizeX > GenerationStepSize ) { splitEdge = RoundToUnits( area->GetCorner( NORTH_WEST ).x, GenerationStepSize ); if ( splitEdge < area->GetCorner( NORTH_WEST ).x + minSplitSize ) splitEdge += GenerationStepSize; splitAlongX = false; didSplit = area->SplitEdit( splitAlongX, splitEdge, &first, &second ); } if ( sizeY > GenerationStepSize ) { splitEdge = RoundToUnits( area->GetCorner( NORTH_WEST ).y, GenerationStepSize ); if ( splitEdge < area->GetCorner( NORTH_WEST ).y + minSplitSize ) splitEdge += GenerationStepSize; splitAlongX = true; if ( didSplit ) { didSplit = first->SplitEdit( splitAlongX, splitEdge, &third, &fourth ); didSplit = second->SplitEdit( splitAlongX, splitEdge, &first, &second ); } else { didSplit = area->SplitEdit( splitAlongX, splitEdge, &first, &second ); } } if ( !didSplit ) return false; if ( addToSelectedSet ) { TheNavMesh->AddToSelectedSet( first ); TheNavMesh->AddToSelectedSet( second ); TheNavMesh->AddToSelectedSet( third ); TheNavMesh->AddToSelectedSet( fourth ); } ReduceToComponentAreas( first, addToSelectedSet ); ReduceToComponentAreas( second, addToSelectedSet ); ReduceToComponentAreas( third, addToSelectedSet ); ReduceToComponentAreas( fourth, addToSelectedSet ); return true; }
/** * Follow our leader * @todo Clean up this nasty mess */ void FollowState::OnUpdate( CDABot *me ) { // if we lost our leader, give up if (m_leader == NULL || !m_leader->IsAlive()) { me->Idle(); return; } // look around me->UpdateLookAround(); // if we are moving, we are not idle if (me->IsNotMoving() == false) m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) ); // compute the leader's speed Vector leaderVel = m_leader->GetAbsVelocity(); float leaderSpeed = Vector2D( leaderVel.x, leaderVel.y ).Length(); // determine our leader's movement state ComputeLeaderMotionState( leaderSpeed ); // track whether we can see the leader bool isLeaderVisible; Vector leaderOrigin = GetCentroid( m_leader ); if (me->IsVisible( leaderOrigin )) { m_lastSawLeaderTime = gpGlobals->curtime; isLeaderVisible = true; } else { isLeaderVisible = false; } // determine whether we should sneak or not const float farAwayRange = 750.0f; Vector myOrigin = GetCentroid( me ); if ((leaderOrigin - myOrigin).IsLengthGreaterThan( farAwayRange )) { // far away from leader - run to catch up m_isSneaking = false; } else if (isLeaderVisible) { // if we see leader walking and we are nearby, walk if (m_leaderMotionState == WALKING) m_isSneaking = true; // if we are sneaking and our leader starts running, stop sneaking if (m_isSneaking && m_leaderMotionState == RUNNING) m_isSneaking = false; } // if we haven't seen the leader for a long time, run const float longTime = 20.0f; if (gpGlobals->curtime - m_lastSawLeaderTime > longTime) m_isSneaking = false; if (m_isSneaking) me->Walk(); else me->Run(); bool repath = false; // if the leader has stopped, hide nearby const float nearLeaderRange = 250.0f; if (!me->HasPath() && m_leaderMotionState == STOPPED && m_leaderMotionStateTime.GetElapsedTime() > m_waitTime) { // throttle how often this check occurs m_waitTime += RandomFloat( 1.0f, 3.0f ); // the leader has stopped - if we are close to him, take up a hiding spot if ((leaderOrigin - myOrigin).IsLengthLessThan( nearLeaderRange )) { const float hideRange = 250.0f; if (me->TryToHide( NULL, -1.0f, hideRange, false, USE_NEAREST )) { me->ResetStuckMonitor(); return; } } } // if we have been idle for awhile, move if (m_idleTimer.IsElapsed()) { repath = true; // always walk when we move such a short distance m_isSneaking = true; } // if our leader has moved, repath (don't repath if leading is stopping) if (leaderSpeed > 100.0f && m_leaderMotionState != STOPPED) { repath = true; } // move along our path if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CDABot::PROGRESSING) { me->DestroyPath(); } // recompute our path if necessary if (repath && m_repathInterval.IsElapsed() && !me->IsOnLadder()) { // recompute our path to keep us near our leader m_lastLeaderPos = leaderOrigin; me->ResetStuckMonitor(); const float runSpeed = 200.0f; const float collectRange = (leaderSpeed > runSpeed) ? 600.0f : 400.0f; // 400, 200 FollowTargetCollector collector( m_leader ); SearchSurroundingAreas( TheNavMesh->GetNearestNavArea( m_lastLeaderPos ), m_lastLeaderPos, collector, collectRange ); if (cv_bot_debug.GetBool()) { for( int i=0; i<collector.m_targetAreaCount; ++i ) collector.m_targetArea[i]->Draw( /*255, 0, 0, 2*/ ); } // move to one of the collected areas if (collector.m_targetAreaCount) { CNavArea *target = NULL; Vector targetPos; // if we are idle, pick a random area if (m_idleTimer.IsElapsed()) { target = collector.m_targetArea[ RandomInt( 0, collector.m_targetAreaCount-1 ) ]; targetPos = target->GetCenter(); me->PrintIfWatched( "%4.1f: Bored. Repathing to a new nearby area\n", gpGlobals->curtime ); } else { me->PrintIfWatched( "%4.1f: Repathing to stay with leader.\n", gpGlobals->curtime ); // find closest area to where we are CNavArea *area; float closeRangeSq = 9999999999.9f; Vector close; for( int a=0; a<collector.m_targetAreaCount; ++a ) { area = collector.m_targetArea[a]; area->GetClosestPointOnArea( myOrigin, &close ); float rangeSq = (myOrigin - close).LengthSqr(); if (rangeSq < closeRangeSq) { target = area; targetPos = close; closeRangeSq = rangeSq; } } } if (target == NULL || me->ComputePath( target->GetCenter(), FASTEST_ROUTE ) == NULL) me->PrintIfWatched( "Pathfind to leader failed.\n" ); // throttle how often we repath m_repathInterval.Start( 0.5f ); m_idleTimer.Reset(); } } }
// Compute shortest path to goal position via A* algorithm // If 'goalArea' is NULL, path will get as close as it can. bool CCSBot::ComputePath(CNavArea *goalArea, const Vector *goal, RouteType route) { // Throttle re-pathing if (!m_repathTimer.IsElapsed()) return false; // randomize to distribute CPU load m_repathTimer.Start(RANDOM_FLOAT(0.4f, 0.6f)); DestroyPath(); CNavArea *startArea = m_lastKnownArea; if (!startArea) return false; // note final specific position Vector pathEndPosition; if (!goal && !goalArea) return false; if (!goal) pathEndPosition = *goalArea->GetCenter(); else pathEndPosition = *goal; // make sure path end position is on the ground if (goalArea) pathEndPosition.z = goalArea->GetZ(&pathEndPosition); else GetGroundHeight(&pathEndPosition, &pathEndPosition.z); // if we are already in the goal area, build trivial path if (startArea == goalArea) { BuildTrivialPath(&pathEndPosition); return true; } // Compute shortest path to goal CNavArea *closestArea = nullptr; PathCost pathCost(this, route); bool pathToGoalExists = NavAreaBuildPath(startArea, goalArea, goal, pathCost, &closestArea); CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea; // Build path by following parent links // get count int count = 0; CNavArea *area; for (area = effectiveGoalArea; area; area = area->GetParent()) { count++; } // save room for endpoint if (count > MAX_PATH_LENGTH - 1) count = MAX_PATH_LENGTH - 1; if (count == 0) return false; if (count == 1) { BuildTrivialPath(&pathEndPosition); return true; } // build path m_pathLength = count; for (area = effectiveGoalArea; count && area; area = area->GetParent()) { count--; m_path[count].area = area; m_path[count].how = area->GetParentHow(); } // compute path positions if (ComputePathPositions() == false) { PrintIfWatched("Error building path\n"); DestroyPath(); return false; } if (!goal) { switch (m_path[m_pathLength - 1].how) { case GO_NORTH: case GO_SOUTH: pathEndPosition.x = m_path[m_pathLength - 1].pos.x; pathEndPosition.y = effectiveGoalArea->GetCenter()->y; break; case GO_EAST: case GO_WEST: pathEndPosition.x = effectiveGoalArea->GetCenter()->x; pathEndPosition.y = m_path[m_pathLength - 1].pos.y; break; } GetGroundHeight(&pathEndPosition, &pathEndPosition.z); } // append path end position m_path[m_pathLength].area = effectiveGoalArea; m_path[m_pathLength].pos = pathEndPosition; m_path[m_pathLength].ladder = nullptr; m_path[m_pathLength].how = NUM_TRAVERSE_TYPES; m_pathLength++; // do movement setup m_pathIndex = 1; m_areaEnteredTimestamp = gpGlobals->time; m_spotEncounter = nullptr; m_goalPosition = m_path[1].pos; if (m_path[1].ladder) SetupLadderMovement(); else m_pathLadder = nullptr; return true; }
CBasePlayer *CCSBot::FindMostDangerousThreat() { // maximum number of simulataneously attendable threats enum { MAX_THREATS = 16 }; struct CloseInfo { CBasePlayer *enemy; float range; } threat[ MAX_THREATS ]; int threatCount = 0; m_bomber = NULL; m_closestVisibleFriend = NULL; float closeFriendRange = 99999999999.9f; m_closestVisibleHumanFriend = NULL; float closeHumanFriendRange = 99999999999.9f; int i; { for (i = 1; i <= gpGlobals->maxClients; ++i) { CBasePlayer *player = UTIL_PlayerByIndex(i); if (player == NULL) continue; if (FNullEnt(player->pev)) continue; // is it a player? if (!player->IsPlayer()) continue; // ignore self if (player->entindex() == entindex()) continue; // is it alive? if (!player->IsAlive()) continue; // is it an enemy? if (player->m_iTeam == m_iTeam) { TraceResult result; UTIL_TraceLine(GetEyePosition(), player->pev->origin, ignore_monsters, ignore_glass, edict(), &result); if (result.flFraction == 1.0f) { // update watch timestamp int idx = player->entindex() - 1; m_watchInfo[idx].timestamp = gpGlobals->time; m_watchInfo[idx].isEnemy = false; // keep track of our closest friend Vector to = pev->origin - player->pev->origin; float rangeSq = to.LengthSquared(); if (rangeSq < closeFriendRange) { m_closestVisibleFriend = player; closeFriendRange = rangeSq; } // keep track of our closest human friend if (!player->IsBot() && rangeSq < closeHumanFriendRange) { m_closestVisibleHumanFriend = player; closeHumanFriendRange = rangeSq; } } continue; } // check if this enemy is fully if (!IsVisible(player, CHECK_FOV)) continue; // update watch timestamp int idx = player->entindex() - 1; m_watchInfo[idx].timestamp = gpGlobals->time; m_watchInfo[idx].isEnemy = true; // note if we see the bomber if (player->IsBombGuy()) { m_bomber = player; } // keep track of all visible threats Vector d = pev->origin - player->pev->origin; float distSq = d.LengthSquared(); // maintain set of visible threats, sorted by increasing distance if (threatCount == 0) { threat[0].enemy = player; threat[0].range = distSq; threatCount = 1; } else { // find insertion point int j; for (j = 0; j < threatCount; ++j) { if (distSq < threat[j].range) break; } // shift lower half down a notch for (int k = threatCount - 1; k >= j; --k) threat[k + 1] = threat[k]; // insert threat into sorted list threat[j].enemy = player; threat[j].range = distSq; if (threatCount < MAX_THREATS) ++threatCount; } } } { // track the maximum enemy and friend counts we've seen recently int prevEnemies = m_nearbyEnemyCount; int prevFriends = m_nearbyFriendCount; m_nearbyEnemyCount = 0; m_nearbyFriendCount = 0; for (i = 0; i < MAX_CLIENTS; ++i) { if (m_watchInfo[i].timestamp <= 0.0f) continue; const float recentTime = 3.0f; if (gpGlobals->time - m_watchInfo[i].timestamp < recentTime) { if (m_watchInfo[i].isEnemy) ++m_nearbyEnemyCount; else ++m_nearbyFriendCount; } } // note when we saw this batch of enemies if (prevEnemies == 0 && m_nearbyEnemyCount > 0) { m_firstSawEnemyTimestamp = gpGlobals->time; } if (prevEnemies != m_nearbyEnemyCount || prevFriends != m_nearbyFriendCount) { PrintIfWatched("Nearby friends = %d, enemies = %d\n", m_nearbyFriendCount, m_nearbyEnemyCount); } } { // Track the place where we saw most of our enemies struct PlaceRank { unsigned int place; int count; }; static PlaceRank placeRank[ MAX_PLACES_PER_MAP ]; int locCount = 0; PlaceRank common; common.place = 0; common.count = 0; for (i = 0; i < threatCount; ++i) { // find the area the player/bot is standing on CNavArea *area; CCSBot *bot = dynamic_cast<CCSBot *>(threat[i].enemy); if (bot != NULL && bot->IsBot()) { area = bot->GetLastKnownArea(); } else { area = TheNavAreaGrid.GetNearestNavArea(&threat[i].enemy->pev->origin); } if (area == NULL) continue; unsigned int threatLoc = area->GetPlace(); if (!threatLoc) continue; // if place is already in set, increment count int j; for (j = 0; j < locCount; ++j) { if (placeRank[j].place == threatLoc) break; } if (j == locCount) { // new place if (locCount < MAX_PLACES_PER_MAP) { placeRank[ locCount ].place = threatLoc; placeRank[ locCount ].count = 1; if (common.count == 0) common = placeRank[locCount]; ++locCount; } } else { // others are in that place, increment ++placeRank[j].count; // keep track of the most common place if (placeRank[j].count > common.count) common = placeRank[j]; } } // remember most common place m_enemyPlace = common.place; } { if (threatCount == 0) return NULL; // otherwise, find the closest threat that without using shield int t; for (t = 0; t < threatCount; ++t) { if (!threat[t].enemy->IsProtectedByShield()) { return threat[t].enemy; } } } // return closest threat return threat[0].enemy; }