Skynet - avoiding splash damage
Skynet tries to avoid some area-of-effect spells and splash damage from some attacks. This is the fanciest skill that I’m writing up.
You can only avoid stuff that you can see coming. Skynet tracks threats in class AOEThreatTracker (where AOE stands for Area Of Effect), which creates and stores AOEThreat objects. Here is the key code from AOEThreatTracker::update(), which is called once per frame from the main onFrame() in Skynet.cpp:
for each(Unit unit in UnitTracker::Instance().selectAllEnemy())
{
const BWAPI::UnitType &type = unit->getType();
if((type == BWAPI::UnitTypes::Protoss_Scarab || type == BWAPI::UnitTypes::Terran_Vulture_Spider_Mine) && mUnitThreats.count(unit) == 0)
{
AOEThreat newThreat = AOEThreat(new AOEThreatClass(unit));
mAllThreats.insert(newThreat);
mUnitThreats[unit] = newThreat;
}
}
for each(BWAPI::Bullet* bullet in BWAPI::Broodwar->getBullets())
{
const BWAPI::BulletType &type = bullet->getType();
if((type == BWAPI::BulletTypes::Psionic_Storm || type == BWAPI::BulletTypes::EMP_Missile) && mBulletThreats.count(bullet) == 0)
{
AOEThreat newThreat = AOEThreat(new AOEThreatClass(bullet));
mAllThreats.insert(newThreat);
mBulletThreats[bullet] = newThreat;
}
}
Skynet recognizes enemy reaver scarabs and spider mines as splash damage threats. (A bot that might lay mines of its own should also recognize its own spider mines as threats. Skynet does not use mind control.) It also recognizes psionic storm and EMP as area effect spells to try to dodge.
What does it do with the information it’s tracking? Here’s the public interface from AOEThreatTracker.h:
void update(); AOEThreat getClosestGroundThreat(const Position &pos) const; AOEThreat getClosestAirThreat(const Position &pos) const; AOEThreat getClosestEnergyThreat(const Position &pos) const; AOEThreat getClosestThreat(Unit unit) const; bool isTargetOfThreat(Unit unit) const;
Psionic storm is both an air threat and a ground threat. Scarabs and mines are ground threats. EMP is the only energy threat. But of the 5 informational methods, only getClosestThreat() and isTargetOfThreat() are used in the rest of the code; the other 3 get methods serve no purpose. getClosestThreat() takes a unit and considers whether it is an air or ground unit to find the closest threat. It counts EMP as a threat to spellcasters only, even though all protoss units will lose their shields to it. It’s a little inconsistent; corsairs count as spellcasters, but Skynet never researches disruption web.
The results of all this tracking work are used in only one place, in BasicUnitAction::update(), which is a micro action like the others we’ve seen in the last days. The BasicUnitAction of course has very low priority among micro actions, so if the unit under consideration is already doing something else (like dragging the mine that is the threat), then none of this happens.
const bool isTargetOfThreat = AOEThreatTracker::Instance().isTargetOfThreat(mUnit);
if(!isTargetOfThreat)
{
const AOEThreat &closestThreat = AOEThreatTracker::Instance().getClosestThreat(mUnit);
if(closestThreat)
{
const int distanceToThreat = mUnit->getDistance(closestThreat->getPosition());
if(distanceToThreat < closestThreat->getRadius()+32)
{
stayAtRange(mUnit, closestThreat->getPosition(), closestThreat->getRadius() + 64, distanceToThreat);
return true;
}
}
}
First it remembers whether the current unit mUnit is the target of the threat, for later use. The explicit target does not dodge the threat (sometimes it can’t). If the current unit is not the target, but it is within range of at least one threat, then it finds the closest threat and tries to get out of its range. stayAtRange() calculates a direction and moves that way. There’s no attempt to avoid multiple threats, which are likely for spider mines; Skynet is happy to flee one and run into another. Also, I notice that AOEThreatTracker keeps careful track of each threat radius, but this code ignores it and flees to a fixed radius. I assume that’s good enough? Anyway, this limitation may explain why Skynet does not try to dodge lurker spines, which affect a long narrow area.
If the current unit is the target, the unit stays put so that the other units around it know what to do to avoid the threat. currentTargetUnit is whatever the current unit is already shooting at. This is not best if under attack by a reaver scarab, but it does help nearby units know which way to flee.
// If the target of a threat, dont do anything to cause it to move
if(isTargetOfThreat)
{
if(currentTargetUnit)
mUnit->attack(currentTargetUnit);
else
mUnit->stop();
return true;
}
Skynet has other interesting skills. See BlockedPathManager and MineBlockingMineralTask, for example. But that’s enough for now.
Tomorrow: Wrapup and lessons learned.
Comments