void CEconomy::addUnitOnCreated(CUnit& unit) { unitCategory c = unit.type->cats; if ((c&MEXTRACTOR).any()) { CGroup *group = requestGroup(); group->addUnit(unit); takenMexes[group->key] = group->pos(); CUnit *builder = ai->unittable->getUnit(group->firstUnit()->builtBy); if (builder) { assert(group->key != builder->group->key); takenMexes.erase(builder->group->key); } } else if(unit.type->def->needGeo) { CGroup *group = requestGroup(); group->addUnit(unit); takenGeo[group->key] = group->pos(); CUnit *builder = ai->unittable->getUnit(group->firstUnit()->builtBy); if (builder) { assert(group->key != builder->group->key); takenGeo.erase(builder->group->key); } } }
void CMilitary::update(int frame) { int busyScoutGroups = 0; std::vector<int> keys; std::vector<int> occupied; std::map<int, bool> isOccupied; std::map<int, ATask*>::iterator itTask; std::map<MilitaryGroupBehaviour, std::map<int, CGroup*>* >::iterator itGroup; TargetsFilter tf; // NOTE: we store occupied targets in two formats because vector is used // for list of targets (which can be used if no suitable primary // targets are found), second one is used for TargetFilter to filter out // occupied targets when searching for primary targets for (itTask = ai->tasks->activeTasks[TASK_ATTACK].begin(); itTask != ai->tasks->activeTasks[TASK_ATTACK].end(); ++itTask) { AttackTask *task = (AttackTask*)itTask->second; occupied.push_back(task->target); isOccupied[task->target] = true; } for(itGroup = groups.begin(); itGroup != groups.end(); itGroup++) { int target = -2; MilitaryGroupBehaviour behaviour = itGroup->first; // setup common target filter params per behaviour... tf.reset(); switch(behaviour) { case SCOUT: tf.threatRadius = 300.0f; tf.threatFactor = 1000.0f; break; case ENGAGE: tf.threatFactor = 0.001f; break; case BOMBER: tf.threatFactor = 100.0f; // TODO: replace constant with maneuvering radius of plane? tf.threatRadius = 1000.0f; break; case AIRFIGHTER: tf.threatFactor = 100.0f; break; } // NOTE: start with random group ID because some groups can't reach the // target (e.g. Fleas); this helps to overcome the problem when there // is a target, but first group can't reach it, and AI constantly // trying to add same task again and again which leads to attack stall keys.clear(); util::GetShuffledKeys<int, CGroup*>(keys, *(itGroup->second)); const std::vector<CategoryMatcher>& targetBlocks = ai->intel->targets[behaviour]; for (int i = 0; i < keys.size(); ++i) { CGroup *group = (*(itGroup->second))[keys[i]]; // if group is busy, don't bother... if (group->busy || !group->canPerformTasks()) { if (group->busy) { if (behaviour == SCOUT) busyScoutGroups++; if (drawTasks) visualizeTasks(group); } continue; } // NOTE: each group can have different score on the same target // because of their disposition, strength etc. tf.scoreCeiling = std::numeric_limits<float>::max(); tf.excludeId = &isOccupied; // setup custom target filter params per current group... switch(behaviour) { case SCOUT: if ((group->cats&AIR).any()) tf.threatCeiling = 1.1f; else tf.threatCeiling = (std::max<float>((float)MAX_SCOUTS_IN_GROUP / group->units.size(), 1.0f)) * group->strength - EPS; break; case BOMBER: tf.threatCeiling = group->strength + group->firstUnit()->type->dps; break; } // basic target selection... if (target != -1) { for(int b = 0; b < targetBlocks.size(); b++) { target = group->selectTarget(ai->intel->enemies.getUnits(targetBlocks[b]), tf); } } bool isAssembling = isAssemblingGroup(group); if ((!isAssembling && behaviour == SCOUT) || target < 0) { // scan for better target among existing targets... tf.excludeId = NULL; int assistTarget = group->selectTarget(occupied, tf); if (assistTarget >= 0 && assistTarget != target) { ATask *task = ai->tasks->getTaskByTarget(assistTarget); if (task) { bool canAssist = false; int assisters = task->assisters.size(); float cumulativeStrength = task->firstGroup()->strength; std::list<ATask*>::iterator itATask; for (itATask = task->assisters.begin(); itATask != task->assisters.end(); itATask++) { cumulativeStrength += (*itATask)->firstGroup()->strength; } switch(behaviour) { case SCOUT: canAssist = assisters == 0; break; case ENGAGE: canAssist = cumulativeStrength < 2.0f * tf.threatValue; break; case BOMBER: canAssist = assisters < 9; break; case AIRFIGHTER: canAssist = assisters < 3; break; } if (canAssist) { mergeGroups.erase(group->key); if (!ai->tasks->addTask(new AssistTask(ai, *task, *group))) group->addBadTarget(assistTarget); break; } } } } bool isStrongEnough = true; if (target >= 0) { isStrongEnough = group->strength >= (tf.threatValue - EPS); bool isSizeEnough = (behaviour == ENGAGE) ? group->units.size() >= ai->cfgparser->getMinGroupSize(group->techlvl) : true; if (behaviour != ENGAGE) isAssembling = false; if ((isAssembling && isSizeEnough) || (!isAssembling && isStrongEnough)) { ATask::NPriority taskPriority = (behaviour == BOMBER || behaviour == AIRFIGHTER) ? ATask::HIGH : ATask::NORMAL; mergeGroups.erase(group->key); if (ai->tasks->addTask(new AttackTask(ai, target, *group), taskPriority)) { occupied.push_back(target); isOccupied[target] = true; } else { group->addBadTarget(target); } break; } } bool bMerge = !(isStrongEnough || isAssembling); switch(behaviour) { case SCOUT: bMerge = bMerge && activeScoutGroups.size() > 1 && group->units.size() < MAX_SCOUTS_IN_GROUP; break; default: bMerge = bMerge && groups[behaviour]->size() > 1; } if (bMerge) mergeGroups[group->key] = group; } } /* Merge the groups that were not strong enough */ if (mergeGroups.size() >= 2) { std::list<CGroup*> merge; for (std::map<int, CGroup*>::iterator base = mergeGroups.begin(); base != mergeGroups.end(); ++base) { if (!base->second->busy) { for (std::map<int,CGroup*>::iterator compare = mergeGroups.begin(); compare != mergeGroups.end(); ++compare) { if (!compare->second->busy && base->first != compare->first) { if (base->second->canMerge(compare->second)) { bool canMerge = false; if ((base->second->cats&SCOUTER).any()) // TODO: replace MERGE_DISTANCE with ETA? canMerge = (base->second->pos().distance2D(compare->second->pos()) < MERGE_DISTANCE); else canMerge = true; if (canMerge) { if (merge.empty()) merge.push_back(base->second); merge.push_back(compare->second); break; } } } } if (!merge.empty()) { ai->tasks->addTask(new MergeTask(ai, merge)); merge.clear(); break; } } } // remove busy (merging) groups... std::map<int, CGroup*>::iterator it = mergeGroups.begin(); while(it != mergeGroups.end()) { int key = it->first; ++it; if (mergeGroups[key]->busy) mergeGroups.erase(key); } } //bool gotAirFactory = ai->unittable->gotFactory(AIRCRAFT); //bool gotSeaFactory = (ai->unittable->gotFactory(NAVAL) || ai->unittable->gotFactory(HOVER)); if (ai->difficulty == DIFFICULTY_HARD) { // when all scouts are busy create some more... // FIXME: when scouts are stucked AI will not build them anymore, // while there are scout targets available if (busyScoutGroups == activeScoutGroups.size()) { //unitCategory baseType = ai->gamemap->IsWaterMap() && gotSeaFactory ? SEA|SUB : LAND; Wish::NPriority p = activeScoutGroups.size() < ai->cfgparser->getMinScouts() ? Wish::HIGH: Wish::NORMAL; //if(gotAirFactory && rng.RandFloat() > 0.66f) // baseType = AIR; ai->wishlist->push(MOBILE | SCOUTER | allowedEnvCats, 0, p); } } // TODO: build units on real need only, not always ai->wishlist->push(requestUnit(allowedEnvCats), 0, Wish::NORMAL); /* if (gotAirFactory && rng.RandFloat() > 0.66f) { ai->wishlist->push(requestUnit(AIR), forbiddenCats); } else { if (ai->gamemap->IsWaterMap() && gotSeaFactory) ai->wishlist->push(requestUnit(SEA|SUB), forbiddenCats, Wish::NORMAL); else ai->wishlist->push(requestUnit(LAND), forbiddenCats, Wish::NORMAL); } */ }
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); } }
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; } } }