ProtoBot
Loading...
Searching...
No Matches
ScoutingProbe Class Reference

ScoutingProbe handles the initial scouting of the enemy base. The probe then steals gas if available and then starts a cycle of harassing enemy workers then orbiting their base. More...

#include <ScoutingProbe.h>

Public Member Functions

 ScoutingProbe (ProtoBotCommander *commander, ScoutingManager *manager)
void onStart ()
 Initializes the scouting probe state.
void onFrame ()
 Main update loop executed every frame.
void onUnitDestroy (BWAPI::Unit unit)
void setEnemyMain (const BWAPI::TilePosition &tp)
void assign (BWAPI::Unit unit)
 Assigns a unit as the scouting probe.
bool hasScout () const
void drawDebug () const
 Draws debug information on the game map.

Private Types

enum class  State {
  Search , GasSteal , Harass , Orbit ,
  ReturningCargo , Done
}

Private Member Functions

void buildStartTargets ()
void issueMoveToward (const BWAPI::Position &p, int reissueDist=32, bool force=false)
bool seeAnyEnemyBuildingNear (const BWAPI::Position &p, int radius) const
bool anyRefineryOn (BWAPI::Unit geyser) const
bool tryGasSteal ()
 Attempts to perform a gas steal on the enemy geyser.
bool tryHarassWorker ()
 Attempts to harass nearby enemy workers.
void ensureOrbitWaypoints ()
 Generates orbit waypoints around the enemy base.
BWAPI::Position currentOrbitPoint () const
void advanceOrbitIfArrived ()
bool threatenedNow () const
BWAPI::Position getAvgPosition ()
bool planTerrainPathTo (const BWAPI::Position &goal)
bool hasPlannedPath () const
BWAPI::Position currentPlannedWaypoint () const
BWAPI::Position computeSidestepTarget (const BWAPI::Position &goal)
bool isStuck (int now)
 Detects whether the probe is stuck.
BWAPI::Position computeEscapeGoal () const
BWAPI::Unit findAssimilatorOnTargetGeyser () const
bool tryConfirmEnemyMainByStartLocations ()
void handleReturningCargoState ()
BWAPI::Unit findClosestDepot (const BWAPI::Position &from) const
BWAPI::Position computeOrbitCenter () const
bool planAStarPathTo (const BWAPI::Position &goal, bool interactableEndpoint=false)
 Generates an A* path to a target position.
void followPlannedPath (const BWAPI::Position &finalGoal, int reissueDist=48)

Static Private Member Functions

static int angleDeg (const BWAPI::Position &from, const BWAPI::Position &to)
static int normDeg (int d)
static BWAPI::Position clampToMapPx (const BWAPI::Position &p, int marginPx=32)
static BWAPI::Position snapToNearestWalkable (BWAPI::Position p, int maxRadiusPx=128)
static BWAPI::Position snapToNearestWalkableClear (BWAPI::Position p, BWAPI::UnitType ut, int maxRadiusPx=128)

Private Attributes

ProtoBotCommandercommanderRef = nullptr
ScoutingManagermanager = nullptr
BWAPI::Unit scout = nullptr
std::optional< BWAPI::TilePosition > enemyMainTile
BWAPI::Position enemyMainPos = BWAPI::Positions::Invalid
std::vector< BWAPI::TilePosition > startTargets
std::size_t nextTarget = 0
State state = State::Done
bool gasStealDone = false
BWAPI::Unit targetGeyser = nullptr
int nextGasStealRetryFrame = 0
bool gasStealRequested = false
bool gasStealApproved = false
int gasStealRequestFrame = 0
int nextCheesePollFrame = 0
bool gasStealHoldingForMinerals = false
int nextMineralCheckFrame = 0
int lastMoveIssueFrame = 0
int lastHP = -1
int lastShields = -1
int lastThreatFrame = -999999
BWAPI::Position lastPos = BWAPI::Positions::Invalid
int lastProgressFrame = 0
int lastProgressDist = INT_MAX
int sidestepDir = 1
int sidestepAttempts = 0
int nextReplanFrame = 0
BWAPI::Position lastPlannedGoal = BWAPI::Positions::Invalid
BWAPI::Position currentMoveGoal = BWAPI::Positions::Invalid
int aStarEscapeUntilFrame = 0
BWAPI::Position aStarEscapeGoal = BWAPI::Positions::Invalid
BWAPI::Position lastStuckPos = BWAPI::Positions::Invalid
int lastStuckCheckFrame = 0
int stuckFrames = 0
std::vector< BWAPI::Position > orbitWaypoints
std::vector< BWAPI::Position > plannedPath
std::size_t orbitIdx = 0
int dwellUntilFrame = 0
int dbgLastOrbitPrintFrame = 0
int dbgLastOrbitBuildMs = 0
int dbgLastOrbitReplanMs = 0
int dbgLastAStarMs = 0
int dbgLastSnapMs = 0

Static Private Attributes

static constexpr int kCloseEnoughToTarget = 96
static constexpr int kMoveCooldownFrames = 8
static constexpr int kOrbitRadius = 350
static constexpr int kGasStealRetryCooldown = 24
static constexpr int kHarassRadiusFromMain = 320
static constexpr int kThreatRearmFrames = 8
static constexpr int kCalmFramesToResumeHarass = 72
static constexpr int kReplanFrames = 24
static constexpr int kGoalChangeReplanDist = 96

Detailed Description

ScoutingProbe handles the initial scouting of the enemy base. The probe then steals gas if available and then starts a cycle of harassing enemy workers then orbiting their base.

Definition at line 18 of file ScoutingProbe.h.

Member Enumeration Documentation

◆ State

enum class ScoutingProbe::State
strongprivate
Enumerator
Search 

Visiting potential enemy start locations.

GasSteal 

Attempting to steal enemy gas.

Harass 

Attacking enemy workers.

Orbit 

Circling enemy base while avoiding threats.

ReturningCargo 

Returning carried resources.

Done 

Behavior finished.

Definition at line 36 of file ScoutingProbe.h.

37 {
39 Search,
40
42 GasSteal,
43
45 Harass,
46
48 Orbit,
49
51 ReturningCargo,
52
54 Done
55 };

Constructor & Destructor Documentation

◆ ScoutingProbe()

ScoutingProbe::ScoutingProbe ( ProtoBotCommander * commander,
ScoutingManager * manager )
inlineexplicit

Definition at line 20 of file ScoutingProbe.h.

21 : commanderRef(commander), manager(manager) {
22 }

Member Function Documentation

◆ advanceOrbitIfArrived()

void ScoutingProbe::advanceOrbitIfArrived ( )
private

Definition at line 804 of file ScoutingProbe.cpp.

804 {
805 const auto goal = currentOrbitPoint();
806 if (!goal.isValid()) return;
807 const int now = Broodwar->getFrameCount();
808
809 if (scout->getDistance(goal) < 80) {
810 if (dwellUntilFrame == 0) dwellUntilFrame = now + 12;
811 if (now >= dwellUntilFrame) {
812 orbitIdx = (orbitIdx + 1) % orbitWaypoints.size();
813 dwellUntilFrame = 0;
814 }
815 }
816 else {
817 dwellUntilFrame = 0;
818 }
819}

◆ angleDeg()

int ScoutingProbe::angleDeg ( const BWAPI::Position & from,
const BWAPI::Position & to )
staticprivate

Definition at line 852 of file ScoutingProbe.cpp.

852 {
853 const double ang = std::atan2(double(to.y - from.y), double(to.x - from.x)) * 180.0 / 3.141592653589793;
854 int d = int(std::lround(ang)); d %= 360; if (d < 0) d += 360; return d;
855}

◆ anyRefineryOn()

bool ScoutingProbe::anyRefineryOn ( BWAPI::Unit geyser) const
private

Definition at line 488 of file ScoutingProbe.cpp.

488 {
489 if (!geyser || !geyser->exists()) return false;
490 const TilePosition tp = geyser->getTilePosition();
491 for (auto& u : Broodwar->getAllUnits()) {
492 if (!u || !u->exists()) continue;
493 if (!u->getType().isRefinery()) continue;
494 if (u->getTilePosition() == tp) return true;
495 }
496 return false;
497}

◆ assign()

void ScoutingProbe::assign ( BWAPI::Unit unit)

Assigns a unit as the scouting probe.

