void AttackTask::onUpdate() { CGroup *group = firstGroup(); if (group->isMicroing() && group->isIdle()) { targetAlt = -1; // for sure group->micro(false); } if (isMoving) { /* Keep tracking the target */ pos = ai->cbc->GetUnitPos(target); float3 gpos = group->pos(); float dist = gpos.distance2D(pos); float range = group->getRange(); /* See if we can attack our target already */ if (dist <= range) { bool canAttack = true; /* // for ground units prevent shooting across hill... if ((group->cats&AIR).none()) { // FIXME: improve dist = ai->pathfinder->getPathLength(*group, pos); canAttack = (dist <= range * 1.1f); } */ if (canAttack) { if ((group->cats&BUILDER).any()) group->reclaim(target); else group->attack(target); isMoving = false; ai->pathfinder->remove(*group); group->micro(true); } } } /* See if we can attack a target we found on our path */ if (!(group->isMicroing() || urgent())) { if ((group->cats&BUILDER).any()) resourceScan(); // builders should not be too aggressive else enemyScan(targetAlt); } }
void AssistTask::onUpdate() { CGroup *group = firstGroup(); if (group->isMicroing() && group->isIdle()) { targetAlt = -1; // for sure group->micro(false); } if (!assisting) { if (isMoving) { /* Keep tracking the target */ pos = assist->pos; float3 gpos = group->pos(); float dist = gpos.distance2D(pos); float range = group->getRange(); if (dist <= range) { bool canAssist = true; /* // for ground units prevent assisting across hill... if ((group->cats&AIR).none()) { dist = ai->pathfinder->getPathLength(*group, pos); canAssist = (dist <= range * 1.1f); } */ if (canAssist) { isMoving = false; ai->pathfinder->remove(*group); } } } if (!isMoving) { group->assist(*assist); group->micro(true); assisting = true; } } if (!group->isMicroing()) { if ((group->cats&BUILDER).any()) resourceScan(); // builders should not be too aggressive else if ((group->cats&AIR).none()) { enemyScan(targetAlt); } } }
void MergeTask::onUpdate() { /* See which groups can be merged already */ std::list<CGroup*>::iterator it; for (it = groups.begin(); it != groups.end(); ++it) { CGroup *g = *it; if (g->isMicroing()) continue; if (pos.distance2D(g->pos()) < range) { mergable[g->key] = g; g->micro(true); } } /* We have at least two groups, now we can merge */ if (mergable.size() >= 2) { std::vector<int> keys; std::map<int, CGroup*>::iterator it; // get keys because while merging "mergable" is reducing... for (it = mergable.begin(); it != mergable.end(); ++it) { keys.push_back(it->first); } for (int i = 0; i < keys.size(); i++) { int key = keys[i]; if (key != masterGroup->key) { CGroup *g = mergable[key]; LOG_II("MergeTask::update merging " << (*g) << " with " << (*masterGroup)) // NOTE: group being merged is automatically removed masterGroup->merge(*g); } } assert(mergable.size() == 1); mergable.clear(); masterGroup->micro(false); } // if only one (or none) group remains, merging is no longer possible, // remove the task, unreg groups... if (groups.size() <= 1) ATask::remove(); }
void CEconomy::update() { int buildersCount = 0; int assistersCount = 0; int maxTechLevel = ai->cfgparser->getMaxTechLevel(); /* See if we can improve our eco by controlling metalmakers */ controlMetalMakers(); /* If we are stalling, do something about it */ preventStalling(); /* Update idle worker groups */ std::map<int, CGroup*>::iterator i; for (i = activeGroups.begin(); i != activeGroups.end(); ++i) { CGroup *group = i->second; CUnit *unit = group->firstUnit(); if ((group->cats&MOBILE).any() && (group->cats&BUILDER).any()) buildersCount++; if ((group->cats&MOBILE).any() && (group->cats&ASSISTER).any() && (group->cats&BUILDER).none()) assistersCount++; if (group->busy || !group->canPerformTasks()) continue; if ((group->cats&FACTORY).any()) { ai->tasks->addTask(new FactoryTask(ai, *group)); continue; } if ((group->cats&STATIC).any() && (group->cats&BUILDER).none()) { // we don't have a task for current unit type yet (these types are: // MEXTRACTOR & geoplant) continue; } float3 pos = group->pos(); // NOTE: we're using special algo for commander to prevent // it walking outside the base if ((unit->type->cats&COMMANDER).any()) { tryFixingStall(group); if (group->busy) continue; /* If we don't have a factory, build one */ if (ai->unittable->factories.empty() || mexceeding) { unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel); if (factory.any()) buildOrAssist(*group, BUILD_FACTORY, factory); if (group->busy) continue; } /* If we are exceeding and don't have estorage yet, build estorage */ if (eexceeding && !ai->unittable->factories.empty()) { if (ai->unittable->energyStorages.size() < ai->cfgparser->getMaxTechLevel()) buildOrAssist(*group, BUILD_ESTORAGE, ESTORAGE); if (group->busy) continue; } // NOTE: in NOTA only static commanders can build TECH1 factories if ((unit->type->cats&STATIC).any()) { /* See if this unit can build desired factory */ unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel); if (factory.any()) buildOrAssist(*group, BUILD_FACTORY, factory); if (group->busy) continue; factory = getNextTypeToBuild(unit, BUILDER|STATIC, maxTechLevel); if (factory.any()) // TODO: invoke BUILD_ASSISTER building algo instead of // BUILD_FACTORY to properly place static builders? buildOrAssist(*group, BUILD_FACTORY, factory); if (group->busy) continue; } // build nearby metal extractors if possible... if (mstall && !ai->gamemap->IsMetalMap()) { // NOTE: there is a special hack withing buildOrAssist() // to prevent building unnecessary MMAKERS buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR); if (group->busy) continue; } // see if we can build defense tryBuildingDefense(group); if (group->busy) continue; tryAssistingFactory(group); if (group->busy) continue; } else if ((unit->type->cats&BUILDER).any()) { tryFixingStall(group); if (group->busy) continue; /* See if this unit can build desired factory */ unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel); if (factory.any()) buildOrAssist(*group, BUILD_FACTORY, factory); if (group->busy) continue; /* If we are overflowing energy build an estorage */ if (eexceeding && !mRequest) { if (ai->unittable->energyStorages.size() < ai->cfgparser->getMaxTechLevel()) buildOrAssist(*group, BUILD_ESTORAGE, ESTORAGE); if (group->busy) continue; } /* If we are overflowing metal build an mstorage */ if (mexceeding && !eRequest) { buildOrAssist(*group, BUILD_MSTORAGE, MSTORAGE); if (group->busy) continue; } /* If both requested, see what is required most */ if (eRequest && mRequest) { if ((mNow / mStorage) > (eNow / eStorage)) buildOrAssist(*group, BUILD_EPROVIDER, EMAKER); else buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR); if (group->busy) continue; } /* See if we can build defense */ tryBuildingDefense(group); if (group->busy) continue; tryBuildingAntiNuke(group); if (group->busy) continue; tryBuildingJammer(group); if (group->busy) continue; tryBuildingStaticAssister(group); if (group->busy) continue; /* Else just provide what is requested */ if (eRequest) { buildOrAssist(*group, BUILD_EPROVIDER, EMAKER); if (group->busy) continue; } if (mRequest) { buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR); if (group->busy) continue; } tryAssistingFactory(group); if (group->busy) continue; tryBuildingShield(group); if (group->busy) continue; /* Otherwise just expand */ if (!ai->gamemap->IsMetalMap()) { if ((mNow / mStorage) > (eNow / eStorage)) buildOrAssist(*group, BUILD_EPROVIDER, EMAKER); else buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR); } } else if ((unit->type->cats&ASSISTER).any()) { // TODO: repair damaged buildings (& non-moving units?) // TODO: finish unfinished bulidings tryAssist(group, BUILD_IMP_DEFENSE); if (group->busy) continue; tryAssistingFactory(group); if (group->busy) continue; tryAssist(group, BUILD_AG_DEFENSE); if (group->busy) continue; tryAssist(group, BUILD_AA_DEFENSE); if (group->busy) continue; tryAssist(group, BUILD_UW_DEFENSE); if (group->busy) continue; tryAssist(group, BUILD_MISC_DEFENSE); if (group->busy) continue; } } // TODO: consider assistersCount & military groups count for // requesting assisters if (buildersCount < ai->cfgparser->getMaxWorkers() && (buildersCount < ai->cfgparser->getMinWorkers())) ai->wishlist->push(BUILDER, 0, Wish::HIGH); else { if (buildersCount < ai->cfgparser->getMaxWorkers()) ai->wishlist->push(BUILDER, 0, Wish::NORMAL); } }
float3 CEconomy::getBestSpot(CGroup& group, std::list<float3>& resources, std::map<int, float3>& tracker, bool metal) { bool staticBuilder = (group.cats&STATIC).any(); bool canBuildUnderWater = (group.cats&(SEA|SUB|AIR)).any(); bool canBuildAboveWater = (group.cats&(LAND|AIR)).any(); float bestDist = std::numeric_limits<float>::max(); float3 bestSpot = ERRORVECTOR; float3 gpos = group.pos(); float radius; if (metal) radius = ai->cb->GetExtractorRadius(); else radius = 16.0f; std::list<float3>::iterator i; std::map<int, float3>::iterator j; for (i = resources.begin(); i != resources.end(); ++i) { if (i->y < 0.0f) { if (!canBuildUnderWater) continue; } else { if (!canBuildAboveWater) continue; } bool taken = false; for (j = tracker.begin(); j != tracker.end(); ++j) { if (i->distance2D(j->second) < radius) { taken = true; break; } } if (taken) continue; // already taken or scheduled by current AI int numUnits = ai->cb->GetFriendlyUnits(&ai->unitIDs[0], *i, 1.1f * radius); for (int u = 0; u < numUnits; u++) { const int uid = ai->unitIDs[u]; const UnitDef *ud = ai->cb->GetUnitDef(uid); if (metal) taken = (UC(ud->id) & MEXTRACTOR).any(); else taken = ud->needGeo; if (taken) break; } if (taken) continue; // already taken by ally team float dist = gpos.distance2D(*i); if (staticBuilder) { if (dist > group.buildRange) continue; // spot is out of range } /* // NOTE: getPathLength() also considers static units float dist = ai->pathfinder->getPathLength(group, *i); if (dist < 0.0f) continue; // spot is out of build range or unreachable */ // TODO: actually any spot with any threat should be skipped; // to implement this effectively we need to refactor tasks, cause // builder during approaching should scan target place for threat // periodically; currently it does not, so skipping dangerous spot // has no real profit dist += 1000.0f * group.getThreat(*i, 300.0f); if (dist < bestDist) { bestDist = dist; bestSpot = *i; } } if (bestSpot != ERRORVECTOR) // TODO: improper place for this tracker[group.key] = bestSpot; return bestSpot; }
void CEconomy::buildOrAssist(CGroup& group, buildType bt, unitCategory include, unitCategory exclude) { ATask* task = canAssist(bt, group); if (task != NULL) { ai->tasks->addTask(new AssistTask(ai, *task, group)); return; } if ((group.cats&BUILDER).none()) return; float3 pos = group.pos(); float3 goal = pos; unitCategory catsWhere = canBuildWhere(group.cats); unitCategory catsWhereStrict = canBuildWhere(group.cats, true); if (bt == BUILD_EPROVIDER) { if (!windmap) exclude |= WIND; if (!worthBuildingTidal) exclude |= TIDAL; } else if (bt == BUILD_MPROVIDER) { if ((include&MEXTRACTOR).any()) { goal = getClosestOpenMetalSpot(group); if (goal != ERRORVECTOR) if (goal.y < 0.0f) exclude |= (LAND|AIR); else exclude |= (SEA|SUB); } } const CUnit* unit = group.firstUnit(); bool isComm = (unit->type->cats&COMMANDER).any(); std::multimap<float, UnitType*> candidates; // retrieve the allowed buildable units... if ((include&MEXTRACTOR).any()) ai->unittable->getBuildables(unit->type, include|catsWhereStrict, exclude, candidates); else ai->unittable->getBuildables(unit->type, include|catsWhere, exclude, candidates); std::multimap<float, UnitType*>::iterator i = candidates.begin(); int iterations = candidates.size() / (ai->cfgparser->getTotalStates() - state + 1); bool affordable = false; if (!candidates.empty()) { /* Determine which of these we can afford */ while(iterations >= 0) { if (canAffordToBuild(unit->type, i->second)) affordable = true; else break; if (i == --candidates.end()) break; iterations--; i++; } } else if (bt != BUILD_MPROVIDER) { return; } else { goal = ERRORVECTOR; } /* Perform the build */ switch(bt) { case BUILD_EPROVIDER: { if (i->second->def->needGeo) { goal = getClosestOpenGeoSpot(group); if (goal != ERRORVECTOR) { // TODO: prevent commander walking? if (!eRequest && ai->pathfinder->getETA(group, goal) > 450.0f) { goal = ERRORVECTOR; } } } else if ((i->second->cats&EBOOSTER).any()) { goal = ai->coverage->getNextClosestBuildSite(unit, i->second); } if (goal == ERRORVECTOR) { goal = pos; if (i != candidates.begin()) --i; else ++i; } if (i != candidates.end()) ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal)); break; } case BUILD_MPROVIDER: { bool canBuildMMaker = (eIncome - eUsage) >= METAL2ENERGY || eexceeding; if (goal != ERRORVECTOR) { bool allowDirectTask = true; // TODO: there is a flaw in logic because when spot is under // threat we anyway send builders there if (isComm) { int numOtherBuilders = ai->unittable->builders.size() - ai->unittable->factories.size(); // if commander can build mmakers and there is enough // ordinary builders then prevent it walking for more than // 20 sec... if (numOtherBuilders > 2 && ai->unittable->canBuild(unit->type, MMAKER)) allowDirectTask = ai->pathfinder->getETA(group, goal) < 600.0f; } if (allowDirectTask) { ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal)); } else if (mstall && (isComm || areMMakersEnabled) && canBuildMMaker) { UnitType* mmaker = ai->unittable->canBuild(unit->type, MMAKER); if (mmaker != NULL) ai->tasks->addTask(new BuildTask(ai, bt, mmaker, group, pos)); } } else if (mstall && areMMakersEnabled && canBuildMMaker) { UnitType* mmaker = ai->unittable->canBuild(unit->type, MMAKER); if (mmaker != NULL) ai->tasks->addTask(new BuildTask(ai, bt, mmaker, group, pos)); } else if (!eexceeding) { buildOrAssist(group, BUILD_EPROVIDER, EMAKER); } break; } case BUILD_MSTORAGE: case BUILD_ESTORAGE: { /* Start building storage after enough ingame time */ if (!taskInProgress(bt) && ai->cb->GetCurrentFrame() > 30*60*7) { pos = ai->defensematrix->getBestDefendedPos(0); ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, pos)); } break; } case BUILD_FACTORY: { if (!taskInProgress(bt)) { int numFactories = ai->unittable->factories.size(); bool build = numFactories <= 0; // TODO: add some delay before building next factory if (!build && affordable && !eRequest) { float m = mNow / mStorage; switch(state) { case 0: { build = (m > 0.45f); break; } case 1: { build = (m > 0.40f); break; } case 2: { build = (m > 0.35f); break; } case 3: { build = (m > 0.3f); break; } case 4: { build = (m > 0.25f); break; } case 5: { build = (m > 0.2f); break; } case 6: { build = (m > 0.15f); break; } default: { build = (m > 0.1f); } } } if (build) { // TODO: fix getBestDefendedPos() for static builders if (numFactories > 1 && (unit->type->cats&MOBILE).any()) { goal = ai->coverage->getClosestDefendedPos(pos); if (goal == ERRORVECTOR) { goal = pos; } } ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal)); } } break; } case BUILD_IMP_DEFENSE: { // NOTE: important defense placement selects important place, // not closest one as other BUILD_XX_DEFENSE algoes do if (!taskInProgress(bt)) { bool allowTask = true; // TODO: implement in getNextXXXBuildSite() "radius" argument // and fill it for static builders goal = ai->coverage->getNextImportantBuildSite(i->second); allowTask = (goal != ERRORVECTOR); if (allowTask && isComm) allowTask = ai->pathfinder->getETA(group, goal) < 300.0f; if (allowTask) ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal)); } break; } case BUILD_AG_DEFENSE: case BUILD_AA_DEFENSE: case BUILD_UW_DEFENSE: case BUILD_MISC_DEFENSE: { if (affordable && !taskInProgress(bt)) { bool allowTask = true; // TODO: implement in getNextBuildSite() "radius" argument // and fill it for static builders goal = ai->coverage->getNextClosestBuildSite(unit, i->second); allowTask = (goal != ERRORVECTOR); if (allowTask && isComm) allowTask = ai->pathfinder->getETA(group, goal) < 300.0f; if (allowTask) ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal)); } break; } default: { if (affordable && !taskInProgress(bt)) ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal)); break; } } }