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

ScoutingObserver Controls scouting behavior for a Protoss Observer. Maintains vision over key enemy locations, avoids detection threats, and monitors expansions using a slot-based system. Observers either hold fixed positions near the enemy base or dynamically patrol expansion locations to provide map awareness. More...

#include <ScoutingObserver.h>

Public Member Functions

 ScoutingObserver (ProtoBotCommander *cmd=nullptr, ScoutingManager *mgr=nullptr)
void onStart ()
 Initializes the observer scouting state.
void onFrame ()
 Main update loop executed every frame.
void onUnitDestroy (BWAPI::Unit u)
 Handles observer destruction.
void assign (BWAPI::Unit u)
 Assigns a unit as the scouting observer.
void setEnemyMain (const BWAPI::TilePosition &tp)
 Sets the enemy main base and recomputes observation posts.
void setObserverSlot (int idx)
void drawDebug () const
 Draws debug information for the observer on the map.

Private Types

enum class  State {
  Idle , MoveToPost , Hold , AvoidDetection ,
  Done
}

Private Member Functions

void computePosts ()
 Computes observation posts around the enemy base.
BWAPI::Position postTarget () const
 Returns the current target position for this observer.
void issueMove (const BWAPI::Position &p, bool force=false, int reissueDist=64)
 Issues a movement command to the observer.
bool detectorThreat (BWAPI::Position &avoidTo) const
 Detects nearby enemy detectors and computes a safe escape position.
bool isUnsafe (const BWAPI::Position &p) const
 Determines if a position is unsafe based on threat data.
BWAPI::Position pickDetourToward (const BWAPI::Position &target) const
 Selects a safe detour toward a target position.
bool haveVisionAt (const BWAPI::Position &p, int radiusPx) const
 Checks whether the bot currently has vision at a position.
void rebuildSlot3Checkpoints ()
 Rebuilds roaming checkpoints for slot 3 observers.

Static Private Member Functions

static int detectionRadiusFor (const BWAPI::UnitType &t)
static BWAPI::Position clampToMapPx (const BWAPI::Position &p, int margin=16)
static double groundPathLengthPx (const BWAPI::Position &from, const BWAPI::Position &to)

Private Attributes

ProtoBotCommandercommanderRef = nullptr
ScoutingManagermanager = nullptr
BWAPI::Unit observer { nullptr }
State state = State::Idle
std::vector< BWAPI::Position > posts
int slotIndex = -1
std::optional< BWAPI::TilePosition > enemyMainTile
BWAPI::Position enemyMainPos = BWAPI::Positions::Invalid
int lastMoveFrame = 0
int lastThreatFrame = -10000
BWAPI::Position slot3Home = BWAPI::Positions::Invalid
bool slot3HomeSet = false
bool slot3ReturningHome = false
std::vector< BWAPI::Position > slot3Checkpoints
int slot3NextRebuildFrame = 0
bool slot3NeedsRebuild = false
int slot3NextIdx = 0
int slot3NextMoveFrame = 0
BWAPI::Position slot3CurTarget = BWAPI::Positions::Invalid

Static Private Attributes

static constexpr int kMoveCooldown = 6
static constexpr int kSafeFramesToReturn = 48
static constexpr int kEdgeMarginPx = 8
static constexpr int kHoldReissueDist = 16
static constexpr int kAirThreatThreshold = 40
static constexpr int kDetectReactBufferPx = 48
static constexpr int kSlot3RebuildEveryFrames = 24 * 6
static constexpr int kSlot3MinBetweenMoves = 24
static constexpr int kSlot3ArriveDist = 96

Detailed Description

ScoutingObserver Controls scouting behavior for a Protoss Observer. Maintains vision over key enemy locations, avoids detection threats, and monitors expansions using a slot-based system. Observers either hold fixed positions near the enemy base or dynamically patrol expansion locations to provide map awareness.

Definition at line 22 of file ScoutingObserver.h.

Member Enumeration Documentation

◆ State

enum class ScoutingObserver::State
strongprivate

Definition at line 42 of file ScoutingObserver.h.

42{ Idle, MoveToPost, Hold, AvoidDetection, Done };

Constructor & Destructor Documentation

◆ ScoutingObserver()

ScoutingObserver::ScoutingObserver ( ProtoBotCommander * cmd = nullptr,
ScoutingManager * mgr = nullptr )
inlineexplicit

Definition at line 25 of file ScoutingObserver.h.

26 : commanderRef(cmd), manager(mgr) {
27 }

Member Function Documentation

◆ assign()

void ScoutingObserver::assign ( BWAPI::Unit u)

Assigns a unit as the scouting observer.