Initializes tracking values such as HP/shields and determines the starting state of the probe.

If the unit is carrying resources, transitions to ReturningCargo, otherwise begins scouting.

Parameters
unitThe unit to assign as the scout

Definition at line 184 of file ScoutingProbe.cpp.

184 {
185 if (!unit || !unit->exists()) return;
186 scout = unit;
187 scout->stop();
188 //Broodwar->printf("[Scouting] Assigned scout: %s (id=%d)", scout->getType().c_str(), scout->getID());
189 lastHP = scout->getHitPoints();
190 lastShields = scout->getShields();
191 aStarEscapeUntilFrame = 0;
192 aStarEscapeGoal = BWAPI::Positions::Invalid;
193 stuckFrames = 0;
194 if (scout->isCarryingMinerals() || scout->isCarryingGas())
195 state = State::ReturningCargo;
196 else
197 state = startTargets.empty() ? State::Done : State::Search;
198}
@ Search
Visiting potential enemy start locations.
@ ReturningCargo
Returning carried resources.
@ Done
Behavior finished.

◆ buildStartTargets()

void ScoutingProbe::buildStartTargets ( )
private

Definition at line 414 of file ScoutingProbe.cpp.

414 {
415 startTargets.clear();
416 const TilePosition myStart = Broodwar->self()->getStartLocation();
417 for (const auto& area : Map::Instance().Areas()) {
418 for (const auto& base : area.Bases()) {
419 if (base.Starting() && base.Location() != myStart) {
420 startTargets.push_back(base.Location());
421 }
422 }
423 }
424 std::sort(startTargets.begin(), startTargets.end(),
425 [&](const TilePosition& a, const TilePosition& b) {
426 return Position(myStart).getDistance(Position(a)) <
427 Position(myStart).getDistance(Position(b));
428 });
429 //Broodwar->printf("[Scouting] %d start targets.", static_cast<int>(startTargets.size()));
430}

◆ clampToMapPx()

BWAPI::Position ScoutingProbe::clampToMapPx ( const BWAPI::Position & p,
int marginPx = 32 )
staticprivate

Definition at line 952 of file ScoutingProbe.cpp.

952 {
953 int x = std::max(marginPx, std::min(p.x, mapWpx() - 1 - marginPx));
954 int y = std::max(marginPx, std::min(p.y, mapHpx() - 1 - marginPx));
955 return Position(x, y);
956}

◆ computeEscapeGoal()

BWAPI::Position ScoutingProbe::computeEscapeGoal ( ) const
private

Definition at line 1277 of file ScoutingProbe.cpp.

1278{
1279 if (!scout || !scout->exists())
1280 {
1281 return BWAPI::Positions::Invalid;
1282 }
1283
1284 const BWAPI::Position from = scout->getPosition();
1285
1286 // Prefer orbit target, but if it's invalid fall back to enemy main center
1287 BWAPI::Position baseGoal = currentOrbitPoint();
1288 if (!baseGoal.isValid())
1289 {
1290 baseGoal = enemyMainPos;
1291 }
1292
1293 if (!baseGoal.isValid())
1294 {
1295 return BWAPI::Positions::Invalid;
1296 }
1297
1298 // Push outward away from threat direction to break worker surrounds
1299 const BWAPI::Position d = baseGoal - from;
1300 if (!d.isValid() || (d.x == 0 && d.y == 0))
1301 {
1302 return snapToNearestWalkable(from, 192);
1303 }
1304
1305 const double len = std::sqrt(double(d.x) * double(d.x) + double(d.y) * double(d.y));
1306 if (len < 1.0)
1307 {
1308 return snapToNearestWalkable(from, 192);
1309 }
1310
1311 const double nx = double(d.x) / len;
1312 const double ny = double(d.y) / len;
1313
1314 // step a bit toward the orbit target so A* can route around minerals/workers
1315 BWAPI::Position raw(from.x + int(nx * 256.0), from.y + int(ny * 256.0));
1316 raw = clampToMapPx(raw, 32);
1317
1318 // Make sure it's walkable-ish
1319 return snapToNearestWalkable(raw, 256);
1320}

◆ computeOrbitCenter()

BWAPI::Position ScoutingProbe::computeOrbitCenter ( ) const
private

Definition at line 857 of file ScoutingProbe.cpp.

857 {
858 if (enemyMainPos.isValid())
859 {
860 return enemyMainPos;
861 }
862
863 if (scout && scout->exists())
864 {
865 return scout->getPosition();
866 }
867
868 return Position(0, 0);
869}

◆ computeSidestepTarget()

BWAPI::Position ScoutingProbe::computeSidestepTarget ( const BWAPI::Position & goal)
private

Definition at line 889 of file ScoutingProbe.cpp.

890{
891 if (!scout || !scout->exists() || !goal.isValid())
892 {
893 return BWAPI::Positions::Invalid;
894 }
895
896 const BWAPI::Position from = scout->getPosition();
897 const BWAPI::Position d = goal - from;
898
899 if (!d.isValid() || (d.x == 0 && d.y == 0))
900 {
901 return BWAPI::Positions::Invalid;
902 }
903
904 const double len = std::sqrt(double(d.x) * double(d.x) + double(d.y) * double(d.y));
905 if (len < 1.0)
906 {
907 return BWAPI::Positions::Invalid;
908 }
909
910 const double nx = double(d.x) / len;
911 const double ny = double(d.y) / len;
912
913 // perpendicular unit vector, flip using sidestepDir
914 const double px = -ny * double(sidestepDir);
915 const double py = nx * double(sidestepDir);
916
917 const int steps[] = { 64, 96, 128, 160 };
918
919 for (int step : steps)
920 {
921 BWAPI::Position raw(from.x + int(px * step), from.y + int(py * step));
922
923 raw = clampToMapPx(raw, 32);
924
925 BWAPI::Position snapped = snapToNearestWalkable(raw, 160);
926 if (snapped.isValid())
927 {
928 return snapped;
929 }
930 }
931
932 // swap direction once and retry
933 sidestepDir = -sidestepDir;
934
935 for (int step : steps)
936 {
937 BWAPI::Position raw(from.x + int(-px * step), from.y + int(-py * step));
938
939 raw = clampToMapPx(raw, 32);
940
941 BWAPI::Position snapped = snapToNearestWalkable(raw, 160);
942 if (snapped.isValid())
943 {
944 return snapped;
945 }
946 }
947
948 return BWAPI::Positions::Invalid;
949}

◆ currentOrbitPoint()

BWAPI::Position ScoutingProbe::currentOrbitPoint ( ) const
private

Definition at line 799 of file ScoutingProbe.cpp.

799 {
800 if (orbitWaypoints.empty()) return enemyMainPos;
801 return orbitWaypoints[orbitIdx % orbitWaypoints.size()];
802}

◆ currentPlannedWaypoint()

BWAPI::Position ScoutingProbe::currentPlannedWaypoint ( ) const
private

Definition at line 884 of file ScoutingProbe.cpp.

884 {
885 if (plannedPath.empty()) return Positions::Invalid;
886 return plannedPath.front();
887}

◆ drawDebug()

void ScoutingProbe::drawDebug ( ) const

Draws debug information on the game map.

Displays:

  • Current state
  • Movement targets
  • Orbit waypoints
  • Planned paths
  • Escape goals

Used for debugging and visualization.

Definition at line 1401 of file ScoutingProbe.cpp.

