|
6 | 6 | #include "bot/behavior/neo_bot_attack.h" |
7 | 7 | #include "bot/behavior/neo_bot_seek_and_destroy.h" |
8 | 8 | #include "nav_mesh.h" |
| 9 | +#include "neo_ghost_cap_point.h" |
9 | 10 |
|
10 | 11 | extern ConVar neo_bot_path_lookahead_range; |
11 | 12 | extern ConVar neo_bot_offense_must_push_time; |
@@ -369,21 +370,114 @@ void CNEOBotSeekAndDestroy::RecomputeSeekPath( CNEOBot *me ) |
369 | 370 | #endif |
370 | 371 |
|
371 | 372 | if (NEORules()->GhostExists()) |
372 | | - { // If the ghost exists, go to the ghost |
373 | | - m_vGoalPos = NEORules()->GetGhostPos(); |
374 | | - constexpr int DISTANCE_CONSIDERED_ARRIVED_SQUARED = 5000; |
375 | | - if (m_vGoalPos.DistToSqr(me->GetAbsOrigin()) < DISTANCE_CONSIDERED_ARRIVED_SQUARED) |
| 373 | + { |
| 374 | + const Vector vGhostPos = NEORules()->GetGhostPos(); |
| 375 | + const int iGhosterPlayer = NEORules()->GetGhosterPlayer(); |
| 376 | + |
| 377 | + bool bGoToGoalPos = true; |
| 378 | + bool bGetCloserToGhoster = false; |
| 379 | + bool bQuickToGoalPos = false; |
| 380 | + |
| 381 | + if (iGhosterPlayer > 0) |
376 | 382 | { |
377 | | - constexpr float RECHECK_TIME = 30.f; |
378 | | - m_repathTimer.Start(RECHECK_TIME); |
379 | | - m_bGoingToTargetEntity = false; |
380 | | - return; |
| 383 | + const int iMyTeam = me->GetTeamNumber(); |
| 384 | + const int iGhosterTeam = NEORules()->GetGhosterTeam(); |
| 385 | + |
| 386 | + // If there's a player playing ghost, turn toward cap zones that's |
| 387 | + // closest to the ghoster player |
| 388 | + Vector vrTargetCapPos; |
| 389 | + int iMinCapGhostLength = INT_MAX; |
| 390 | + |
| 391 | + // Enemy team is carrying the ghost - try to defend the cap zone |
| 392 | + // You or friendly team is carrying the ghost - go towards the cap point |
| 393 | + const int iTargetCapTeam = (iGhosterTeam == iMyTeam) ? iMyTeam : iGhosterTeam; |
| 394 | + |
| 395 | + for (int i = 0; i < NEORules()->m_pGhostCaps.Count(); i++) |
| 396 | + { |
| 397 | + auto pGhostCap = dynamic_cast<CNEOGhostCapturePoint *>( |
| 398 | + UTIL_EntityByIndex(NEORules()->m_pGhostCaps[i])); |
| 399 | + if (!pGhostCap) |
| 400 | + { |
| 401 | + continue; |
| 402 | + } |
| 403 | + |
| 404 | + const Vector vCapPos = pGhostCap->GetAbsOrigin(); |
| 405 | + const Vector vGhostCapDist = vGhostPos - vCapPos; |
| 406 | + const int iGhostCapLength = static_cast<int>(vGhostCapDist.Length()); |
| 407 | + const int iCapTeam = pGhostCap->owningTeamAlternate(); |
| 408 | + |
| 409 | + if (iCapTeam == iTargetCapTeam && iGhostCapLength < iMinCapGhostLength) |
| 410 | + { |
| 411 | + vrTargetCapPos = vCapPos; |
| 412 | + iMinCapGhostLength = iGhostCapLength; |
| 413 | + } |
| 414 | + } |
| 415 | + |
| 416 | + if (!me->IsCarryingGhost()) |
| 417 | + { |
| 418 | + // If a ghoster player carrying and nearby, get close to them |
| 419 | + const float flGhosterMeters = METERS_PER_INCH * me->GetAbsOrigin().DistTo(vGhostPos); |
| 420 | + const float flMinCapMeters = METERS_PER_INCH * iMinCapGhostLength; |
| 421 | + static const constexpr float FL_NEARBY_FOLLOW_METERS = 26.0f; |
| 422 | + static const constexpr float FL_NEARBY_CAPZONE_METERS = 18.0f; |
| 423 | + const bool bGhosterNearby = flGhosterMeters < FL_NEARBY_FOLLOW_METERS; |
| 424 | + const bool bCapzoneNearby = flMinCapMeters < FL_NEARBY_CAPZONE_METERS; |
| 425 | + // But a nearby capzone overrides a nearby ghoster |
| 426 | + bGetCloserToGhoster = !bCapzoneNearby && bGhosterNearby && flMinCapMeters > flGhosterMeters; |
| 427 | + } |
| 428 | + |
| 429 | + if (bGetCloserToGhoster) |
| 430 | + { |
| 431 | + m_vGoalPos = vGhostPos; |
| 432 | + bQuickToGoalPos = true; |
| 433 | + } |
| 434 | + else |
| 435 | + { |
| 436 | + // iMinCapGhostLength == INT_MAX should never happen, just disable going to target |
| 437 | + Assert(iMinCapGhostLength < INT_MAX); |
| 438 | + bGoToGoalPos = (iMinCapGhostLength < INT_MAX); |
| 439 | + |
| 440 | + m_vGoalPos = vrTargetCapPos; |
| 441 | + bQuickToGoalPos = (iGhosterTeam != iMyTeam); |
| 442 | + } |
381 | 443 | } |
382 | | - m_bGoingToTargetEntity = true; |
383 | | - CNEOBotPathCost cost(me, SAFEST_ROUTE); |
384 | | - if (m_path.Compute(me, m_vGoalPos, cost, 0.0f, true, true) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH) |
| 444 | + else |
385 | 445 | { |
386 | | - return; |
| 446 | + // If the ghost exists, go to the ghost |
| 447 | + m_vGoalPos = vGhostPos; |
| 448 | + // NEO TODO (nullsystem): More sophisticated on handling non-ghost playing scenario, |
| 449 | + // although it kind of already prefer hunting down players when they're in view, but |
| 450 | + // just going towards ghost isn't something that always happens in general. |
| 451 | + } |
| 452 | + |
| 453 | + if (bGoToGoalPos) |
| 454 | + { |
| 455 | + if (bGetCloserToGhoster) |
| 456 | + { |
| 457 | + constexpr int DISTANCE_CONSIDERED_ASSISTING_SQUARED = 50000; |
| 458 | + if (m_vGoalPos.DistToSqr(me->GetAbsOrigin()) < DISTANCE_CONSIDERED_ASSISTING_SQUARED) |
| 459 | + { |
| 460 | + // Don't stop targeting entity even when near enough |
| 461 | + return; |
| 462 | + } |
| 463 | + } |
| 464 | + else |
| 465 | + { |
| 466 | + constexpr int DISTANCE_CONSIDERED_ARRIVED_SQUARED = 10000; |
| 467 | + if (m_vGoalPos.DistToSqr(me->GetAbsOrigin()) < DISTANCE_CONSIDERED_ARRIVED_SQUARED) |
| 468 | + { |
| 469 | + constexpr float RECHECK_TIME = 30.f; |
| 470 | + m_repathTimer.Start(RECHECK_TIME); |
| 471 | + m_bGoingToTargetEntity = false; |
| 472 | + return; |
| 473 | + } |
| 474 | + } |
| 475 | + m_bGoingToTargetEntity = true; |
| 476 | + CNEOBotPathCost cost(me, bQuickToGoalPos ? FASTEST_ROUTE : SAFEST_ROUTE); |
| 477 | + if (m_path.Compute(me, m_vGoalPos, cost, 0.0f, true, true) && m_path.IsValid() && m_path.GetResult() == Path::COMPLETE_PATH) |
| 478 | + { |
| 479 | + return; |
| 480 | + } |
387 | 481 | } |
388 | 482 | } |
389 | 483 |
|
|
0 commit comments