Initializes behavior based on assigned slot:

  • Slot 3: roaming expansion checker
  • Other slots: fixed observation posts
Parameters
uObserver unit

Definition at line 68 of file ScoutingObserver.cpp.

68 {
69 if (!u || !u->exists()) return;
70
71 observer = u;
72
73 if (enemyMainPos.isValid() && posts.empty())
74 {
76 }
77
78 // Slot 3: home is wherever we are right now
79 if (slotIndex == 3 && !slot3HomeSet)
80 {
81 slot3Home = observer->getPosition();
82 slot3Home = clampToMapPx(slot3Home, 12);
83 slot3HomeSet = true;
84 slot3NextRebuildFrame = 0;
85 slot3NextIdx = 0;
86 slot3CurTarget = BWAPI::Positions::Invalid;
87 }
88
89 if (slotIndex == 3)
90 {
91 state = State::Hold;
92 }
93 else
94 {
95 state = (slotIndex >= 0 && !posts.empty()) ? State::MoveToPost : State::Idle;
96 }
97}
void computePosts()
Computes observation posts around the enemy base.

◆ clampToMapPx()

BWAPI::Position ScoutingObserver::clampToMapPx ( const BWAPI::Position & p,
int margin = 16 )
staticprivate

Definition at line 462 of file ScoutingObserver.cpp.

463{
464 const int x = std::max(margin, std::min(p.x, mapWpx() - 1 - margin));
465 const int y = std::max(margin, std::min(p.y, mapHpx() - 1 - margin));
466 return BWAPI::Position(x, y);
467}

◆ computePosts()

void ScoutingObserver::computePosts ( )
private

Computes observation posts around the enemy base.

Selects:

  • Enemy main
  • Natural expansion
  • Additional nearby expansions

Ensures posts are reachable and valid positions.

Definition at line 313 of file ScoutingObserver.cpp.

314{
315 posts.clear();
316 if (!enemyMainPos.isValid() || !enemyMainTile.has_value()) return;
317
318 const TilePosition enemyMain = *enemyMainTile;
319 const Position enemyMainCenter = Position(enemyMain) + Position(16, 16);
320 const auto* mainArea = BWEM::Map::Instance().GetArea(enemyMain);
321
322 // 0) Over enemy main, slightly above center
323 Position mainCenter = enemyMainCenter + Position(0, -32);
324 posts.push_back(clampToMapPx(mainCenter, 16));
325
326 struct Candidate { const BWEM::Base* base; double gdist; Position pos; };
327 std::vector<Candidate> cands;
328
329 // Collect non-starting, reachable bases NOT in the main's area
330 for (const BWEM::Area& area : BWEM::Map::Instance().Areas())
331 {
332 for (const BWEM::Base& base : area.Bases()) {
333 if (base.Starting()) continue;
334
335 const auto* bArea = BWEM::Map::Instance().GetArea(base.Location());
336 if (bArea == mainArea) continue;
337
338 // Prefer base center if available; else location + (16,16)
339 Position basePos = base.Center(); // BWEM::Base::Center is BWAPI::Position
340 if (!basePos.isValid()) basePos = Position(base.Location()) + Position(16, 16);
341
342 const auto& path = BWEM::Map::Instance().GetPath(enemyMainCenter, basePos, nullptr);
343 if (path.empty()) continue; // unreachable by ground
344
345 double g = groundPathLengthPx(enemyMainCenter, basePos);
346 cands.push_back({ &base, g, basePos });
347 }
348 }
349
350 std::sort(cands.begin(), cands.end(),
351 [](const Candidate& a, const Candidate& b) { return a.gdist < b.gdist; });
352
353 // 1) natural (closest), 2) second closest expo, 3) third closest expo
354 if (!cands.empty()) posts.push_back(cands[0].pos);
355 if (cands.size() >= 2) posts.push_back(cands[1].pos);
356 if (cands.size() >= 3) posts.push_back(cands[2].pos);
357
358 // Nudge every post slightly "above" and clamp
359 for (auto& p : posts) p = clampToMapPx(p + Position(0, -24), 12);
360
361 // Ensure exactly 4 posts (pad with main if needed)
362 while (posts.size() < 4) posts.push_back(mainCenter);
363 if (posts.size() > 4) posts.resize(4);
364}

◆ detectionRadiusFor()

int ScoutingObserver::detectionRadiusFor ( const BWAPI::UnitType & t)
staticprivate

Definition at line 449 of file ScoutingObserver.cpp.