1402{
1403 if (!scout || !scout->exists())
1404 {
1405 return;
1406 }
1407
1408 BWAPI::Position p = scout->getPosition();
1409
1410 const char* stateName = "Unknown";
1411 switch (state)
1412 {
1413 case State::Search:
1414 stateName = "Search";
1415 break;
1416 case State::GasSteal:
1417 stateName = "GasSteal";
1418 break;
1419 case State::Harass:
1420 stateName = "Harass";
1421 break;
1422 case State::Orbit:
1423 stateName = "Orbit";
1424 break;
1426 stateName = "ReturningCargo";
1427 break;
1428 case State::Done:
1429 stateName = "Done";
1430 break;
1431 }
1432
1433 BWAPI::Broodwar->drawCircleMap(p, 18, BWAPI::Colors::Yellow, false);
1434 BWAPI::Broodwar->drawTextMap(p.x - 40, p.y - 46, "\x03Probe Scout");
1435 BWAPI::Broodwar->drawTextMap(p.x - 40, p.y - 34, "\x11State: %s", stateName);
1436
1437 if (enemyMainPos.isValid())
1438 {
1439 BWAPI::Broodwar->drawCircleMap(enemyMainPos, 18, BWAPI::Colors::Red, false);
1440 //BWAPI::Broodwar->drawTextMap(enemyMainPos.x + 8, enemyMainPos.y - 8, "\x08" "Enemy Main");
1441 }
1442
1443 if (state == State::Search)
1444 {
1445 if (nextTarget < startTargets.size())
1446 {
1447 BWAPI::Position tgt(startTargets[nextTarget]);
1448 BWAPI::Broodwar->drawLineMap(p, tgt, BWAPI::Colors::Green);
1449 BWAPI::Broodwar->drawCircleMap(tgt, 10, BWAPI::Colors::Green, false);
1450 BWAPI::Broodwar->drawTextMap(tgt.x + 6, tgt.y - 6, "\x07Search Target");
1451 }
1452
1453 for (std::size_t i = 0; i < startTargets.size(); ++i)
1454 {
1455 BWAPI::Position tp(startTargets[i]);
1456 BWAPI::Color c = (i == nextTarget) ? BWAPI::Colors::Green : BWAPI::Colors::White;
1457 BWAPI::Broodwar->drawCircleMap(tp, 6, c, false);
1458 BWAPI::Broodwar->drawTextMap(tp.x + 4, tp.y - 4, "#%d", (int)i);
1459 }
1460 }
1461
1462 if (state == State::GasSteal && targetGeyser && targetGeyser->exists())
1463 {
1464 BWAPI::Position gp = targetGeyser->getPosition();
1465 BWAPI::Broodwar->drawLineMap(p, gp, BWAPI::Colors::Orange);
1466 BWAPI::Broodwar->drawCircleMap(gp, 14, BWAPI::Colors::Orange, false);
1467 BWAPI::Broodwar->drawTextMap(gp.x + 6, gp.y - 6, "\x10Target Geyser");
1468
1469 BWAPI::Broodwar->drawTextMap(
1470 p.x - 40,
1471 p.y - 22,
1472 "\x10Req:%d Appr:%d Hold:%d",
1473 gasStealRequested ? 1 : 0,
1474 gasStealApproved ? 1 : 0,
1475 gasStealHoldingForMinerals ? 1 : 0
1476 );
1477 }
1478
1479 if (state == State::Harass)
1480 {
1481 BWAPI::Broodwar->drawTextMap(p.x - 40, p.y - 22, "\x08Mode: Harass");
1482 }
1483
1484 if (state == State::Orbit)
1485 {
1486 BWAPI::Position orbitGoal = currentOrbitPoint();
1487 if (orbitGoal.isValid())
1488 {
1489 BWAPI::Broodwar->drawLineMap(p, orbitGoal, BWAPI::Colors::Cyan);
1490 BWAPI::Broodwar->drawCircleMap(orbitGoal, 10, BWAPI::Colors::Cyan, false);
1491 BWAPI::Broodwar->drawTextMap(orbitGoal.x + 6, orbitGoal.y - 6, "\x0fOrbit Goal");
1492 }
1493
1494 for (std::size_t i = 0; i < orbitWaypoints.size(); ++i)
1495 {
1496 BWAPI::Color c = (i == orbitIdx % (orbitWaypoints.empty() ? 1 : orbitWaypoints.size()))
1497 ? BWAPI::Colors::Cyan
1498 : BWAPI::Colors::Blue;
1499
1500 BWAPI::Broodwar->drawCircleMap(orbitWaypoints[i], 6, c, false);
1501
1502 if (i + 1 < orbitWaypoints.size())
1503 {
1504 BWAPI::Broodwar->drawLineMap(orbitWaypoints[i], orbitWaypoints[i + 1], BWAPI::Colors::Blue);
1505 }
1506 }
1507
1508 if (!orbitWaypoints.empty())
1509 {
1510 BWAPI::Broodwar->drawLineMap(
1511 orbitWaypoints.back(),
1512 orbitWaypoints.front(),
1513 BWAPI::Colors::Blue
1514 );
1515 }
1516 }
1517
1518 if (!plannedPath.empty())
1519 {
1520 BWAPI::Position prev = p;
1521 for (const BWAPI::Position& wp : plannedPath)
1522 {
1523 if (!wp.isValid())
1524 {
1525 continue;
1526 }
1527
1528 BWAPI::Broodwar->drawLineMap(prev, wp, BWAPI::Colors::White);
1529 BWAPI::Broodwar->drawCircleMap(wp, 4, BWAPI::Colors::White, true);
1530 prev = wp;
1531 }
1532 }
1533
1534 if (currentMoveGoal.isValid())
1535 {
1536 BWAPI::Broodwar->drawLineMap(p, currentMoveGoal, BWAPI::Colors::Green);
1537 BWAPI::Broodwar->drawCircleMap(currentMoveGoal, 8, BWAPI::Colors::Green, false);
1538 }
1539
1540 if (aStarEscapeGoal.isValid())
1541 {
1542 BWAPI::Broodwar->drawLineMap(p, aStarEscapeGoal, BWAPI::Colors::Purple);
1543 BWAPI::Broodwar->drawCircleMap(aStarEscapeGoal, 10, BWAPI::Colors::Purple, false);
1544 BWAPI::Broodwar->drawTextMap(aStarEscapeGoal.x + 6, aStarEscapeGoal.y - 6, "\x05" "Escape");
1545 }
1546
1547}
@ Orbit
Circling enemy base while avoiding threats.
@ Harass
Attacking enemy workers.
@ GasSteal
Attempting to steal enemy gas.

◆ ensureOrbitWaypoints()

void ScoutingProbe::ensureOrbitWaypoints ( )
private

Generates orbit waypoints around the enemy base.

Creates a circular set of safe positions around the enemy main to allow continuous movement while avoiding threats.

Waypoints are snapped to valid walkable positions.

Definition at line 725 of file ScoutingProbe.cpp.

725 {
726 if (!orbitWaypoints.empty())
727 {
728 return;
729 }
730
731 int buildMs = 0;
732 {
733 ScopedMsTimer t("ensureOrbitWaypoints", &buildMs);
734
735 orbitWaypoints.clear();
736 orbitWaypoints.reserve(8);
737
738 const Position center = clampToMapPx(computeOrbitCenter(), 64);
739 const int radius = kOrbitRadius;
740
741 int snapTotalMs = 0;
742
743 for (int deg = 0; deg < 360; deg += 45)
744 {
745 Position raw = orbitPoint(center, radius, deg);
746 raw = clampToMapPx(raw, 48);
747
748 int snapMs = 0;
749 Position snapped;
750 {
751 ScopedMsTimer ts("snapToNearestWalkable", &snapMs);
752 const BWAPI::UnitType ut = (scout && scout->exists()) ? scout->getType() : BWAPI::UnitTypes::Protoss_Probe;
753 snapped = snapToNearestWalkableClear(raw, ut, 160);
754 }
755
756 snapTotalMs += snapMs;
757
758 if (snapped.isValid())
759 {
760 orbitWaypoints.push_back(snapped);
761 }
762 }
763
764 dbgLastSnapMs = snapTotalMs;
765
766 if (orbitWaypoints.empty())
767 {
768 orbitWaypoints.push_back(center);
769 }
770
771 Position threat = getAvgPosition();
772 int oppDeg = threat.isValid()
773 ? angleDeg(center, threat) + 180
774 : (scout && scout->exists() ? angleDeg(center, scout->getPosition()) + 180 : 0);
775
776 oppDeg = normDeg(oppDeg);
777
778 const int step = 45;
779 int startIdx = (oppDeg + step / 2) / step;
780 orbitIdx = static_cast<std::size_t>(startIdx % int(orbitWaypoints.size()));
781 dwellUntilFrame = 0;
782 }
783
784 dbgLastOrbitBuildMs = buildMs;
785
786 /*debugEveryNFramesIfMsUnder(
787 dbgLastOrbitPrintFrame,
788 24,
789 dbgLastOrbitBuildMs,
790 200,
791 [&]()
792 {
793 return "[Orbit] built waypoints in " + std::to_string(dbgLastOrbitBuildMs) +
794 "ms (snap total " + std::to_string(dbgLastSnapMs) + "ms)";
795 }
796 );*/
797}

