Skynet - arbiter control
Arbiters are tough to use well. You want to maneuver your arbiter to cloak as many of your units as possible while staying safe. You want to stasis or recall at critical times and places. Occasionally you want to do damage with the pea-shooter. The goals pull in different directions, and you have to decide what’s best.
Here’s how Skynet decides. Skynet has two arbiter skills: It knows how to cloak units efficiently and how to stasis. Skynet does not use recall, and it (understandably) doesn’t put any emphasis on targeting enemies with the puny gun.
Arbiter control is in ArbiterAction::update(), which is like the other micro actions, set up by Behaviour::createDefaultActions(). First it considers stasis:
if(mUnit->getEnergy() >= 100 && BWAPI::Broodwar->self()->hasResearched(BWAPI::TechTypes::Stasis_Field))
{
const int stasisSize = 48;
bool stasisUrgently = false;
if(UnitInformation::Instance().getUnitsTargetting(mUnit).size() >= 6 || (UnitInformation::Instance().getUnitsTargetting(mUnit).size() >= 1 && mUnit->totalHitPointFraction() < 0.3))
stasisUrgently = true;
First, if stasis is possible and the arbiter is in danger of being lost, Skynet sets stasisUrgently to true.
UnitGroup stasisChoices;
for each(Unit enemy in UnitTracker::Instance().selectAllEnemy())
{
if(!UnitHelper::isArmyUnit(enemy->getType()))
continue;
if(enemy->isUnderStorm() || enemy->isStasised())
continue;
const int distance = mUnit->getDistance(enemy);
if(distance > 250 || distance < stasisSize)
continue;
stasisChoices.insert(enemy);
}
Then it comes up with a list of candidate enemies to stasis. Enemies under psionic storm are not candidates—let them suffer!
if(stasisChoices.size() > 4 || (!stasisChoices.empty() && stasisUrgently))
{
UnitGroup stasisTargets = stasisChoices.getBestFittingToCircle(stasisSize);
if(stasisTargets.size() > 4 || (!stasisTargets.empty() && stasisUrgently))
{
const Position &stasisLocation = stasisTargets.getCenter();
if(mUnit->getDistance(stasisLocation) <= BWAPI::TechTypes::Stasis_Field.getWeapon().maxRange())
{
mUnit->useTech(BWAPI::TechTypes::Stasis_Field, stasisLocation);
LatencyTracker::Instance().placingStasis(mUnit, stasisLocation);
return true;
}
else
{
mUnit->move(stasisLocation);
return true;
}
}
}
}
And finally Skynet decides whether and where to stasis. If it can stasis at least 5 enemy units, or at least 1 unit and stasisUrgently is set, then it does. UnitGroup::getBestFittingToCircle() finds a circle of the given size which covers as many units in the UnitGroup as possible, and returns the smaller UnitGroup which it covers.
It’s a simple heuristic. An arbiter will happily stasis 5 vultures uselessly when it reaches stasis energy after the army it was covering is destroyed by tanks and vultures. The “right” way to do this would be with a pay-me-now-or-pay-me-later tradeoff that compares the situation now with possible future situations, which would of course be far more complicated. And, since that’s not hard enough yet, it should also consider the effect on the battle: Stasis on a ramp to block reinforcements? Stasis the tanks that are doing the most damage, or the vessel that is detecting the cloaked army? And so on.
Next is maneuvering the arbiter to cloak units:
UnitGroup unitsToCloak;
for each(Unit unit in squadUnitGroup)
{
if(!UnitHelper::isArmyUnit(unit->getType()))
continue;
if(unit->getType() == BWAPI::UnitTypes::Protoss_Arbiter || unit->getType().isBuilding())
continue;
if(mUnit->getDistance(unit) > 250)
continue;
unitsToCloak.insert(unit);
}
if(!unitsToCloak.empty())
{
unitsToCloak = unitsToCloak.getBestFittingToCircle(136);
if(!unitsToCloak.empty())
{
Position cloakLocation = unitsToCloak.getCenter();
if(mUnit->getDistance(cloakLocation) > 110)
{
mUnit->move(cloakLocation);
return true;
}
}
}
Candidate units to cloak are basically military units in the squadUnitGroup which can be cloaked and are near enough. Skynet never tries to protect probes or observers with the cloaking field (UnitHelper::isArmyUnit() returns false for both), but it will try to cloak dark templar that are already cloaked (a simple omission from the above code and trivial to fix) and units that are already covered by another arbiter (only a little harder to fix). It does the getBestFittingToCircle() thing to find the largest circle of units that it can cloak, and moves toward the circle’s center if it’s not close enough. If it’s already close enough then the micro action does not occur, which I assume frees the arbiter to fire its gun when possible, flee danger within limits, and so on.
Tomorrow: Avoiding splash damage, finishing the series.
Comments
krasi0 on :
provides?
Jay Scott on :