450{
451 if (t == BWAPI::UnitTypes::Terran_Missile_Turret) return 224;
452 if (t == BWAPI::UnitTypes::Protoss_Photon_Cannon) return 224;
453 if (t == BWAPI::UnitTypes::Zerg_Spore_Colony) return 224;
454 if (t == BWAPI::UnitTypes::Terran_Science_Vessel) return 224;
455 if (t == BWAPI::UnitTypes::Protoss_Observer) return 224; // enemy obs can reveal you to army
456 int r = t.sightRange();
457 if (r <= 0) r = 224;
458 // a small bias outward
459 return r;
460}

◆ detectorThreat()

bool ScoutingObserver::detectorThreat ( BWAPI::Position & avoidTo) const
private

Detects nearby enemy detectors and computes a safe escape position.

Parameters
avoidToOutput position to move away from detection
Returns
True if a detection threat exists

Definition at line 407 of file ScoutingObserver.cpp.

408{
409 if (!observer || !observer->exists()) return false;
410 const Position me = observer->getPosition();
411
412 int bestOver = INT_MIN;
413 Position bestPos = Positions::Invalid;
414
415 for (auto e : Broodwar->enemy()->getUnits())
416 {
417 if (!e || !e->exists()) continue;
418 const auto t = e->getType();
419 if (!t.isDetector()) continue;
420
421 const int detR = detectionRadiusFor(t); // detector radius in px
422 const int d = e->getDistance(me);
423 const int over = detR - d; // >0 means we are inside detection
424
425 // If we're inside (or nearly at the edge), compute a point just outside
426 if (over >= -kDetectReactBufferPx)
427 {
428 // step along vector from detector->me to just outside radius+margin
429 const Position c = e->getPosition();
430 int dx = me.x - c.x, dy = me.y - c.y;
431 double len = std::max(1.0, std::sqrt(double(dx * dx + dy * dy)));
432 const int want = detR + kEdgeMarginPx + kDetectReactBufferPx;
433 Position out(c.x + int(dx / len * want), c.y + int(dy / len * want));
434 out = clampToMapPx(out, 12);
435
436 // pick the “most violating” detector (largest over) to react to
437 if (over > bestOver)
438 {
439 bestOver = over;
440 bestPos = out;
441 }
442 }
443 }
444
445 if (bestPos.isValid()) { avoidTo = bestPos; return true; }
446 return false;
447}

◆ drawDebug()

void ScoutingObserver::drawDebug ( ) const

Draws debug information for the observer on the map.

Displays:

  • Current state
  • Assigned slot
  • Target positions
  • Observation posts and checkpoints

Definition at line 799 of file ScoutingObserver.cpp.

800{
801 if (!observer || !observer->exists())
802 {
803 return;
804 }
805
806 BWAPI::Position p = observer->getPosition();
807
808 BWAPI::Broodwar->drawCircleMap(p, 20, BWAPI::Colors::Cyan, false);
809 BWAPI::Broodwar->drawTextMap(p.x - 34, p.y - 42, "\x0fObserver Scout");
810 BWAPI::Broodwar->drawTextMap(p.x - 34, p.y - 30, "\x11Slot: %d", slotIndex);
811
812 const char* stateName = "Unknown";
813
814 switch (state)
815 {
816 case State::Idle:
817 stateName = "Idle";
818 break;
819 case State::MoveToPost:
820 stateName = "MoveToPost";
821 break;
822 case State::Hold:
823 stateName = "Hold";
824 break;
825 case State::AvoidDetection:
826 stateName = "AvoidDetection";
827 break;
828 case State::Done:
829 stateName = "Done";
830 break;
831 }
832
833 BWAPI::Broodwar->drawTextMap(p.x - 34, p.y - 18, "\x11State: %s", stateName);
834
835 BWAPI::Position tgt = BWAPI::Positions::Invalid;
836
837 if (slotIndex == 3)
838 {
839 if (slot3CurTarget.isValid())
840 {
841 tgt = slot3CurTarget;
842 }
843 else if (slot3HomeSet)
844 {
845 tgt = slot3Home;
846 }
847 }
848 else
849 {
850 tgt = postTarget();
851 }
852
853 if (tgt.isValid())
854 {
855 BWAPI::Broodwar->drawLineMap(p, tgt, BWAPI::Colors::Green);
856 BWAPI::Broodwar->drawCircleMap(tgt, 10, BWAPI::Colors::Green, false);
857 }
858
859 if (enemyMainPos.isValid())
860 {
861 BWAPI::Broodwar->drawCircleMap(enemyMainPos, 24, BWAPI::Colors::Red, false);
862 BWAPI::Broodwar->drawTextMap(enemyMainPos.x - 20, enemyMainPos.y - 18, "\x08Enemy Main");
863 }
864
865 for (int i = 0; i < (int)posts.size(); ++i)
866 {
867 BWAPI::Color c = BWAPI::Colors::White;
868
869 if (i == 0)
870 {
871 c = BWAPI::Colors::Yellow;
872 }
873 else if (i == 1)
874 {
875 c = BWAPI::Colors::Cyan;
876 }
877 else if (i == 2)
878 {
879 c = BWAPI::Colors::Purple;
880 }
881 else if (i == 3)
882 {
883 c = BWAPI::Colors::Orange;
884 }
885
886 BWAPI::Broodwar->drawCircleMap(posts[i], 8, c, true);
887 BWAPI::Broodwar->drawTextMap(posts[i].x + 6, posts[i].y - 6, "#%d", i);
888 }
889
890 if (slotIndex == 3)
891 {
892 for (int i = 0; i < (int)slot3Checkpoints.size(); ++i)
893 {
894 BWAPI::Broodwar->drawCircleMap(slot3Checkpoints[i], 6, BWAPI::Colors::Orange, false);
895
896 if (i + 1 < (int)slot3Checkpoints.size())
897 {
898 BWAPI::Broodwar->drawLineMap(slot3Checkpoints[i], slot3Checkpoints[i + 1], BWAPI::Colors::Orange);
899 }
900 }
901
902 if (slot3HomeSet)
903 {
904 BWAPI::Broodwar->drawCircleMap(slot3Home, 10, BWAPI::Colors::Blue, false);
905 BWAPI::Broodwar->drawTextMap(slot3Home.x + 6, slot3Home.y - 6, "\x10Obs Home");
906 }
907 }
908}
BWAPI::Position postTarget() const
Returns the current target position for this observer.