◆ findAssimilatorOnTargetGeyser()

BWAPI::Unit ScoutingProbe::findAssimilatorOnTargetGeyser ( ) const
private

Definition at line 1068 of file ScoutingProbe.cpp.

1069{
1070 if (!targetGeyser || !targetGeyser->exists())
1071 {
1072 return nullptr;
1073 }
1074
1075 const BWAPI::TilePosition gtp = targetGeyser->getTilePosition();
1076
1077 for (BWAPI::Unit u : BWAPI::Broodwar->getAllUnits())
1078 {
1079 if (!u || !u->exists())
1080 {
1081 continue;
1082 }
1083
1084 if (u->getType() != BWAPI::UnitTypes::Protoss_Assimilator)
1085 {
1086 continue;
1087 }
1088
1089 if (u->getTilePosition() == gtp)
1090 {
1091 return u;
1092 }
1093 }
1094
1095 return nullptr;
1096}

◆ findClosestDepot()

BWAPI::Unit ScoutingProbe::findClosestDepot ( const BWAPI::Position & from) const
private

Definition at line 402 of file ScoutingProbe.cpp.

402 {
403 BWAPI::Unit best = nullptr; int bestDist = INT_MAX;
404 for (auto& u : BWAPI::Broodwar->self()->getUnits()) {
405 if (!u || !u->exists()) continue;
406 if (!u->getType().isResourceDepot()) continue;
407 int d = u->getDistance(from);
408 if (d < bestDist) { bestDist = d; best = u; }
409 }
410 return best;
411}

◆ followPlannedPath()

void ScoutingProbe::followPlannedPath ( const BWAPI::Position & finalGoal,
int reissueDist = 48 )
private

Definition at line 1157 of file ScoutingProbe.cpp.

1158{
1159 if (!scout || !scout->exists() || !finalGoal.isValid())
1160 {
1161 return;
1162 }
1163
1164 const int now = BWAPI::Broodwar->getFrameCount();
1165
1166 bool needReplan = false;
1167
1168 if (now >= nextReplanFrame)
1169 {
1170 needReplan = true;
1171 }
1172
1173 if (lastPlannedGoal.isValid() && finalGoal.getApproxDistance(lastPlannedGoal) > kGoalChangeReplanDist)
1174 {
1175 needReplan = true;
1176 }
1177
1178 if (!hasPlannedPath())
1179 {
1180 needReplan = true;
1181 }
1182
1183
1184
1185 if (needReplan)
1186 {
1187 int replanMs = 0;
1188 {
1189 ScopedMsTimer t("followPlannedPath replan", &replanMs);
1190 planAStarPathTo(finalGoal, false);
1191 }
1192 dbgLastOrbitReplanMs = replanMs;
1193
1194 lastPlannedGoal = finalGoal;
1195 nextReplanFrame = now + kReplanFrames;
1196
1197 /*debugEveryNFramesIfMsUnder(
1198 dbgLastOrbitPrintFrame,
1199 24,
1200 dbgLastOrbitReplanMs,
1201 200,
1202 [&]()
1203 {
1204 return "[Orbit] replan " + std::to_string(dbgLastOrbitReplanMs) +
1205 "ms (A* " + std::to_string(dbgLastAStarMs) +
1206 "ms) pathLen=" + std::to_string((int)plannedPath.size());
1207 }
1208 );*/
1209 }
1210
1211 if (plannedPath.empty())
1212 {
1213 issueMoveToward(finalGoal, reissueDist, true);
1214 return;
1215 }
1216
1217 while (!plannedPath.empty() && scout->getDistance(plannedPath.front()) < 72)
1218 {
1219 plannedPath.erase(plannedPath.begin());
1220 }
1221
1222 BWAPI::Position wp = plannedPath.empty() ? finalGoal : plannedPath.front();
1223 issueMoveToward(wp, reissueDist, false);
1224}
bool planAStarPathTo(const BWAPI::Position &goal, bool interactableEndpoint=false)
Generates an A* path to a target position.

◆ getAvgPosition()

BWAPI::Position ScoutingProbe::getAvgPosition ( )
private

Definition at line 840 of file ScoutingProbe.cpp.

840 {
841 long long sumX = 0, sumY = 0; int numUnits = 0;
842 const Unitset& group = Broodwar->enemy()->getUnits();
843 for (auto u : group) {
844 if (!u || !u->exists()) continue;
845 const Position p = u->getPosition();
846 if (!p.isValid()) continue;
847 sumX += p.x; sumY += p.y; ++numUnits;
848 }
849 return (numUnits > 0) ? Position(int(sumX / numUnits), int(sumY / numUnits)) : Positions::Invalid;
850}

◆ handleReturningCargoState()

void ScoutingProbe::handleReturningCargoState ( )
private

Definition at line 386 of file ScoutingProbe.cpp.

386 {
387 if (!scout) return;
388 if (!scout->isCarryingMinerals() && !scout->isCarryingGas()) {
389 state = startTargets.empty() ? State::Done : State::Search;
390 return;
391 }
392 if (scout->getOrder() != Orders::ReturnMinerals && scout->getOrder() != Orders::ReturnGas) {
393 if (!scout->returnCargo()) {
394 if (BWAPI::Unit depot = findClosestDepot(scout->getPosition())) {
395 scout->rightClick(depot);
396 }
397 }
398 }
399 //Broodwar->drawTextMap(scout->getPosition(), "Returning cargo...");
400}

◆ hasPlannedPath()

bool ScoutingProbe::hasPlannedPath ( ) const
inlineprivate

Definition at line 138 of file ScoutingProbe.h.

138{ return !plannedPath.empty(); }

◆ hasScout()

bool ScoutingProbe::hasScout ( ) const
inline

Definition at line 30 of file ScoutingProbe.h.

30{ return scout && scout->exists(); }

◆ isStuck()

bool ScoutingProbe::isStuck ( int now)
private

Detects whether the probe is stuck.

Compares position changes over time to determine if the unit is not making meaningful movement.

Parameters
nowCurrent frame count
Returns
True if the probe is considered stuck

Definition at line 1235 of file ScoutingProbe.cpp.

1236{
1237 if (!scout || !scout->exists())
1238 {
1239 return false;
1240 }
1241
1242 static constexpr int kStuckCheckEvery = 4;
1243 static constexpr int kStuckMoveEps = 16;
1244 static constexpr int kStuckTriggerFrames = 8;
1245
1246 if (now - lastStuckCheckFrame < kStuckCheckEvery)
1247 {
1248 return stuckFrames >= kStuckTriggerFrames;
1249 }
1250
1251 lastStuckCheckFrame = now;
1252
1253 const BWAPI::Position cur = scout->getPosition();
1254
1255 if (!lastStuckPos.isValid())
1256 {
1257 lastStuckPos = cur;
1258 stuckFrames = 0;
1259 return false;
1260 }
1261
1262 const int moved = cur.getApproxDistance(lastStuckPos);
1263 lastStuckPos = cur;
1264
1265 if (moved < kStuckMoveEps)
1266 {
1267 stuckFrames += kStuckCheckEvery;
1268 }
1269 else
1270 {
1271 stuckFrames = 0;
1272 }
1273
1274 return stuckFrames >= kStuckTriggerFrames;
1275}

◆ issueMoveToward()

void ScoutingProbe::issueMoveToward ( const BWAPI::Position & p,
int reissueDist = 32,
bool force = false )
private

Definition at line 432 of file ScoutingProbe.cpp.

433{
434 if (!scout || !scout->exists() || !p.isValid())
435 {
436 return;
437 }
438
439 if (!force && Broodwar->getFrameCount() - lastMoveIssueFrame < kMoveCooldownFrames)
440 {
441 return;
442 }
443
444 BWAPI::Position target = p;
445
446 target = snapToNearestWalkableClear(target, scout->getType(), 192);
447
448 // If we are trying to move into a building footprint, trigger A* escape mode
449 if (!IsFreeForUnitFootprint(target, scout->getType()))
450 {
451 aStarEscapeGoal = snapToNearestWalkableClear(target, scout->getType(), 256);
452 if (!aStarEscapeGoal.isValid())
453 {
454 aStarEscapeGoal = snapToNearestWalkable(target, 256);
455 }
456
457 if (aStarEscapeGoal.isValid())
458 {
459 plannedPath.clear();
460 planAStarPathTo(aStarEscapeGoal, false);
461 aStarEscapeUntilFrame = Broodwar->getFrameCount() + 48;
462 stuckFrames = 0;
463 return;
464 }
465 }
466
467 if (force || scout->getDistance(target) > reissueDist || !scout->isMoving())
468 {
469 scout->move(target);
470 currentMoveGoal = target;
471 lastMoveIssueFrame = Broodwar->getFrameCount();
472 }
473
474 //Broodwar->drawLineMap(scout->getPosition(), target, Colors::Yellow);
475}