◆ groundPathLengthPx()

double ScoutingObserver::groundPathLengthPx ( const BWAPI::Position & from,
const BWAPI::Position & to )
staticprivate

Definition at line 469 of file ScoutingObserver.cpp.

470{
471 auto& m = BWEM::Map::Instance();
472 const auto& path = m.GetPath(from, to, nullptr);
473 if (path.empty()) return from.getDistance(to);
474 double sum = 0.0;
475 BWAPI::Position prev = from;
476 for (const BWEM::ChokePoint* cp : path)
477 {
478 if (!cp) continue;
479 BWAPI::Position c(cp->Center());
480 sum += prev.getDistance(c);
481 prev = c;
482 }
483 sum += prev.getDistance(to);
484 return sum;
485}

◆ haveVisionAt()

bool ScoutingObserver::haveVisionAt ( const BWAPI::Position & p,
int radiusPx ) const
private

Checks whether the bot currently has vision at a position.

Parameters
pPosition to check
radiusPxSearch radius
Returns
True if vision is available

Definition at line 617 of file ScoutingObserver.cpp.

618{
619 if (!p.isValid()) return true;
620
621 // If tile is visible this frame, we definitely have vision.
622 if (BWAPI::Broodwar->isVisible(BWAPI::TilePosition(p)))
623 {
624 return true;
625 }
626
627 // Otherwise, check if any of our units are physically near it.
628 for (auto u : BWAPI::Broodwar->self()->getUnits())
629 {
630 if (!u || !u->exists()) continue;
631 if (u->getDistance(p) <= radiusPx) return true;
632 }
633
634 return false;
635}

◆ issueMove()

void ScoutingObserver::issueMove ( const BWAPI::Position & p,
bool force = false,
int reissueDist = 64 )
private

Issues a movement command to the observer.

Includes throttling to prevent excessive command spam.

Parameters
pTarget position
forceForce movement regardless of cooldown
reissueDistMinimum distance to reissue move

Definition at line 388 of file ScoutingObserver.cpp.

389{
390 if (!observer || !observer->exists() || !p.isValid()) return;
391 const int now = Broodwar->getFrameCount();
392 if (!force && now - lastMoveFrame < kMoveCooldown) return;
393 if (force || observer->getDistance(p) > reissueDist || !observer->isMoving())
394 {
395 observer->move(p);
396 lastMoveFrame = now;
397 }
398 //Broodwar->drawLineMap(observer->getPosition(), p, Colors::White);
399}

◆ isUnsafe()

bool ScoutingObserver::isUnsafe ( const BWAPI::Position & p) const
private

Determines if a position is unsafe based on threat data.

Parameters
pPosition to evaluate
Returns
True if unsafe

Definition at line 493 of file ScoutingObserver.cpp.

494{
495 if (!commanderRef) return false;
496
497 const auto threat = commanderRef->queryThreatAt(p);
498
499 if (threat.detectorThreat > 0)
500 {
501 return true;
502 }
503
504 if (threat.airThreat > kAirThreatThreshold)
505 {
506 return true;
507 }
508
509 return false;
510}

◆ onFrame()

void ScoutingObserver::onFrame ( )

Main update loop executed every frame.

Handles:

  • Movement toward observation posts
  • Detection avoidance
  • Slot-based scouting behavior

Slot behavior:

  • Slots 0–2: move to and hold posts
  • Slot 3: roam and check expansions

Definition at line 141 of file ScoutingObserver.cpp.

141 {
142 if (!observer || !observer->exists() || state == State::Done) return;
143
144 if (!enemyMainPos.isValid())
145 {
146 state = State::Idle;
147 return;
148 }
149
150 if (posts.empty()) computePosts();
151 if (slotIndex < 0) return;
152
153 // Detection avoidance (your existing logic)
154 BWAPI::Position safePt;
155 if (detectorThreat(safePt))
156 {
157 lastThreatFrame = Broodwar->getFrameCount();
158 issueMove(safePt, /*force*/true, /*reissue*/32);
159 state = State::AvoidDetection;
160 return;
161 }
162
163 const int now = Broodwar->getFrameCount();
164
165 // --------------------------
166 // Slot 3: roaming expansion checker
167 // --------------------------
168 if (slotIndex == 3 && slot3HomeSet)
169 {
170 if (slot3Checkpoints.empty())
171 {
173 }
174 // If we have checkpoints, go visit them
175 // If we have checkpoints, go visit them
176 if (!slot3Checkpoints.empty())
177 {
178 // If we’re returning home after a sweep, ignore checkpoints until we get home.
179 if (slot3ReturningHome)
180 {
181 if (observer->getDistance(slot3Home) <= kSlot3ArriveDist)
182 {
183 slot3ReturningHome = false;
184
185 if (slot3NeedsRebuild)
186 {
188 slot3NeedsRebuild = false;
189 }
190 }
191 else if (now >= slot3NextMoveFrame)
192 {
193 issueMove(slot3Home, false, kHoldReissueDist);
194 slot3NextMoveFrame = now + kSlot3MinBetweenMoves;
195 }
196
197 return;
198 }
199
200 // Pick/advance target
201 if (!slot3CurTarget.isValid() || observer->getDistance(slot3CurTarget) <= kSlot3ArriveDist)
202 {
203 // Finished the list: rebuild ONCE and keep patrolling from current position.
204 // Only go home if rebuild produces no nodes.
205 if (slot3NextIdx >= (int)slot3Checkpoints.size())
206 {
208
209 slot3NextIdx = 0;
210 slot3CurTarget = BWAPI::Positions::Invalid;
211
212 if (slot3Checkpoints.empty())
213 {
214 slot3ReturningHome = true; // only now do we go home
215 }
216
217 return;
218 }
219 slot3CurTarget = slot3Checkpoints[slot3NextIdx++];
220 }
221
222 // Move toward current target (throttled)
223 if (slot3CurTarget.isValid() && now >= slot3NextMoveFrame)
224 {
225 const BWAPI::Position step = pickDetourToward(slot3CurTarget);
226 issueMove(step, false, 64);
227 slot3NextMoveFrame = now + kSlot3MinBetweenMoves;
228 }
229
230 return;
231 }
232
233 // No checkpoints: just hold home
234 if (now - lastMoveFrame > 48)
235 {
236 issueMove(slot3Home, /*force*/true, kHoldReissueDist);
237 }
238
239 return;
240 }
241
242 // --------------------------
243 // Normal slots 0-2 behavior (your existing switch)
244 // --------------------------
245 if (slotIndex >= (int)posts.size()) return;
246
247 switch (state) {
248 case State::Idle:
249 state = State::MoveToPost;
250 break;
251
252 case State::MoveToPost:
253 {
254 const BWAPI::Position tgt = postTarget();
255 const BWAPI::Position step = pickDetourToward(tgt);
256
257 issueMove(step, false, 64);
258
259 if (observer->getDistance(tgt) <= 80)
260 {
261 state = State::Hold;
262 }
263 break;
264 }
265
266 case State::Hold: {
267 const Position tgt = postTarget();
268 if (!observer->isMoving() && !observer->isAttacking())
269 {
270 // Hold is just "don't drift"; observers can't hold-position command,
271 // but reissue a move to target occasionally to keep tight.
272 if (Broodwar->getFrameCount() - lastMoveFrame > 48)
273 issueMove(tgt, /*force*/true, kHoldReissueDist);
274 }
275 break;
276 }
277
278 case State::AvoidDetection:
279 {
280 // Once calm enough, return to post
281 if (Broodwar->getFrameCount() - lastThreatFrame >= kSafeFramesToReturn)
282 {
283 state = State::MoveToPost;
284 }
285 break;
286 }
287 default: break;
288 }
289
290 // tiny debug
291 /*
292 Broodwar->drawTextMap(observer->getPosition(), "\x11OBS slot %d", slotIndex);
293 for (int i = 0; i < (int)posts.size(); ++i) {
294 auto c = (i == 0) ? Colors::Yellow : (i == 1 ? Colors::Cyan : (i == 2 ? Colors::Purple : Colors::Orange));
295 Broodwar->drawCircleMap(posts[i], 8, c, true);
296 Broodwar->drawTextMap(posts[i], "#%d", i);
297 }*/
298}
void issueMove(const BWAPI::Position &p, bool force=false, int reissueDist=64)
Issues a movement command to the observer.
BWAPI::Position pickDetourToward(const BWAPI::Position &target) const
Selects a safe detour toward a target position.
void rebuildSlot3Checkpoints()
Rebuilds roaming checkpoints for slot 3 observers.
bool detectorThreat(BWAPI::Position &avoidTo) const
Detects nearby enemy detectors and computes a safe escape position.