◆ normDeg()

int ScoutingProbe::normDeg ( int d)
inlinestaticprivate

Definition at line 153 of file ScoutingProbe.h.

153{ d %= 360; return d < 0 ? d + 360 : d; }

◆ onFrame()

void ScoutingProbe::onFrame ( )

Main update loop executed every frame.

Drives probe behavior using a finite state machine. Handles:

  • Threat detection
  • Movement and pathing
  • State transitions

States handled:

  • Search
  • GasSteal
  • Harass
  • Orbit
  • ReturningCargo

Definition at line 225 of file ScoutingProbe.cpp.

225 {
226 if (!scout || !scout->exists() || state == State::Done) return;
227
228 const int now = Broodwar->getFrameCount();
229 const int hp = scout->getHitPoints();
230 const int sh = scout->getShields();
231 const bool tookDamage = (lastHP >= 0 && lastShields >= 0) && (hp + sh) < (lastHP + lastShields);
232 const bool imminent = scout->isUnderAttack() || threatenedNow();
233
234 if ((tookDamage || imminent) && (now - lastThreatFrame >= kThreatRearmFrames)) {
235 lastThreatFrame = now;
237
238 if (!hasPlannedPath() || scout->getDistance(currentPlannedWaypoint()) < 80) {
239 if (hasPlannedPath()) plannedPath.erase(plannedPath.begin());
240 if (!hasPlannedPath()) planTerrainPathTo(currentOrbitPoint());
241 }
242 const auto wp = currentPlannedWaypoint();
243 if (wp.isValid()) issueMoveToward(wp, /*reissue*/48);
244 state = State::Orbit;
245 lastHP = hp; lastShields = sh;
246 return;
247 }
248
249 switch (state) {
251 handleReturningCargoState();
252 break;
253
254 case State::Search:
255 {
256 if (nextTarget >= (int)startTargets.size())
257 {
258 state = State::Done;
259 break;
260 }
261
262 const auto tp = startTargets[nextTarget];
263 const BWAPI::Position goal(tp);
264
265 if (plannedPath.empty() || !lastPlannedGoal.isValid() || lastPlannedGoal.getApproxDistance(goal) > 32)
266 {
267 planTerrainPathTo(goal);
268 lastPlannedGoal = goal;
269 nextReplanFrame = now + 999999;
270 }
271
272 followPlannedPath(goal, 64);
273
274 if (seeAnyEnemyBuildingNear(goal, 800))
275 {
276 //Broodwar->printf("[Scouting] Enemy main at (%d,%d)", tp.x, tp.y);
277
278 if (manager && !manager->getEnemyMain().has_value())
279 {
280 manager->setEnemyMain(tp);
281 }
282 else
283 {
284 enemyMainTile = tp;
285 enemyMainPos = goal;
286 state = State::GasSteal;
287 }
288
289 break;
290 }
291
292 if (scout->getDistance(goal) <= kCloseEnoughToTarget)
293 {
294 ++nextTarget;
295 }
296
297 break;
298 }
299
300 case State::GasSteal:
301 if (tryGasSteal()) break;
302 if (gasStealDone) state = State::Harass;
303 break;
304
305 case State::Harass:
306 if (enemyMainPos.isValid() && tryHarassWorker()) break;
307 state = State::Orbit;
308 break;
309
310 case State::Orbit:
311 {
312 if (threatenedNow())
313 {
314 lastThreatFrame = now;
315 }
316
317 if (enemyMainPos.isValid()
318 && (now - lastThreatFrame) >= kCalmFramesToResumeHarass)
319 {
320 plannedPath.clear();
321 aStarEscapeUntilFrame = 0;
322 aStarEscapeGoal = BWAPI::Positions::Invalid;
323 stuckFrames = 0;
324 state = State::Harass;
325 break;
326 }
327
329
330 // If we are currently in escape mode, follow A* until timeout or success
331 if (aStarEscapeUntilFrame > now && aStarEscapeGoal.isValid())
332 {
333 followPlannedPath(aStarEscapeGoal, 64);
334
335 // If we got far enough from where we were jammed, return to orbit
336 if (scout->getDistance(aStarEscapeGoal) < 96)
337 {
338 aStarEscapeUntilFrame = 0;
339 aStarEscapeGoal = BWAPI::Positions::Invalid;
340 plannedPath.clear();
341 stuckFrames = 0;
342 }
343
344 advanceOrbitIfArrived();
345 break;
346 }
347
348 // Normal mode: old orbit movement (no A*)
349 issueMoveToward(currentOrbitPoint(), 64, false);
350 advanceOrbitIfArrived();
351
352 // Detect jam and trigger A* escape
353 if (isStuck(now))
354 {
355 aStarEscapeGoal = computeEscapeGoal();
356
357 if (aStarEscapeGoal.isValid())
358 {
359 plannedPath.clear();
360 planAStarPathTo(aStarEscapeGoal, false);
361
362 // stay in escape mode for ~2 seconds max
363 aStarEscapeUntilFrame = now + 48;
364
365 // prevent immediate re-trigger loop
366 stuckFrames = 0;
367 }
368 }
369
370 break;
371 }
372
373 case State::Done:
374 default:
375 break;
376 }
377}
bool tryGasSteal()
Attempts to perform a gas steal on the enemy geyser.
bool isStuck(int now)
Detects whether the probe is stuck.
void ensureOrbitWaypoints()
Generates orbit waypoints around the enemy base.
bool tryHarassWorker()
Attempts to harass nearby enemy workers.

◆ onStart()

void ScoutingProbe::onStart ( )

Initializes the scouting probe state.

Sets up scouting targets and resets all internal state:

  • Enemy base tracking
  • Gas steal state
  • Orbit pathing
  • Movement tracking

Definition at line 147 of file ScoutingProbe.cpp.

147 {
148 if (!Map::Instance().Initialized()) {
149 //Broodwar->printf("ScoutingProbe: BWEM not initialized.");
150 return;
151 }
152 buildStartTargets();
153 enemyMainTile.reset();
154 enemyMainPos = Positions::Invalid;
155 gasStealDone = false;
156 targetGeyser = nullptr;
157 nextGasStealRetryFrame = 0;
158 lastMoveIssueFrame = 0;
159 orbitWaypoints.clear();
160 orbitIdx = 0;
161 dwellUntilFrame = 0;
162 gasStealHoldingForMinerals = false;
163 nextMineralCheckFrame = 0;
164 state = startTargets.empty() ? State::Done : State::Search;
165 aStarEscapeUntilFrame = 0;
166 aStarEscapeGoal = BWAPI::Positions::Invalid;
167
168 lastStuckPos = BWAPI::Positions::Invalid;
169 lastStuckCheckFrame = 0;
170 stuckFrames = 0;
171}

◆ onUnitDestroy()

void ScoutingProbe::onUnitDestroy ( BWAPI::Unit unit)

Definition at line 200 of file ScoutingProbe.cpp.

200 {
201 if (unit && scout && unit == scout) {
202 //Broodwar->printf("[Scouting] Scout destroyed. Clearing assignment.");
203 scout = nullptr;
204 state = State::Done;
205 }
206}

◆ planAStarPathTo()

bool ScoutingProbe::planAStarPathTo ( const BWAPI::Position & goal,
bool interactableEndpoint = false )
private

Generates an A* path to a target position.

Ensures the path is valid and avoids obstacles. Optionally adjusts the goal to a safe reachable position.

Parameters
goalTarget position
interactableEndpointWhether the endpoint must be directly reachable
Returns
True if a valid path was generated

Definition at line 1108 of file ScoutingProbe.cpp.