◆ onStart()

void ScoutingObserver::onStart ( )

Initializes the observer scouting state.

Resets movement tracking, threat tracking, and clears any existing observation posts.

Definition at line 52 of file ScoutingObserver.cpp.

52 {
53 lastMoveFrame = 0;
54 lastThreatFrame = -10000;
55 posts.clear();
56 state = State::Idle;
57}

◆ onUnitDestroy()

void ScoutingObserver::onUnitDestroy ( BWAPI::Unit u)

Handles observer destruction.

Clears the assigned unit and marks behavior as complete.

Parameters
uDestroyed unit

Definition at line 121 of file ScoutingObserver.cpp.

121 {
122 if (observer && u == observer) {
123 observer = nullptr;
124 state = State::Done;
125 }
126}

◆ pickDetourToward()

BWAPI::Position ScoutingObserver::pickDetourToward ( const BWAPI::Position & target) const
private

Selects a safe detour toward a target position.

Attempts alternative paths to avoid threats while still progressing toward the goal.

Parameters
targetTarget position
Returns
Best safe position to move toward

Definition at line 521 of file ScoutingObserver.cpp.

522{
523 if (!observer || !observer->exists())
524 {
525 return BWAPI::Positions::Invalid;
526 }
527
528 const BWAPI::Position me = observer->getPosition();
529
530 // If target itself is safe, just go directly.
531 if (!isUnsafe(target))
532 {
533 return target;
534 }
535
536 // Try a few detours around our current heading.
537 const BWAPI::Position toTgt = BWAPI::Position(target.x - me.x, target.y - me.y);
538
539 // If we're basically on top of target, no detour helps.
540 if (std::abs(toTgt.x) + std::abs(toTgt.y) < 8)
541 {
542 return target;
543 }
544
545 // Candidate radii (pixels)
546 const int radii[] = { 96, 160, 224, 288 };
547
548 // Candidate angles around heading (radians)
549 const double angles[] =
550 {
551 0.0,
552 0.35, -0.35,
553 0.70, -0.70,
554 1.05, -1.05,
555 1.40, -1.40
556 };
557
558 int bestScore = INT_MIN;
559 BWAPI::Position bestPos = BWAPI::Positions::Invalid;
560
561 // Normalize direction roughly (avoid expensive normalize)
562 BWAPI::Position dir = toTgt;
563 const int len = std::max(1, int(std::sqrt(double(dir.x * dir.x + dir.y * dir.y))));
564 dir = BWAPI::Position(int(double(dir.x) / len * 64.0), int(double(dir.y) / len * 64.0)); // scaled
565
566 for (int r : radii)
567 {
568 for (double a : angles)
569 {
570 BWAPI::Position cand = me + rotatePoint(BWAPI::Position(int(dir.x * (double(r) / 64.0)), int(dir.y * (double(r) / 64.0))), a);
571 cand = clampToMapPx(cand, 12);
572
573 // Must be walkable-ish (observers fly, but avoid silly invalid positions)
574 if (!cand.isValid())
575 {
576 continue;
577 }
578
579 // Skip unsafe candidates.
580 if (isUnsafe(cand))
581 {
582 continue;
583 }
584
585 // Score: prefer getting closer to target, with a mild bias for larger steps.
586 const int distNow = me.getApproxDistance(target);
587 const int distCand = cand.getApproxDistance(target);
588 const int progress = distNow - distCand;
589
590 const int score = progress + (r / 8);
591
592 if (score > bestScore)
593 {
594 bestScore = score;
595 bestPos = cand;
596 }
597 }
598 }
599
600 // If nothing safe found, just return the safest “least bad” direction (fallback).
601 if (!bestPos.isValid())
602 {
603 return target;
604 }
605
606 return bestPos;
607}
bool isUnsafe(const BWAPI::Position &p) const
Determines if a position is unsafe based on threat data.

◆ postTarget()

BWAPI::Position ScoutingObserver::postTarget ( ) const
private

Returns the current target position for this observer.

Returns
Target position based on assigned slot

Definition at line 372 of file ScoutingObserver.cpp.

373{
374 if (slotIndex >= 0 && slotIndex < (int)posts.size())
375 return posts[slotIndex];
376 return enemyMainPos;
377}

◆ rebuildSlot3Checkpoints()

void ScoutingObserver::rebuildSlot3Checkpoints ( )
private

Rebuilds roaming checkpoints for slot 3 observers.

Selects expansion locations to patrol while:

  • Avoiding friendly bases
  • Avoiding overlap with other observers
  • Avoiding unsafe areas

Definition at line 676 of file ScoutingObserver.cpp.

677{
678 slot3Checkpoints.clear();
679
680 if (!observer || !observer->exists())
681 {
682 slot3NextIdx = 0;
683 slot3CurTarget = BWAPI::Positions::Invalid;
684 return;
685 }
686
687 // Tuning knobs
688 const int kNexusExcludeRadius = 10 * 32; // 10 tiles
689 const int kObserverCoverRadius = 14 * 32; // 14 tiles
690 const int kMaxPatrolPoints = 16; // cap to keep it stable
691 const int kMinPatrolPoints = 2; // ensure at least 2 so its not just freaking out
692
693 std::vector<BWAPI::Position> candidates;
694 candidates.reserve(32);
695
696 for (const BWEM::Area& area : BWEM::Map::Instance().Areas())
697 {
698 for (const BWEM::Base& base : area.Bases())
699 {
700 BWAPI::Position pos = base.Center();
701 if (!pos.isValid())
702 {
703 pos = BWAPI::Position(base.Location()) + BWAPI::Position(16, 16);
704 }
705
706 pos = clampToMapPx(pos, 12);
707
708 if (hasOurNexusNear(pos, kNexusExcludeRadius))
709 {
710 continue;
711 }
712
713 if (isOtherObserverCovering(pos, observer, kObserverCoverRadius))
714 {
715 continue;
716 }
717
718 // This is your “detectors matter while moving, not in cost” rule:
719 // We only use isUnsafe as a hard filter (optional).
720 // If you want it NOT to be a hard filter, remove this and rely on pickDetourToward().
721 if (isUnsafe(pos))
722 {
723 continue;
724 }
725
726 candidates.push_back(pos);
727 }
728 }
729
730 if (candidates.empty())
731 {
732 slot3NextIdx = 0;
733 slot3CurTarget = BWAPI::Positions::Invalid;
734 return;
735 }
736
737 // Nearest-neighbor greedy ordering (straight-line distance).
738 BWAPI::Position cur = observer->getPosition();
739
740 while (!candidates.empty() && (int)slot3Checkpoints.size() < kMaxPatrolPoints)
741 {
742 int bestIdx = -1;
743 int bestD = INT_MAX;
744
745 for (int i = 0; i < (int)candidates.size(); ++i)
746 {
747 const int d = cur.getApproxDistance(candidates[i]);
748 if (d < bestD)
749 {
750 bestD = d;
751 bestIdx = i;
752 }
753 }
754
755 if (bestIdx < 0)
756 {
757 break;
758 }
759
760 slot3Checkpoints.push_back(candidates[bestIdx]);
761 cur = candidates[bestIdx];
762 candidates.erase(candidates.begin() + bestIdx);
763 }
764
765 // If we ended up with only 1 but we had more candidates, force-add one more nearest
766 // so it doesn’t look “stuck”.
767 if ((int)slot3Checkpoints.size() < kMinPatrolPoints && !candidates.empty())
768 {
769 int bestIdx = 0;
770 int bestD = INT_MAX;
771
772 for (int i = 0; i < (int)candidates.size(); ++i)
773 {
774 const int d = cur.getApproxDistance(candidates[i]);
775 if (d < bestD)
776 {
777 bestD = d;
778 bestIdx = i;
779 }
780 }
781
782 slot3Checkpoints.push_back(candidates[bestIdx]);
783 }
784
785 slot3NextIdx = 0;
786 slot3CurTarget = BWAPI::Positions::Invalid;
787}

◆ setEnemyMain()

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

Sets the enemy main base and recomputes observation posts.

Transitions the observer from Idle to active scouting if a valid slot is assigned.

Parameters
tpEnemy main tile position

Definition at line 107 of file ScoutingObserver.cpp.

107 {
108 enemyMainTile = tp;
109 enemyMainPos = Position(tp);
110 computePosts();
111 if (state == State::Idle && slotIndex >= 0) state = State::MoveToPost;
112}