1109{
1110 plannedPath.clear();
1111
1112 if (!scout || !scout->exists() || !goal.isValid())
1113 {
1114 return false;
1115 }
1116
1117 int astarMs = 0;
1118 BWAPI::Position safeGoal = goal;
1119
1120 if (!interactableEndpoint)
1121 {
1122 const BWAPI::UnitType ut = scout->getType();
1123 if (!IsFreeForUnitFootprint(safeGoal, ut))
1124 {
1125 safeGoal = snapToNearestWalkableClear(safeGoal, ut, 256);
1126 }
1127 }
1128
1129 if (!safeGoal.isValid())
1130 {
1131 plannedPath.push_back(goal);
1132 return true;
1133 }
1134 Path p;
1135 {
1136 ScopedMsTimer t("AStar::GeneratePath", &astarMs);
1137 p = AStar::GeneratePath(scout->getPosition(), scout->getType(), safeGoal, interactableEndpoint);
1138 }
1139 dbgLastAStarMs = astarMs;
1140
1141 if (p.positions.empty())
1142 {
1143 plannedPath.push_back(goal);
1144 return true;
1145 }
1146
1147 plannedPath = p.positions;
1148
1149 if (!plannedPath.empty() && scout->getDistance(plannedPath.front()) < 24)
1150 {
1151 plannedPath.erase(plannedPath.begin());
1152 }
1153
1154 return !plannedPath.empty();
1155}
static Path GeneratePath(BWAPI::Position _start, BWAPI::UnitType unitType, BWAPI::Position _end, bool isInteractableEndpoint=false)
Generates path from start to end using A* pathfinding algorithm.
vector< BWAPI::Position > positions
A vector of BWAPI::Positions representing the waypoints along the path to follow ///<.

◆ planTerrainPathTo()

bool ScoutingProbe::planTerrainPathTo ( const BWAPI::Position & goal)
private

Definition at line 871 of file ScoutingProbe.cpp.

871 {
872 plannedPath.clear();
873 if (!scout || !scout->exists() || !goal.isValid()) return false;
874 const auto& cpPath = BWEM::Map::Instance().GetPath(scout->getPosition(), goal, nullptr);
875 if (cpPath.empty()) { plannedPath.push_back(goal); return true; }
876 for (const auto* cp : cpPath) {
877 if (!cp) continue;
878 plannedPath.push_back(Position(cp->Center()));
879 }
880 plannedPath.push_back(goal);
881 return true;
882}

◆ seeAnyEnemyBuildingNear()

bool ScoutingProbe::seeAnyEnemyBuildingNear ( const BWAPI::Position & p,
int radius ) const
private

Definition at line 479 of file ScoutingProbe.cpp.

479 {
480 for (auto& u : Broodwar->enemy()->getUnits()) {
481 if (!u || !u->exists()) continue;
482 if (!u->getType().isBuilding()) continue;
483 if (u->getPosition().isValid() && u->getDistance(p) <= radius) return true;
484 }
485 return false;
486}

◆ setEnemyMain()

void ScoutingProbe::setEnemyMain ( const BWAPI::TilePosition & tp)

Definition at line 379 of file ScoutingProbe.cpp.

379 {
380 enemyMainTile = tp;
381 enemyMainPos = Position(tp);
382 state = State::GasSteal;
383}

◆ snapToNearestWalkable()

BWAPI::Position ScoutingProbe::snapToNearestWalkable ( BWAPI::Position p,
int maxRadiusPx = 128 )
staticprivate

Definition at line 958 of file ScoutingProbe.cpp.

958 {
959 p = clampToMapPx(p, 32);
960 WalkPosition w0(p);
961 const int rMax = std::max(1, maxRadiusPx / 8);
962 auto toPosCenter = [](const WalkPosition& w) { return Position(w.x * 8 + 4, w.y * 8 + 4); };
963 if (w0.isValid() && Broodwar->isWalkable(w0)) return toPosCenter(w0);
964
965 int bestD2 = INT_MAX; WalkPosition best(-1, -1);
966 for (int r = 1; r <= rMax; ++r) {
967 bool foundThisRing = false;
968 for (int dy = -r; dy <= r; ++dy) {
969 for (int dx = -r; dx <= r; ++dx) {
970 if (std::max(std::abs(dx), std::abs(dy)) != r) continue;
971 WalkPosition w(w0.x + dx, w0.y + dy);
972 if (!w.isValid()) continue;
973 if (!Broodwar->isWalkable(w)) continue;
974 int d2 = dx * dx + dy * dy;
975 if (d2 < bestD2) { bestD2 = d2; best = w; foundThisRing = true; }
976 }
977 }
978 if (foundThisRing) break;
979 }
980 if (best.x != -1) return toPosCenter(best);
981 return Positions::Invalid;
982}

◆ snapToNearestWalkableClear()

BWAPI::Position ScoutingProbe::snapToNearestWalkableClear ( BWAPI::Position p,
BWAPI::UnitType ut,
int maxRadiusPx = 128 )
staticprivate

Definition at line 984 of file ScoutingProbe.cpp.

990{
991 p = clampToMapPx(p, 32);
992 BWAPI::WalkPosition w0(p);
993
994 const int rMax = std::max(1, maxRadiusPx / 8);
995
996 auto toPosCenter =
997 [](const BWAPI::WalkPosition& w)
998 {
999 return BWAPI::Position(w.x * 8 + 4, w.y * 8 + 4);
1000 };
1001
1002 if (w0.isValid() && BWAPI::Broodwar->isWalkable(w0))
1003 {
1004 const BWAPI::Position c = toPosCenter(w0);
1005 if (IsFreeForUnitFootprint(c, ut))
1006 {
1007 return c;
1008 }
1009 }
1010
1011 int bestD2 = INT_MAX;
1012 BWAPI::WalkPosition best(-1, -1);
1013
1014 for (int r = 1; r <= rMax; ++r)
1015 {
1016 bool foundThisRing = false;
1017
1018 for (int dy = -r; dy <= r; ++dy)
1019 {
1020 for (int dx = -r; dx <= r; ++dx)
1021 {
1022 if (std::max(std::abs(dx), std::abs(dy)) != r)
1023 {
1024 continue;
1025 }
1026
1027 BWAPI::WalkPosition w(w0.x + dx, w0.y + dy);
1028 if (!w.isValid())
1029 {
1030 continue;
1031 }
1032
1033 if (!BWAPI::Broodwar->isWalkable(w))
1034 {
1035 continue;
1036 }
1037
1038 const BWAPI::Position c = toPosCenter(w);
1039 if (!IsFreeForUnitFootprint(c, ut))
1040 {
1041 continue;
1042 }
1043
1044 const int d2 = dx * dx + dy * dy;
1045 if (d2 < bestD2)
1046 {
1047 bestD2 = d2;
1048 best = w;
1049 foundThisRing = true;
1050 }
1051 }
1052 }
1053
1054 if (foundThisRing)
1055 {
1056 break;
1057 }
1058 }
1059
1060 if (best.x != -1)
1061 {
1062 return toPosCenter(best);
1063 }
1064
1065 return BWAPI::Positions::Invalid;
1066}

◆ threatenedNow()

bool ScoutingProbe::threatenedNow ( ) const
private

Definition at line 821 of file ScoutingProbe.cpp.

821 {
822 if (!scout || !scout->exists()) return false;
823 if (scout->isUnderAttack()) return true;
824
825 for (auto& e : Broodwar->enemy()->getUnits()) {
826 if (!e || !e->exists()) continue;
827 if (e->getType().isWorker()) continue;
828 if (!e->getType().canAttack()) continue;
829 if (e->getDistance(scout) < 192) return true;
830 }
831 for (auto& e : Broodwar->enemy()->getUnits()) {
832 if (!e || !e->exists() || !e->getType().isWorker()) continue;
833 if (e->getDistance(scout) <= 112 &&
834 (e->isAttacking() || e->getOrder() == Orders::AttackUnit || e->getTarget() == scout))
835 return true;
836 }
837 return false;
838}

◆ tryConfirmEnemyMainByStartLocations()

bool ScoutingProbe::tryConfirmEnemyMainByStartLocations ( )
private

Definition at line 1322 of file ScoutingProbe.cpp.

1323{
1324 if (enemyMainTile.has_value() || startTargets.empty())
1325 {
1326 return false;
1327 }
1328
1329 BWAPI::Unit found = nullptr;
1330 BWAPI::TilePosition foundTp = BWAPI::TilePositions::Invalid;
1331
1332 static constexpr int kStartConfirmRadius = 9000;
1333
1334 for (auto& e : BWAPI::Broodwar->enemy()->getUnits())
1335 {
1336 if (!e || !e->exists())
1337 {
1338 continue;
1339 }
1340
1341 if (!e->getType().isBuilding())
1342 {
1343 continue;
1344 }
1345
1346 const BWAPI::Position bp = e->getPosition();
1347 if (!bp.isValid())
1348 {
1349 continue;
1350 }
1351
1352 for (const auto& tp : startTargets)
1353 {
1354 const BWAPI::Position sp(tp);
1355 if (bp.getDistance(sp) <= kStartConfirmRadius)
1356 {
1357 found = e;
1358 foundTp = tp;
1359 break;
1360 }
1361 }
1362
1363 if (found)
1364 {
1365 break;
1366 }
1367 }
1368
1369 if (!found || !foundTp.isValid())
1370 {
1371 return false;
1372 }
1373
1374 enemyMainTile = foundTp;
1375 enemyMainPos = BWAPI::Position(foundTp);
1376
1377 if (manager)
1378 {
1379 manager->setEnemyMain(foundTp);
1380 }
1381
1382 //BWAPI::Broodwar->printf("[Scouting] Enemy main confirmed near start (%d,%d)", foundTp.x, foundTp.y);
1383 state = State::GasSteal;
1384 return true;
1385}

◆ tryGasSteal()

bool ScoutingProbe::tryGasSteal ( )
private

Attempts to perform a gas steal on the enemy geyser.

Workflow:

  • Request permission from strategy manager
  • Wait for approval and sufficient minerals
  • Move to target geyser
  • Attempt to build Assimilator

Handles retries, movement, and fallback behavior.

Returns
True if still handling gas steal logic, false otherwise

Definition at line 513 of file ScoutingProbe.cpp.

514{
515 if (gasStealDone)
516 {
517 return false;
518 }
519
520 if (!scout || !scout->exists() || !scout->getType().isWorker())
521 {
522 return false;
523 }
524
525 if (!enemyMainPos.isValid())
526 {
527 return false;
528 }
529
530 const int now = Broodwar->getFrameCount();
531
532 if (now < nextGasStealRetryFrame)
533 {
534 return false;
535 }
536
537 if (Broodwar->self()->getRace() != Races::Protoss)
538 {
539 gasStealDone = true;
540 return false;
541 }
542
543 // 1) Strategy permission (for now assume true)
544 const bool allowedByStrategy = commanderRef->shouldGasSteal();
545 if (!allowedByStrategy)
546 {
547 gasStealDone = true;
548 return false;
549 }
550
551 // If approved but we don't have minerals yet, orbit and wait
552 if (gasStealApproved && Broodwar->self()->minerals() < UnitTypes::Protoss_Assimilator.mineralPrice())
553 {
554 gasStealHoldingForMinerals = true;
555
556 // Keep the scout near enemy main, but don't harass while waiting
558 issueMoveToward(currentOrbitPoint(), 64, false);
559 advanceOrbitIfArrived();
560
561 // Don't spam retry every frame
562 nextGasStealRetryFrame = now + 12;
563
564 return true; // stay in GasSteal state
565 }
566
567 gasStealHoldingForMinerals = false;
568 // 2) Find enemy geyser once
569 if (!targetGeyser || !targetGeyser->exists())
570 {
571 BWAPI::Unit best = nullptr;
572 int bestDist = INT_MAX;
573
574 for (auto g : Broodwar->getGeysers())
575 {
576 if (!g || !g->exists())
577 {
578 continue;
579 }
580
581 const int d = enemyMainPos.getDistance(g->getPosition());
582 if (d > 400)
583 {
584 continue;
585 }
586
587 if (anyRefineryOn(g))
588 {
589 gasStealDone = true;
590 return false;
591 }
592
593 if (d < bestDist)
594 {
595 bestDist = d;
596 best = g;
597 }
598 }
599
600 targetGeyser = best;
601
602 if (!targetGeyser)
603 {
604 gasStealDone = true;
605 return false;
606 }
607 }
608
609 if (anyRefineryOn(targetGeyser))
610 {
611 gasStealDone = true;
612 return false;
613 }
614
615 // 3) Make the request once
616 if (!gasStealRequested)
617 {
618 if (commanderRef)
619 {
620 commanderRef->requestCheese(UnitTypes::Protoss_Assimilator, scout);
621 }
622
623 gasStealRequested = true;
624 gasStealRequestFrame = now;
625 nextCheesePollFrame = now;
626 //Broodwar->printf("[GasSteal] Requested Assimilator cheese (unit=%d).", scout->getID());
627 }
628
629 // 4) Poll approval (throttle to avoid spamming)
630 if (!gasStealApproved && now >= nextCheesePollFrame)
631 {
632 gasStealApproved = commanderRef ? commanderRef->checkCheeseRequest(scout) : true;
633 nextCheesePollFrame = now + 12;
634 }
635
636 if (!gasStealApproved)
637 {
638 // While waiting for approval, keep moving toward the geyser
639 if (scout->getDistance(targetGeyser) > 96 || !scout->isMoving())
640 {
641 planAStarPathTo(targetGeyser->getPosition(), true);
642 followPlannedPath(targetGeyser->getPosition(), 64);
643 nextGasStealRetryFrame = now + kGasStealRetryCooldown;
644 return true;
645 }
646
647 return true;
648 }
649
650 // 5) Once approved, ensure we can afford it
651 if (Broodwar->self()->minerals() < UnitTypes::Protoss_Assimilator.mineralPrice())
652 {
653 nextGasStealRetryFrame = now + kGasStealRetryCooldown;
654 return true;
655 }
656
657 // 6) Go build it
658 const TilePosition gtp = targetGeyser->getTilePosition();
659
660 if (scout->getDistance(targetGeyser) > 96)
661 {
662 planAStarPathTo(targetGeyser->getPosition(), true);
663 followPlannedPath(targetGeyser->getPosition(), 64);
664 nextGasStealRetryFrame = now + kGasStealRetryCooldown;
665 return true;
666 }
667 // Try to issue the build (may take multiple frames)
668 if (BWAPI::Unit a = findAssimilatorOnTargetGeyser())
669 {
670 //BWAPI::Broodwar->printf("[GasSteal] Assimilator started (id=%d).", a->getID());
671 gasStealDone = true;
672 return true;
673 }
674
675 if (scout->build(BWAPI::UnitTypes::Protoss_Assimilator, gtp))
676 {
677 //BWAPI::Broodwar->printf("[GasSteal] Build command accepted at (%d,%d). Waiting for start...", gtp.x, gtp.y);
678 nextGasStealRetryFrame = now + 12; // short retry window while we wait for it to appear
679 return true;
680 }
681
682 // If build fails this frame, nudge and retry later
683 planAStarPathTo(targetGeyser->getPosition(), true);
684 followPlannedPath(targetGeyser->getPosition(), 64);
685 nextGasStealRetryFrame = now + kGasStealRetryCooldown;
686 return true;
687}

◆ tryHarassWorker()

bool ScoutingProbe::tryHarassWorker ( )
private

Attempts to harass nearby enemy workers.

Selects the closest valid worker near the enemy main base and issues an attack command.

Returns
True if a worker was targeted, false otherwise

Definition at line 697 of file ScoutingProbe.cpp.

697 {
698 if (!scout || !scout->exists() || !enemyMainPos.isValid()) return false;
699 BWAPI::Unit best = nullptr; int bestDist = 999999;
700 for (auto& e : Broodwar->enemy()->getUnits()) {
701 if (!e || !e->exists()) continue;
702 if (!e->getType().isWorker()) continue;
703 if (e->getDistance(enemyMainPos) > kHarassRadiusFromMain) continue;
704 int d = e->getDistance(scout);
705 if (d < bestDist) { bestDist = d; best = e; }
706 }
707 if (!best) return false;
708 if (Broodwar->getFrameCount() - lastMoveIssueFrame >= kMoveCooldownFrames) {
709 scout->attack(best);
710 lastMoveIssueFrame = Broodwar->getFrameCount();
711 }
712 //Broodwar->drawCircleMap(best->getPosition(), 10, Colors::Red, true);
713 return true;
714}

Member Data Documentation

◆ aStarEscapeGoal