◆ setObserverSlot()

void ScoutingObserver::setObserverSlot ( int idx)
inline

Definition at line 37 of file ScoutingObserver.h.

37{ slotIndex = idx; }

Member Data Documentation

◆ commanderRef

ProtoBotCommander* ScoutingObserver::commanderRef = nullptr
private

Definition at line 44 of file ScoutingObserver.h.

◆ enemyMainPos

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

Definition at line 55 of file ScoutingObserver.h.

◆ enemyMainTile

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

Definition at line 54 of file ScoutingObserver.h.

◆ kAirThreatThreshold

int ScoutingObserver::kAirThreatThreshold = 40
staticconstexprprivate

Definition at line 65 of file ScoutingObserver.h.

◆ kDetectReactBufferPx

int ScoutingObserver::kDetectReactBufferPx = 48
staticconstexprprivate

Definition at line 66 of file ScoutingObserver.h.

◆ kEdgeMarginPx

int ScoutingObserver::kEdgeMarginPx = 8
staticconstexprprivate

Definition at line 63 of file ScoutingObserver.h.

◆ kHoldReissueDist

int ScoutingObserver::kHoldReissueDist = 16
staticconstexprprivate

Definition at line 64 of file ScoutingObserver.h.

◆ kMoveCooldown

int ScoutingObserver::kMoveCooldown = 6
staticconstexprprivate

Definition at line 61 of file ScoutingObserver.h.

◆ kSafeFramesToReturn

int ScoutingObserver::kSafeFramesToReturn = 48
staticconstexprprivate

Definition at line 62 of file ScoutingObserver.h.

◆ kSlot3ArriveDist

int ScoutingObserver::kSlot3ArriveDist = 96
staticconstexprprivate

Definition at line 70 of file ScoutingObserver.h.

◆ kSlot3MinBetweenMoves

int ScoutingObserver::kSlot3MinBetweenMoves = 24
staticconstexprprivate

Definition at line 69 of file ScoutingObserver.h.

◆ kSlot3RebuildEveryFrames

int ScoutingObserver::kSlot3RebuildEveryFrames = 24 * 6
staticconstexprprivate

Definition at line 68 of file ScoutingObserver.h.

◆ lastMoveFrame

int ScoutingObserver::lastMoveFrame = 0
private

Definition at line 57 of file ScoutingObserver.h.

◆ lastThreatFrame

int ScoutingObserver::lastThreatFrame = -10000
private

Definition at line 58 of file ScoutingObserver.h.

◆ manager

ScoutingManager* ScoutingObserver::manager = nullptr
private

Definition at line 45 of file ScoutingObserver.h.

◆ observer

BWAPI::Unit ScoutingObserver::observer { nullptr }
private

Definition at line 47 of file ScoutingObserver.h.

47{ nullptr };

◆ posts

std::vector<BWAPI::Position> ScoutingObserver::posts
private

Definition at line 51 of file ScoutingObserver.h.

◆ slot3Checkpoints

std::vector<BWAPI::Position> ScoutingObserver::slot3Checkpoints
private

Definition at line 76 of file ScoutingObserver.h.

◆ slot3CurTarget

BWAPI::Position ScoutingObserver::slot3CurTarget = BWAPI::Positions::Invalid
private

Definition at line 81 of file ScoutingObserver.h.

◆ slot3Home

BWAPI::Position ScoutingObserver::slot3Home = BWAPI::Positions::Invalid
private

Definition at line 73 of file ScoutingObserver.h.

◆ slot3HomeSet

bool ScoutingObserver::slot3HomeSet = false
private

Definition at line 74 of file ScoutingObserver.h.

◆ slot3NeedsRebuild

bool ScoutingObserver::slot3NeedsRebuild = false
private

Definition at line 78 of file ScoutingObserver.h.

◆ slot3NextIdx

int ScoutingObserver::slot3NextIdx = 0
private

Definition at line 79 of file ScoutingObserver.h.

◆ slot3NextMoveFrame

int ScoutingObserver::slot3NextMoveFrame = 0
private

Definition at line 80 of file ScoutingObserver.h.

◆ slot3NextRebuildFrame

int ScoutingObserver::slot3NextRebuildFrame = 0
private

Definition at line 77 of file ScoutingObserver.h.

◆ slot3ReturningHome

bool ScoutingObserver::slot3ReturningHome = false
private

Definition at line 75 of file ScoutingObserver.h.

◆ slotIndex

int ScoutingObserver::slotIndex = -1
private

Definition at line 52 of file ScoutingObserver.h.

◆ state

State ScoutingObserver::state = State::Idle
private

Definition at line 48 of file ScoutingObserver.h.


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