BWAPI::Position ScoutingProbe::aStarEscapeGoal = BWAPI::Positions::Invalid
private

Definition at line 98 of file ScoutingProbe.h.

◆ aStarEscapeUntilFrame

int ScoutingProbe::aStarEscapeUntilFrame = 0
private

Definition at line 97 of file ScoutingProbe.h.

◆ commanderRef

ProtoBotCommander* ScoutingProbe::commanderRef = nullptr
private

Definition at line 58 of file ScoutingProbe.h.

◆ currentMoveGoal

BWAPI::Position ScoutingProbe::currentMoveGoal = BWAPI::Positions::Invalid
private

Definition at line 94 of file ScoutingProbe.h.

◆ dbgLastAStarMs

int ScoutingProbe::dbgLastAStarMs = 0
private

Definition at line 166 of file ScoutingProbe.h.

◆ dbgLastOrbitBuildMs

int ScoutingProbe::dbgLastOrbitBuildMs = 0
private

Definition at line 164 of file ScoutingProbe.h.

◆ dbgLastOrbitPrintFrame

int ScoutingProbe::dbgLastOrbitPrintFrame = 0
private

Definition at line 163 of file ScoutingProbe.h.

◆ dbgLastOrbitReplanMs

int ScoutingProbe::dbgLastOrbitReplanMs = 0
private

Definition at line 165 of file ScoutingProbe.h.

◆ dbgLastSnapMs

int ScoutingProbe::dbgLastSnapMs = 0
private

Definition at line 167 of file ScoutingProbe.h.

◆ dwellUntilFrame

int ScoutingProbe::dwellUntilFrame = 0
private

Definition at line 110 of file ScoutingProbe.h.

◆ enemyMainPos

BWAPI::Position ScoutingProbe::enemyMainPos = BWAPI::Positions::Invalid
private

Definition at line 64 of file ScoutingProbe.h.

◆ enemyMainTile

std::optional<BWAPI::TilePosition> ScoutingProbe::enemyMainTile
private

Definition at line 63 of file ScoutingProbe.h.

◆ gasStealApproved

bool ScoutingProbe::gasStealApproved = false
private

Definition at line 77 of file ScoutingProbe.h.

◆ gasStealDone

bool ScoutingProbe::gasStealDone = false
private

Definition at line 73 of file ScoutingProbe.h.

◆ gasStealHoldingForMinerals

bool ScoutingProbe::gasStealHoldingForMinerals = false
private

Definition at line 80 of file ScoutingProbe.h.

◆ gasStealRequested

bool ScoutingProbe::gasStealRequested = false
private

Definition at line 76 of file ScoutingProbe.h.

◆ gasStealRequestFrame

int ScoutingProbe::gasStealRequestFrame = 0
private

Definition at line 78 of file ScoutingProbe.h.

◆ kCalmFramesToResumeHarass

int ScoutingProbe::kCalmFramesToResumeHarass = 72
staticconstexprprivate

Definition at line 119 of file ScoutingProbe.h.

◆ kCloseEnoughToTarget

int ScoutingProbe::kCloseEnoughToTarget = 96
staticconstexprprivate

Definition at line 113 of file ScoutingProbe.h.

◆ kGasStealRetryCooldown

int ScoutingProbe::kGasStealRetryCooldown = 24
staticconstexprprivate

Definition at line 116 of file ScoutingProbe.h.

◆ kGoalChangeReplanDist

int ScoutingProbe::kGoalChangeReplanDist = 96
staticconstexprprivate

Definition at line 121 of file ScoutingProbe.h.

◆ kHarassRadiusFromMain

int ScoutingProbe::kHarassRadiusFromMain = 320
staticconstexprprivate

Definition at line 117 of file ScoutingProbe.h.

◆ kMoveCooldownFrames

int ScoutingProbe::kMoveCooldownFrames = 8
staticconstexprprivate

Definition at line 114 of file ScoutingProbe.h.

◆ kOrbitRadius

int ScoutingProbe::kOrbitRadius = 350
staticconstexprprivate

Definition at line 115 of file ScoutingProbe.h.

◆ kReplanFrames

int ScoutingProbe::kReplanFrames = 24
staticconstexprprivate

Definition at line 120 of file ScoutingProbe.h.

◆ kThreatRearmFrames

int ScoutingProbe::kThreatRearmFrames = 8
staticconstexprprivate

Definition at line 118 of file ScoutingProbe.h.

◆ lastHP

int ScoutingProbe::lastHP = -1
private

Definition at line 85 of file ScoutingProbe.h.

◆ lastMoveIssueFrame

int ScoutingProbe::lastMoveIssueFrame = 0
private

Definition at line 84 of file ScoutingProbe.h.

◆ lastPlannedGoal

BWAPI::Position ScoutingProbe::lastPlannedGoal = BWAPI::Positions::Invalid
private

Definition at line 93 of file ScoutingProbe.h.

◆ lastPos

BWAPI::Position ScoutingProbe::lastPos = BWAPI::Positions::Invalid
private

Definition at line 87 of file ScoutingProbe.h.

◆ lastProgressDist

int ScoutingProbe::lastProgressDist = INT_MAX
private

Definition at line 89 of file ScoutingProbe.h.

◆ lastProgressFrame

int ScoutingProbe::lastProgressFrame = 0
private

Definition at line 88 of file ScoutingProbe.h.

◆ lastShields

int ScoutingProbe::lastShields = -1
private

Definition at line 85 of file ScoutingProbe.h.

◆ lastStuckCheckFrame

int ScoutingProbe::lastStuckCheckFrame = 0
private

Definition at line 102 of file ScoutingProbe.h.

◆ lastStuckPos

BWAPI::Position ScoutingProbe::lastStuckPos = BWAPI::Positions::Invalid
private

Definition at line 101 of file ScoutingProbe.h.

◆ lastThreatFrame

int ScoutingProbe::lastThreatFrame = -999999
private

Definition at line 86 of file ScoutingProbe.h.

◆ manager

ScoutingManager* ScoutingProbe::manager = nullptr
private

Definition at line 59 of file ScoutingProbe.h.

◆ nextCheesePollFrame

int ScoutingProbe::nextCheesePollFrame = 0
private

Definition at line 79 of file ScoutingProbe.h.

◆ nextGasStealRetryFrame

int ScoutingProbe::nextGasStealRetryFrame = 0
private

Definition at line 75 of file ScoutingProbe.h.

◆ nextMineralCheckFrame

int ScoutingProbe::nextMineralCheckFrame = 0
private

Definition at line 81 of file ScoutingProbe.h.

◆ nextReplanFrame

int ScoutingProbe::nextReplanFrame = 0
private

Definition at line 92 of file ScoutingProbe.h.

◆ nextTarget

std::size_t ScoutingProbe::nextTarget = 0
private

Definition at line 69 of file ScoutingProbe.h.

◆ orbitIdx

std::size_t ScoutingProbe::orbitIdx = 0
private

Definition at line 109 of file ScoutingProbe.h.

◆ orbitWaypoints

std::vector<BWAPI::Position> ScoutingProbe::orbitWaypoints
private

Definition at line 107 of file ScoutingProbe.h.

◆ plannedPath

std::vector<BWAPI::Position> ScoutingProbe::plannedPath
private

Definition at line 108 of file ScoutingProbe.h.

◆ scout

BWAPI::Unit ScoutingProbe::scout = nullptr
private

Definition at line 60 of file ScoutingProbe.h.

◆ sidestepAttempts

int ScoutingProbe::sidestepAttempts = 0
private

Definition at line 91 of file ScoutingProbe.h.

◆ sidestepDir

int ScoutingProbe::sidestepDir = 1
private

Definition at line 90 of file ScoutingProbe.h.

◆ startTargets

std::vector<BWAPI::TilePosition> ScoutingProbe::startTargets
private

Definition at line 68 of file ScoutingProbe.h.

◆ state

State ScoutingProbe::state = State::Done
private

Definition at line 70 of file ScoutingProbe.h.

◆ stuckFrames

int ScoutingProbe::stuckFrames = 0
private

Definition at line 103 of file ScoutingProbe.h.

◆ targetGeyser

BWAPI::Unit ScoutingProbe::targetGeyser = nullptr
private

Definition at line 74 of file ScoutingProbe.h.


The documentation for this class was generated from the following files: