1 #include "CEconomy.h"
2 
3 #include <cfloat>
4 #include <limits>
5 
6 #include "headers/HEngine.h"
7 
8 #include "CAI.h"
9 #include "CRNG.h"
10 #include "CTaskHandler.h"
11 #include "CUnitTable.h"
12 #include "GameMap.hpp"
13 #include "CWishList.h"
14 #include "CDefenseMatrix.h"
15 #include "CGroup.h"
16 #include "CUnit.h"
17 #include "CPathfinder.h"
18 #include "CConfigParser.h"
19 #include "CIntel.h"
20 #include "GameMap.hpp"
21 #include "ReusableObjectFactory.hpp"
22 #include "CCoverageHandler.h"
23 
24 CEconomy::UnitCategory2UnitCategoryMap CEconomy::canBuildEnv;
25 
CEconomy(AIClasses * ai)26 CEconomy::CEconomy(AIClasses *ai): ARegistrar(700) {
27 	this->ai = ai;
28 	state = 0;
29 	incomes  = 0;
30 	mNow     = mNowSummed     = eNow     = eNowSummed     = 0.0f;
31 	mIncome  = mIncomeSummed  = eIncome  = eIncomeSummed  = 0.0f;
32 	uMIncome = uMIncomeSummed = uEIncome = uEIncomeSummed = 0.0f;
33 	mUsage   = mUsageSummed   = eUsage   = eUsageSummed   = 0.0f;
34 	mStorage = eStorage                                   = 0.0f;
35 	mstall = estall = mexceeding = eexceeding = mRequest = eRequest = false;
36 	initialized = stallThresholdsReady = false;
37 	utCommander = NULL;
38 	areMMakersEnabled = ai->gamemap->IsMetalFreeMap();
39 	windmap = worthBuildingTidal = false;
40 
41 	if (canBuildEnv.empty()) {
42 		canBuildEnv[AIR] = LAND|SEA|SUB; // TODO: -SUB?
43 		canBuildEnv[LAND] = LAND; // TODO: +SEA?
44 		canBuildEnv[SEA] = SEA|SUB; // TODO: +LAND?
45 		canBuildEnv[SUB] = SEA|SUB;
46 	}
47 }
48 
init(CUnit & unit)49 void CEconomy::init(CUnit &unit) {
50 	if (initialized) return;
51 	// NOTE: expecting "unit" is a commander unit
52 	const UnitDef *ud = ai->cb->GetUnitDef(unit.key);
53 
54 	utCommander = UT(ud->id);
55 	windmap = ((ai->cb->GetMaxWind() + ai->cb->GetMinWind()) / 2.0f) >= 10.0f;
56 	//float avgWind   = (ai->cb->GetMinWind() + ai->cb->GetMaxWind()) / 2.0f;
57 	//float windProf  = avgWind / utWind->cost;
58 	//float solarProf = utSolar->energyMake / utSolar->cost;
59 	worthBuildingTidal = ai->cb->GetTidalStrength() > 5.0f;
60 
61 	initialized = true;
62 }
63 
hasBegunBuilding(CGroup & group) const64 bool CEconomy::hasBegunBuilding(CGroup &group) const {
65 	std::map<int, CUnit*>::const_iterator i;
66 	for (i = group.units.begin(); i != group.units.end(); ++i) {
67 		CUnit *unit = i->second;
68 		if (ai->unittable->idle.find(unit->key) == ai->unittable->idle.end()
69 		|| !ai->unittable->idle[unit->key])
70 			return true;
71 	}
72 
73 	return false;
74 }
75 
hasFinishedBuilding(CGroup & group)76 bool CEconomy::hasFinishedBuilding(CGroup &group) {
77 	std::map<int, CUnit*>::iterator i;
78 	for (i = group.units.begin(); i != group.units.end(); i++) {
79 		CUnit *unit = i->second;
80 		if (ai->unittable->builders.find(unit->key) != ai->unittable->builders.end() &&
81 			ai->unittable->builders[unit->key]) {
82 			ai->unittable->builders[unit->key] = false;
83 			return true;
84 		}
85 	}
86 
87 	return false;
88 }
89 
requestGroup()90 CGroup* CEconomy::requestGroup() {
91 	CGroup *group = ReusableObjectFactory<CGroup>::Instance();
92 	group->ai = ai;
93 	group->reset();
94 	group->reg(*this);
95 
96 	activeGroups[group->key] = group;
97 
98 	return group;
99 }
100 
remove(ARegistrar & object)101 void CEconomy::remove(ARegistrar &object) {
102 	CGroup *group = dynamic_cast<CGroup*>(&object);
103 	LOG_II("CEconomy::remove " << (*group))
104 
105 	activeGroups.erase(group->key);
106 	takenMexes.erase(group->key);
107 	takenGeo.erase(group->key);
108 
109 	group->unreg(*this);
110 
111 	ReusableObjectFactory<CGroup>::Release(group);
112 }
113 
addUnitOnCreated(CUnit & unit)114 void CEconomy::addUnitOnCreated(CUnit& unit) {
115 	unitCategory c = unit.type->cats;
116 	if ((c&MEXTRACTOR).any()) {
117 		CGroup *group = requestGroup();
118 		group->addUnit(unit);
119 		takenMexes[group->key] = group->pos();
120 		CUnit *builder = ai->unittable->getUnit(group->firstUnit()->builtBy);
121 		if (builder) {
122 			assert(group->key != builder->group->key);
123 			takenMexes.erase(builder->group->key);
124 		}
125 	}
126 	else if(unit.type->def->needGeo) {
127 		CGroup *group = requestGroup();
128 		group->addUnit(unit);
129 		takenGeo[group->key] = group->pos();
130 		CUnit *builder = ai->unittable->getUnit(group->firstUnit()->builtBy);
131 		if (builder) {
132 			assert(group->key != builder->group->key);
133 			takenGeo.erase(builder->group->key);
134 		}
135 	}
136 }
137 
addUnitOnFinished(CUnit & unit)138 void CEconomy::addUnitOnFinished(CUnit &unit) {
139 	LOG_II("CEconomy::addUnitOnFinished " << unit)
140 
141 	unitCategory c = unit.type->cats;
142 
143 	if ((c&BUILDER).any() || ((c&ASSISTER).any() && (c&MOBILE).any())) {
144 		CGroup *group = requestGroup();
145 		group->addUnit(unit);
146 	}
147 }
148 
buildOrAssist(CGroup & group,buildType bt,unitCategory include,unitCategory exclude)149 void CEconomy::buildOrAssist(CGroup& group, buildType bt, unitCategory include, unitCategory exclude) {
150 	ATask* task = canAssist(bt, group);
151 	if (task != NULL) {
152 		ai->tasks->addTask(new AssistTask(ai, *task, group));
153 		return;
154 	}
155 
156 	if ((group.cats&BUILDER).none())
157 		return;
158 
159 	float3 pos = group.pos();
160 	float3 goal = pos;
161 	unitCategory catsWhere = canBuildWhere(group.cats);
162 	unitCategory catsWhereStrict = canBuildWhere(group.cats, true);
163 
164 	if (bt == BUILD_EPROVIDER) {
165 		if (!windmap)
166 			exclude |= WIND;
167 		if (!worthBuildingTidal)
168 			exclude |= TIDAL;
169 	}
170 	else if (bt == BUILD_MPROVIDER) {
171 		if ((include&MEXTRACTOR).any()) {
172 			goal = getClosestOpenMetalSpot(group);
173 			if (goal != ERRORVECTOR) {
174 				if (goal.y < 0.0f)
175 					exclude |= (LAND|AIR);
176 				else
177 					exclude |= (SEA|SUB);
178 			}
179 		}
180 	}
181 
182 	const CUnit* unit = group.firstUnit();
183 	bool isComm = (unit->type->cats&COMMANDER).any();
184 	std::multimap<float, UnitType*> candidates;
185 
186 	// retrieve the allowed buildable units...
187 	if ((include&MEXTRACTOR).any())
188 		ai->unittable->getBuildables(unit->type, include|catsWhereStrict, exclude, candidates);
189 	else
190 		ai->unittable->getBuildables(unit->type, include|catsWhere, exclude, candidates);
191 
192    	std::multimap<float, UnitType*>::iterator i = candidates.begin();
193    	int iterations = candidates.size() / (ai->cfgparser->getTotalStates() - state + 1);
194    	bool affordable = false;
195 
196 	if (!candidates.empty()) {
197     	/* Determine which of these we can afford */
198     	while(iterations >= 0) {
199     		if (canAffordToBuild(unit->type, i->second))
200     			affordable = true;
201     		else
202     			break;
203 
204     		if (i == --candidates.end())
205     			break;
206     		iterations--;
207     		i++;
208     	}
209 	}
210 	else if (bt != BUILD_MPROVIDER) {
211 		return;
212 	}
213 	else {
214 		goal = ERRORVECTOR;
215 	}
216 
217 	/* Perform the build */
218 	switch(bt) {
219 		case BUILD_EPROVIDER: {
220 			if (i->second->def->needGeo) {
221 				goal = getClosestOpenGeoSpot(group);
222 				if (goal != ERRORVECTOR) {
223 					// TODO: prevent commander walking?
224 					if (!eRequest && ai->pathfinder->getETA(group, goal) > 450.0f) {
225 						goal = ERRORVECTOR;
226 					}
227 				}
228 			}
229 			else if ((i->second->cats&EBOOSTER).any()) {
230 				goal = ai->coverage->getNextClosestBuildSite(unit, i->second);
231 			}
232 
233 			if (goal == ERRORVECTOR) {
234 				goal = pos;
235 				if (i != candidates.begin())
236 					--i;
237 				else
238 					++i;
239 			}
240 
241 			if (i != candidates.end())
242 				ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
243 
244 			break;
245 		}
246 
247 		case BUILD_MPROVIDER: {
248 			bool canBuildMMaker = (eIncome - eUsage) >= METAL2ENERGY || eexceeding;
249 
250 			if (goal != ERRORVECTOR) {
251 				bool allowDirectTask = true;
252 
253 				// TODO: there is a flaw in logic because when spot is under
254 				// threat we anyway send builders there
255 
256 				if (isComm) {
257 					int numOtherBuilders = ai->unittable->builders.size() - ai->unittable->factories.size();
258 					// if commander can build mmakers and there is enough
259 					// ordinary builders then prevent it walking for more than
260 					// 20 sec...
261 					if (numOtherBuilders > 2 && ai->unittable->canBuild(unit->type, MMAKER))
262 						allowDirectTask = ai->pathfinder->getETA(group, goal) < 600.0f;
263 				}
264 
265 				if (allowDirectTask) {
266 					ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
267 				}
268 				else if (mstall && (isComm || areMMakersEnabled) && canBuildMMaker) {
269 					UnitType* mmaker = ai->unittable->canBuild(unit->type, MMAKER);
270 					if (mmaker != NULL)
271 						ai->tasks->addTask(new BuildTask(ai, bt, mmaker, group, pos));
272 				}
273 			}
274 			else if (mstall && areMMakersEnabled && canBuildMMaker) {
275 				UnitType* mmaker = ai->unittable->canBuild(unit->type, MMAKER);
276 				if (mmaker != NULL)
277 					ai->tasks->addTask(new BuildTask(ai, bt, mmaker, group, pos));
278 			}
279 			else if (!eexceeding) {
280 				buildOrAssist(group, BUILD_EPROVIDER, EMAKER);
281 			}
282 			break;
283 		}
284 
285 		case BUILD_MSTORAGE: case BUILD_ESTORAGE: {
286 			/* Start building storage after enough ingame time */
287 			if (!taskInProgress(bt) && ai->cb->GetCurrentFrame() > 30*60*7) {
288 				goal = ai->defensematrix->getBestDefendedPos(0);
289 				if (goal == ERRORVECTOR)
290 					goal = pos;
291 				ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
292 			}
293 			break;
294 		}
295 
296 		case BUILD_FACTORY: {
297 			if (!taskInProgress(bt)) {
298 				int numFactories = ai->unittable->factories.size();
299 
300 				bool build = numFactories <= 0;
301 
302 				// TODO: add some delay before building next factory
303 
304 				if (!build && affordable && !eRequest) {
305 					float m = mNow / mStorage;
306 
307 					switch(state) {
308 						case 0: {
309 							build = (m > 0.45f);
310 							break;
311 						}
312 						case 1: {
313 							build = (m > 0.40f);
314 							break;
315 						}
316 						case 2: {
317 							build = (m > 0.35f);
318 							break;
319 						}
320 						case 3: {
321 							build = (m > 0.3f);
322 							break;
323 						}
324 						case 4: {
325 							build = (m > 0.25f);
326 							break;
327 						}
328 						case 5: {
329 							build = (m > 0.2f);
330 							break;
331 						}
332 						case 6: {
333 							build = (m > 0.15f);
334 							break;
335 						}
336 						default: {
337 							build = (m > 0.1f);
338 						}
339 					}
340 				}
341 
342 				if (build) {
343 					// TODO: fix getBestDefendedPos() for static builders
344 					if (numFactories > 1 && (unit->type->cats&MOBILE).any()) {
345 						goal = ai->coverage->getClosestDefendedPos(pos);
346 						if (goal == ERRORVECTOR) {
347 							goal = pos;
348 						}
349 					}
350 					ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
351 				}
352 			}
353 			break;
354 		}
355 
356 		case BUILD_IMP_DEFENSE: {
357 			// NOTE: important defense placement selects important place,
358 			// not closest one as other BUILD_XX_DEFENSE algoes do
359 			if (!taskInProgress(bt)) {
360 				bool allowTask = true;
361 
362 				// TODO: implement in getNextXXXBuildSite() "radius" argument
363 				// and fill it for static builders
364             	goal = ai->coverage->getNextImportantBuildSite(i->second);
365 
366 				allowTask = (goal != ERRORVECTOR);
367 
368 				if (allowTask && isComm)
369 					allowTask = ai->pathfinder->getETA(group, goal) < 300.0f;
370 
371 				if (allowTask)
372 					ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
373 			}
374 			break;
375 		}
376 
377 		case BUILD_AG_DEFENSE: case BUILD_AA_DEFENSE: case BUILD_UW_DEFENSE: case BUILD_MISC_DEFENSE: {
378 			if (affordable && !taskInProgress(bt)) {
379 				bool allowTask = true;
380 
381 				// TODO: implement in getNextBuildSite() "radius" argument
382 				// and fill it for static builders
383 				goal = ai->coverage->getNextClosestBuildSite(unit, i->second);
384 
385 				allowTask = (goal != ERRORVECTOR);
386 
387 				if (allowTask && isComm)
388 					allowTask = ai->pathfinder->getETA(group, goal) < 300.0f;
389 
390 				if (allowTask)
391 					ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
392 			}
393 			break;
394 		}
395 
396 		default: {
397 			if (affordable && !taskInProgress(bt))
398 				ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
399 			break;
400 		}
401 	}
402 }
403 
getBestSpot(CGroup & group,std::list<float3> & resources,std::map<int,float3> & tracker,bool metal)404 float3 CEconomy::getBestSpot(CGroup& group, std::list<float3>& resources, std::map<int, float3>& tracker, bool metal) {
405 	bool staticBuilder = (group.cats&STATIC).any();
406 	bool canBuildUnderWater = (group.cats&(SEA|SUB|AIR)).any();
407 	bool canBuildAboveWater = (group.cats&(LAND|AIR)).any();
408 	float bestDist = std::numeric_limits<float>::max();
409 	float3 bestSpot = ERRORVECTOR;
410 	float3 gpos = group.pos();
411 	float radius;
412 
413 	if (metal)
414 		radius = ai->cb->GetExtractorRadius();
415 	else
416 		radius = 16.0f;
417 
418 	std::list<float3>::iterator i;
419 	std::map<int, float3>::iterator j;
420 	for (i = resources.begin(); i != resources.end(); ++i) {
421 		if (i->y < 0.0f) {
422 			if (!canBuildUnderWater)
423 				continue;
424 		}
425 		else {
426 			if (!canBuildAboveWater)
427 				continue;
428 		}
429 
430 		bool taken = false;
431 		for (j = tracker.begin(); j != tracker.end(); ++j) {
432 			if (i->distance2D(j->second) < radius) {
433 				taken = true;
434 				break;
435 			}
436 		}
437 		if (taken) continue; // already taken or scheduled by current AI
438 
439 		int numUnits = ai->cb->GetFriendlyUnits(&ai->unitIDs[0], *i, 1.1f * radius);
440 		for (int u = 0; u < numUnits; u++) {
441 			const int uid = ai->unitIDs[u];
442 			const UnitDef *ud = ai->cb->GetUnitDef(uid);
443 			if (metal)
444 				taken = (UC(ud->id) & MEXTRACTOR).any();
445 			else
446 				taken = ud->needGeo;
447 			if (taken) break;
448 		}
449 		if (taken) continue; // already taken by ally team
450 
451 		float dist = gpos.distance2D(*i);
452 
453 		if (staticBuilder) {
454 			if (dist > group.buildRange)
455 				continue; // spot is out of range
456 		}
457 
458 		/*
459 		// NOTE: getPathLength() also considers static units
460 		float dist = ai->pathfinder->getPathLength(group, *i);
461 		if (dist < 0.0f)
462 			continue; // spot is out of build range or unreachable
463 		*/
464 
465 		// TODO: actually any spot with any threat should be skipped;
466 		// to implement this effectively we need to refactor tasks, cause
467 		// builder during approaching should scan target place for threat
468 		// periodically; currently it does not, so skipping dangerous spot
469 		// has no real profit
470 
471 		dist += 1000.0f * group.getThreat(*i, 300.0f);
472 		if (dist < bestDist) {
473 			bestDist = dist;
474 			bestSpot = *i;
475 		}
476 	}
477 
478 	if (bestSpot != ERRORVECTOR)
479 		// TODO: improper place for this
480 		tracker[group.key] = bestSpot;
481 
482 	return bestSpot;
483 }
484 
getClosestOpenMetalSpot(CGroup & group)485 float3 CEconomy::getClosestOpenMetalSpot(CGroup &group) {
486 	return getBestSpot(group, GameMap::metalspots, takenMexes, true);
487 }
488 
getClosestOpenGeoSpot(CGroup & group)489 float3 CEconomy::getClosestOpenGeoSpot(CGroup &group) {
490 	return getBestSpot(group, GameMap::geospots, takenGeo, false);
491 }
492 
update()493 void CEconomy::update() {
494 	int buildersCount = 0;
495 	int assistersCount = 0;
496 	int maxTechLevel = ai->cfgparser->getMaxTechLevel();
497 
498 	/* See if we can improve our eco by controlling metalmakers */
499 	controlMetalMakers();
500 
501 	/* If we are stalling, do something about it */
502 	preventStalling();
503 
504 	/* Update idle worker groups */
505 	std::map<int, CGroup*>::iterator i;
506 	for (i = activeGroups.begin(); i != activeGroups.end(); ++i) {
507 		CGroup *group = i->second;
508 		CUnit *unit = group->firstUnit();
509 
510 		if ((group->cats&MOBILE).any() && (group->cats&BUILDER).any())
511 			buildersCount++;
512 		if ((group->cats&MOBILE).any() && (group->cats&ASSISTER).any() && (group->cats&BUILDER).none())
513 			assistersCount++;
514 
515 		if (group->busy || !group->canPerformTasks())
516 			continue;
517 
518 		if ((group->cats&FACTORY).any()) {
519 			ai->tasks->addTask(new FactoryTask(ai, *group));
520 			continue;
521 		}
522 
523 		if ((group->cats&STATIC).any() && (group->cats&BUILDER).none()) {
524 			// we don't have a task for current unit type yet (these types are:
525 			// MEXTRACTOR & geoplant)
526 			continue;
527 		}
528 
529 		//float3 pos = group->pos();
530 
531 		// NOTE: we're using special algo for commander to prevent
532 		// it walking outside the base
533 		if ((unit->type->cats&COMMANDER).any()) {
534 			tryFixingStall(group);
535 			if (group->busy) continue;
536 
537 			/* If we don't have a factory, build one */
538 			if (ai->unittable->factories.empty() || mexceeding) {
539 				unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel);
540 				if (factory.any())
541 					buildOrAssist(*group, BUILD_FACTORY, factory);
542 				if (group->busy) continue;
543 			}
544 
545 			/* If we are exceeding and don't have estorage yet, build estorage */
546 			if (eexceeding && !ai->unittable->factories.empty()) {
547 				if (ai->unittable->energyStorages.size() < ai->cfgparser->getMaxTechLevel())
548 					buildOrAssist(*group, BUILD_ESTORAGE, ESTORAGE);
549 				if (group->busy) continue;
550 			}
551 
552 			// NOTE: in NOTA only static commanders can build TECH1 factories
553 			if ((unit->type->cats&STATIC).any()) {
554 				/* See if this unit can build desired factory */
555 				unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel);
556 				if (factory.any())
557 					buildOrAssist(*group, BUILD_FACTORY, factory);
558 				if (group->busy) continue;
559 
560 				factory = getNextTypeToBuild(unit, BUILDER|STATIC, maxTechLevel);
561 				if (factory.any())
562 					// TODO: invoke BUILD_ASSISTER building algo instead of
563 					// BUILD_FACTORY to properly place static builders?
564 					buildOrAssist(*group, BUILD_FACTORY, factory);
565 				if (group->busy) continue;
566 			}
567 
568 			// build nearby metal extractors if possible...
569 			if (mstall && !ai->gamemap->IsMetalMap()) {
570 				// NOTE: there is a special hack withing buildOrAssist()
571 				// to prevent building unnecessary MMAKERS
572 				buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
573 				if (group->busy) continue;
574 			}
575 
576 			// see if we can build defense
577 			tryBuildingDefense(group);
578 			if (group->busy) continue;
579 
580 			tryAssistingFactory(group);
581 			if (group->busy) continue;
582 		}
583 		else if ((unit->type->cats&BUILDER).any()) {
584 			tryFixingStall(group);
585 			if (group->busy) continue;
586 
587     		/* See if this unit can build desired factory */
588     		unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel);
589     		if (factory.any())
590     			buildOrAssist(*group, BUILD_FACTORY, factory);
591     		if (group->busy) continue;
592 
593     		/* If we are overflowing energy build an estorage */
594     		if (eexceeding && !mRequest) {
595     			if (ai->unittable->energyStorages.size() < ai->cfgparser->getMaxTechLevel())
596     				buildOrAssist(*group, BUILD_ESTORAGE, ESTORAGE);
597     			if (group->busy) continue;
598     		}
599 
600     		/* If we are overflowing metal build an mstorage */
601     		if (mexceeding && !eRequest) {
602     			buildOrAssist(*group, BUILD_MSTORAGE, MSTORAGE);
603     			if (group->busy) continue;
604     		}
605 
606     		/* If both requested, see what is required most */
607     		if (eRequest && mRequest) {
608     			if ((mNow / mStorage) > (eNow / eStorage))
609     				buildOrAssist(*group, BUILD_EPROVIDER, EMAKER);
610     			else
611     				buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
612     			if (group->busy) continue;
613     		}
614 
615     		/* See if we can build defense */
616     		tryBuildingDefense(group);
617     		if (group->busy) continue;
618 
619     		tryBuildingAntiNuke(group);
620     		if (group->busy) continue;
621 
622 			tryBuildingJammer(group);
623 			if (group->busy) continue;
624 
625     		tryBuildingStaticAssister(group);
626     		if (group->busy) continue;
627 
628     		/* Else just provide what is requested */
629     		if (eRequest) {
630     			buildOrAssist(*group, BUILD_EPROVIDER, EMAKER);
631     			if (group->busy) continue;
632     		}
633 
634     		if (mRequest) {
635     			buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
636     			if (group->busy) continue;
637     		}
638 
639     		tryAssistingFactory(group);
640     		if (group->busy) continue;
641 
642     		tryBuildingShield(group);
643     		if (group->busy) continue;
644 
645     		/* Otherwise just expand */
646     		if (!ai->gamemap->IsMetalMap()) {
647     			if ((mNow / mStorage) > (eNow / eStorage))
648     				buildOrAssist(*group, BUILD_EPROVIDER, EMAKER);
649     			else
650     				buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
651     		}
652 		}
653 		else if ((unit->type->cats&ASSISTER).any()) {
654 			// TODO: repair damaged buildings (& non-moving units?)
655 			// TODO: finish unfinished bulidings
656 			tryAssist(group, BUILD_IMP_DEFENSE);
657 			if (group->busy) continue;
658     		tryAssistingFactory(group);
659     		if (group->busy) continue;
660 			tryAssist(group, BUILD_AG_DEFENSE);
661 			if (group->busy) continue;
662 			tryAssist(group, BUILD_AA_DEFENSE);
663 			if (group->busy) continue;
664 			tryAssist(group, BUILD_UW_DEFENSE);
665 			if (group->busy) continue;
666 			tryAssist(group, BUILD_MISC_DEFENSE);
667 			if (group->busy) continue;
668 		}
669 	}
670 
671 	// TODO: consider assistersCount & military groups count for
672 	// requesting assisters
673 
674 	if (buildersCount < ai->cfgparser->getMaxWorkers()
675 	&& (buildersCount < ai->cfgparser->getMinWorkers()))
676 		ai->wishlist->push(BUILDER, 0, Wish::HIGH);
677 	else {
678 		if (buildersCount < ai->cfgparser->getMaxWorkers())
679 			ai->wishlist->push(BUILDER, 0, Wish::NORMAL);
680 	}
681 }
682 
taskInProgress(buildType bt)683 bool CEconomy::taskInProgress(buildType bt) {
684 	int tasksCounter = 0;
685 	//int maxTechLevel = ai->cfgparser->getMaxTechLevel();
686 	std::map<int, ATask*>::iterator it;
687 
688 	for (it = ai->tasks->activeTasks[TASK_BUILD].begin(); it != ai->tasks->activeTasks[TASK_BUILD].end(); ++it) {
689 		if (((BuildTask*)(it->second))->bt == bt) {
690 			tasksCounter++;
691 		}
692 	}
693 
694 	if (tasksCounter > 0) {
695 		/*
696 		switch (ai->difficulty) {
697 			case DIFFICULTY_EASY:
698 				return true;
699 			case DIFFICULTY_NORMAL:
700 			    if (maxTechLevel > MIN_TECHLEVEL && !(mRequest || eRequest))
701 					return (tasksCounter >= maxTechLevel);
702 				break;
703 			case DIFFICULTY_HARD:
704 				if (!(mRequest || estall))
705 					return (tasksCounter >= maxTechLevel * 2);
706 				break;
707 		}
708 		*/
709 		return true;
710 	}
711 
712 	return false;
713 }
714 
controlMetalMakers()715 void CEconomy::controlMetalMakers() {
716 	float eRatio = eNow / eStorage;
717 
718 	if (eRatio < 0.3f) {
719 		int count = ai->unittable->setOnOff(ai->unittable->metalMakers, false);
720 		if (count > 0) {
721 			if (METAL2ENERGY < 1.0f)
722 				estall = false;
723 			areMMakersEnabled = false;
724 			return;
725 		}
726 	}
727 
728 	if (eRatio > 0.7f) {
729 		int count = ai->unittable->setOnOff(ai->unittable->metalMakers, true);
730 		if (count > 0) {
731 			if (METAL2ENERGY > 1.0f)
732 				mstall = false;
733 			areMMakersEnabled = true;
734 			return;
735 		}
736 	}
737 }
738 
preventStalling()739 void CEconomy::preventStalling() {
740 	bool taskRemoved = false;
741 		// for tracking so only one task is removed per update
742 	bool needMStallFixing, needEStallFixing;
743 	AssistTask
744 		*taskWithPowerBuilder = NULL,
745 		*taskWithFastBuilder = NULL;
746 	std::map<int, ATask*>::iterator it;
747 
748 	// if we're not stalling, return...
749 	if (!mstall && !estall) {
750 		// if factorytask is on wait, unwait it...
751 		for (it = ai->tasks->activeTasks[TASK_FACTORY].begin(); it != ai->tasks->activeTasks[TASK_FACTORY].end(); ++it) {
752 			FactoryTask *task = (FactoryTask*)it->second;
753 			task->setWait(false);
754 		}
755 		return;
756 	}
757 
758 	needMStallFixing = mstall;
759 	needEStallFixing = estall;
760 
761 	// check if there are tasks which are fixing current resource stall problems...
762 	for (it = ai->tasks->activeTasks[TASK_BUILD].begin(); it != ai->tasks->activeTasks[TASK_BUILD].end(); ++it) {
763 		BuildTask *task = (BuildTask*)it->second;
764 		if (needMStallFixing && task->bt == BUILD_MPROVIDER)
765 			needMStallFixing = false;
766 		if (needEStallFixing && task->bt == BUILD_EPROVIDER)
767 			needEStallFixing = false;
768 	}
769 
770 	if (needMStallFixing || needEStallFixing) {
771     	// stop all guarding workers which do not help in fixing the problem...
772     	it = ai->tasks->activeTasks[TASK_ASSIST].begin();
773     	while (it != ai->tasks->activeTasks[TASK_ASSIST].end()) {
774     		AssistTask *task = (AssistTask*)it->second; ++it;
775 
776     		// if the assisting group is moving, skip it...
777     		if (task->isMoving)
778     			continue;
779 
780     		if (task->assist->t == TASK_BUILD) {
781     			// ignore builders which are fixing the problems...
782     			BuildTask* build = dynamic_cast<BuildTask*>(task->assist);
783     			if ((mstall || mRequest) && build->bt == BUILD_MPROVIDER)
784     				continue;
785     			if ((estall || eRequest) && build->bt == BUILD_EPROVIDER)
786     				continue;
787 
788     			task->remove(); taskRemoved = true;
789 
790     			break;
791     		}
792 
793     		// detect fastest & most powerful builders among factory assisters...
794     		if (task->assist->t == TASK_FACTORY) {
795     			if (taskWithPowerBuilder) {
796     				if (task->firstGroup()->firstUnit()->type->def->buildSpeed > taskWithPowerBuilder->firstGroup()->firstUnit()->type->def->buildSpeed)
797     					taskWithPowerBuilder = task;
798     			}
799     			else
800     				taskWithPowerBuilder = task;
801 
802     			if (taskWithFastBuilder) {
803     				// NOTE: after commander walking has been reduced it has
804     				// limited power to solve mstall problem
805     				if ((task->firstGroup()->cats&COMMANDER).none()
806     				&& task->firstGroup()->speed > taskWithFastBuilder->firstGroup()->speed)
807     					taskWithFastBuilder = task;
808     			}
809     			else
810     				taskWithFastBuilder = task;
811     		}
812     	}
813 	}
814 
815 	if (!taskRemoved && needMStallFixing && taskWithFastBuilder) {
816 		taskWithFastBuilder->remove();
817 		taskRemoved = true;
818 	}
819 
820 	if (!taskRemoved && needEStallFixing && taskWithPowerBuilder) {
821 		taskWithPowerBuilder->remove();
822 		taskRemoved = true;
823 	}
824 
825 	// wait all factories and their assisters...
826 	for (it = ai->tasks->activeTasks[TASK_FACTORY].begin(); it != ai->tasks->activeTasks[TASK_FACTORY].end(); ++it) {
827 		FactoryTask *task = (FactoryTask*)it->second;
828 		task->setWait(true);
829 	}
830 }
831 
updateIncomes()832 void CEconomy::updateIncomes() {
833 	const int currentFrame = ai->cb->GetCurrentFrame();
834 
835 	// FIXME:
836 	// 1) algo sucks when game is started without initial resources
837 	// 2) these values should be recalculated each time AI has lost almost
838 	// all of its units (it is like another game start)
839 	// 3) these values should slowly decay to zero
840 	if (!stallThresholdsReady) {
841 		if ((utCommander->cats&FACTORY).any()) {
842 			mStart = utCommander->def->metalMake;
843 			eStart = 0.0f;
844 		}
845 		else {
846 			bool oldAlgo = true;
847 			unitCategory initialFactory = getNextTypeToBuild(utCommander, FACTORY, MIN_TECHLEVEL);
848 			if (initialFactory.any()) {
849 				UnitType* facType = ai->unittable->canBuild(utCommander, initialFactory);
850 				if (facType != NULL) {
851 					float buildTime = facType->def->buildTime / utCommander->def->buildSpeed;
852 					//float mDrain = facType->def->metalCost / buildTime;
853 					float eDrain = facType->def->energyCost / buildTime;
854 					float mTotalIncome = utCommander->def->metalMake * buildTime;
855 
856 					mStart = (1.5f * facType->def->metalCost - ai->cb->GetMetal() - mTotalIncome) / buildTime;
857 					if (mStart < 0.0f)
858 						mStart = 0.0f;
859 					eStart = 0.9f * eDrain;
860 
861 					oldAlgo = false;
862 				}
863 			}
864 
865 			if (oldAlgo) {
866 				mStart = 2.0f * utCommander->def->metalMake;
867 				eStart = 1.5f * utCommander->def->energyMake;
868 			}
869 		}
870 
871 		LOG_II("CEconomy::updateIncomes Metal stall threshold: " << mStart)
872 		LOG_II("CEconomy::updateIncomes Energy stall threshold: " << eStart)
873 
874 		stallThresholdsReady = true;
875 	}
876 
877 	incomes++;
878 
879 	mUsageSummed  += ai->cb->GetMetalUsage();
880 	eUsageSummed  += ai->cb->GetEnergyUsage();
881 	mStorage       = ai->cb->GetMetalStorage();
882 	eStorage       = ai->cb->GetEnergyStorage();
883 
884 	mNow     = ai->cb->GetMetal();
885 	eNow     = ai->cb->GetEnergy();
886 	mIncome  = ai->cb->GetMetalIncome();
887 	eIncome  = ai->cb->GetEnergyIncome();
888 	mUsage   = alpha*(mUsageSummed / incomes) + (1.0f-alpha)*(ai->cb->GetMetalUsage());
889 	eUsage   = beta *(eUsageSummed / incomes) + (1.0f-beta) *(ai->cb->GetEnergyUsage());
890 
891 	//LOG_II("mIncome = " << mIncome << "; mNow = " << mNow << "; mStorage = " << mStorage)
892 	//LOG_II("eIncome = " << eIncome << "; eNow = " << eNow << "; eStorage = " << eStorage)
893 
894 	// FIXME: think smth better to avoid hardcoding, e.g. implement decaying
895 
896 	if (mIncome < EPS && currentFrame > 32) {
897 		mstall = (mNow < (mStorage*0.1f));
898 	}
899 	else {
900 		if (ai->unittable->activeUnits.size() < 5)
901 			mstall = (mNow < (mStorage*0.1f) && mUsage > mIncome) || mIncome < mStart;
902 		else
903 			mstall = (mNow < (mStorage*0.1f) && mUsage > mIncome);
904 	}
905 
906 	if (eIncome < EPS && currentFrame > 32) {
907 	    estall = (eNow < (eStorage*0.1f));
908 	}
909 	else {
910 		if (ai->unittable->activeUnits.size() < 5)
911 			estall = (eNow < (eStorage*0.1f) && eUsage > eIncome) || eIncome < eStart;
912 		else
913 			estall = (eNow < (eStorage*0.1f) && eUsage > eIncome);
914 	}
915 
916 	mexceeding = (mNow > (mStorage*0.9f) && mUsage < mIncome);
917 	eexceeding = (eNow > (eStorage*0.9f) && eUsage < eIncome);
918 
919 	mRequest = (mNow < (mStorage*0.5f) && mUsage > mIncome);
920 	eRequest = (eNow < (eStorage*0.5f) && eUsage > eIncome);
921 
922 	int tstate = ai->cfgparser->determineState(mIncome, eIncome);
923 	if (tstate != state) {
924 		char buf[64];
925 		sprintf(buf, "State changed to %d, activated techlevel %d", tstate, ai->cfgparser->getMaxTechLevel());
926 		LOG_II(buf);
927 		state = tstate;
928 	}
929 }
930 
canAssist(buildType t,CGroup & group)931 ATask* CEconomy::canAssist(buildType t, CGroup &group) {
932 	std::map<int, ATask*>::iterator i;
933 	std::multimap<float, BuildTask*> suited;
934 
935 	if (!group.canAssist())
936 		return NULL;
937 
938 	for (i = ai->tasks->activeTasks[TASK_BUILD].begin(); i != ai->tasks->activeTasks[TASK_BUILD].end(); ++i) {
939 		BuildTask* buildtask = (BuildTask*)i->second;
940 
941 		// only build tasks we are interested in...
942 		float travelTime;
943 		if (buildtask->bt != t || !buildtask->assistable(group, travelTime))
944 			continue;
945 
946 		suited.insert(std::pair<float, BuildTask*>(travelTime, buildtask));
947 	}
948 
949 	// there are no suited tasks that require assistance
950 	if (suited.empty())
951 		return NULL;
952 
953 	bool isCommander = (group.cats&COMMANDER).any();
954 
955 	if (isCommander) {
956 		float eta = (suited.begin())->first;
957 
958 		// don't pursuit as commander when walkdistance is more than 15 seconds
959 		if (eta > 450.0f) return NULL;
960 	}
961 
962 	return suited.begin()->second;
963 }
964 
canAssistFactory(CGroup & group)965 ATask* CEconomy::canAssistFactory(CGroup &group) {
966 	if (!group.canAssist())
967 		return NULL;
968 
969 	float3 pos = group.pos();
970 	std::map<int, ATask*>::iterator i;
971 	std::map<float, FactoryTask*> candidates;
972 	int maxTechLevel = ai->cfgparser->getMaxTechLevel();
973 	unitCategory maxTechLevelCat;
974 
975 	maxTechLevelCat.set(maxTechLevel - 1);
976 
977 	// NOTE: prefer assising more advanced factories depending
978 	// on current possible tech level. Skip factories full of assisters to
979 	// prevent unneeded cotinuous assist attempts on the same factory.
980 
981 	for (i = ai->tasks->activeTasks[TASK_FACTORY].begin(); i != ai->tasks->activeTasks[TASK_FACTORY].end(); ++i) {
982 		/* TODO: instead of euclid distance, use pathfinder distance */
983 		float dist = pos.distance2D(i->second->pos);
984 		if ((i->second->firstGroup()->cats&maxTechLevelCat).any())
985 			dist -= 1000.0f;
986 		dist += (1000.0f * i->second->assisters.size()) / FACTORY_ASSISTERS;
987 		candidates[dist] = (FactoryTask*)i->second;
988 	}
989 
990 	if (candidates.empty())
991 		return NULL;
992 
993 	std::map<float, FactoryTask*>::iterator j;
994 	for (j = candidates.begin(); j != candidates.end(); ++j) {
995 		if (!j->second->assistable(group))
996 			continue;
997 		return j->second;
998 	}
999 
1000 	return NULL;
1001 }
1002 
canAffordToBuild(UnitType * builder,UnitType * utToBuild)1003 bool CEconomy::canAffordToBuild(UnitType *builder, UnitType *utToBuild) {
1004 	float buildTime = utToBuild->def->buildTime / builder->def->buildSpeed;
1005 	float mPrediction = mNow + (mIncome - mUsage) * buildTime - utToBuild->def->metalCost;
1006 	float ePrediction = eNow + (eIncome - eUsage) * buildTime - utToBuild->def->energyCost;
1007 
1008 	if (!mRequest)
1009 		mRequest = mPrediction < 0.0f;
1010 	if (!eRequest)
1011 		eRequest = ePrediction < 0.0f;
1012 
1013 	return (mPrediction >= 0.0f && ePrediction >= 0.0f && mNow/mStorage >= 0.1f);
1014 }
1015 
getNextTypeToBuild(CUnit * unit,unitCategory cats,int maxteachlevel)1016 unitCategory CEconomy::getNextTypeToBuild(CUnit *unit, unitCategory cats, int maxteachlevel) {
1017 	return getNextTypeToBuild(unit->type, cats, maxteachlevel);
1018 }
1019 
getNextTypeToBuild(UnitType * ut,unitCategory cats,int maxteachlevel)1020 unitCategory CEconomy::getNextTypeToBuild(UnitType *ut, unitCategory cats, int maxteachlevel) {
1021 	std::list<unitCategory>::iterator f;
1022 
1023 	if (ai->intel->strategyTechUp) {
1024 		for(f = ai->intel->allowedFactories.begin(); f != ai->intel->allowedFactories.end(); ++f) {
1025 			for(int techlevel = maxteachlevel; techlevel >= MIN_TECHLEVEL; techlevel--) {
1026 				unitCategory type((*f)|cats);
1027 				type.set(techlevel - 1);
1028 				if (isTypeRequired(ut, type, maxteachlevel))
1029 					return type;
1030 			}
1031 		}
1032 	}
1033 	else {
1034 		for(int techlevel = MIN_TECHLEVEL; techlevel <= maxteachlevel; techlevel++) {
1035 			unitCategory techlevelCat;
1036 			techlevelCat.set(techlevel - 1);
1037 			for(f = ai->intel->allowedFactories.begin(); f != ai->intel->allowedFactories.end(); f++) {
1038 				unitCategory type((*f)|cats|techlevelCat);
1039 				if (isTypeRequired(ut, type, maxteachlevel))
1040 					return type;
1041 			}
1042 		}
1043 	}
1044 
1045 	return 0;
1046 }
1047 
isTypeRequired(UnitType * builder,unitCategory cats,int maxteachlevel)1048 bool CEconomy::isTypeRequired(UnitType* builder, unitCategory cats, int maxteachlevel) {
1049 	UnitType* ut = ai->unittable->canBuild(builder, cats);
1050 	if(ut) {
1051 		if ((cats&FACTORY).any()) {
1052 			int numRequired = ut->def->canBeAssisted ? 1: maxteachlevel;
1053 			if (ai->unittable->factoryCount(cats) < numRequired)
1054 				return true;
1055 		}
1056 		else if(ai->unittable->unitCount(cats) == 0)
1057 			return true;
1058 	}
1059 	return false;
1060 }
1061 
tryBuildingDefense(CGroup * group)1062 void CEconomy::tryBuildingDefense(CGroup* group) {
1063 	if (group->busy)
1064 		return;
1065 	if (mstall || estall)
1066 		return;
1067 
1068 	unitCategory incCats, excCats;
1069 	buildType bt;
1070 //	CCoverageCell::NType layer;
1071 
1072 	if (ai->intel->getEnemyCount(AIR) > 0 && rng.RandFloat() > 0.66f) {
1073 		bt = BUILD_AA_DEFENSE;
1074 //		layer = CCoverageCell::DEFENSE_ANTIAIR;
1075 		// TODO: replace STATIC with DEFENSE after all config files updated
1076 		incCats = STATIC|ANTIAIR;
1077 		excCats = TORPEDO;
1078 	}
1079 	else if (ai->gamemap->IsWaterMap() && rng.RandFloat() > 0.5f) {
1080 		bt = BUILD_UW_DEFENSE;
1081 //		layer = CCoverageCell::DEFENSE_UNDERWATER;
1082 		// TODO: replace STATIC with DEFENSE after all config files updated
1083 		incCats = STATIC|TORPEDO;
1084 		// NOTE: we do not support coastal torpedo launchers
1085 		excCats = LAND;
1086 	}
1087 	else
1088 	{
1089 		bt = BUILD_AG_DEFENSE;
1090 //		layer = CCoverageCell::DEFENSE_GROUND;
1091 		incCats = ATTACKER|DEFENSE;
1092 		excCats = ANTIAIR|TORPEDO;
1093 	}
1094 
1095 /*
1096 	int size = ai->coverage->getLayerSize(layer);
1097 	const float k = size / (ai->unittable->staticUnits.size() - size + 1.0f);
1098 	bool allow;
1099 
1100 	switch (ai->difficulty) {
1101 		case DIFFICULTY_EASY:
1102 			allow = k < 0.11f;
1103 			break;
1104 		case DIFFICULTY_NORMAL:
1105 			allow = k < 0.31f;
1106 			break;
1107 		case DIFFICULTY_HARD:
1108 			allow = k < 0.51f;
1109 			break;
1110 	}
1111 */
1112 
1113 	buildOrAssist(*group, bt, incCats, excCats);
1114 }
1115 
tryBuildingShield(CGroup * group)1116 void CEconomy::tryBuildingShield(CGroup* group) {
1117 	if (group->busy)
1118 		return;
1119 	if (estall)
1120 		return;
1121 	if (ai->difficulty == DIFFICULTY_EASY)
1122 		return;
1123 	if (ai->intel->getEnemyCount(ARTILLERY) < 20)
1124 		return;
1125 
1126 	// TODO: also build shields when enemy got LRPC
1127 
1128 	// TODO: introduce importance threshold otherwise AI
1129 	// finally will cover all buildings, including cheap one
1130 
1131 	buildOrAssist(*group, BUILD_IMP_DEFENSE, SHIELD);
1132 }
1133 
tryBuildingStaticAssister(CGroup * group)1134 void CEconomy::tryBuildingStaticAssister(CGroup* group) {
1135 	if (group->busy)
1136 		return;
1137 	if (ai->difficulty == DIFFICULTY_EASY)
1138 		return;
1139 	if (mstall || estall)
1140 		return;
1141 
1142 	buildOrAssist(*group, BUILD_MISC_DEFENSE, NANOTOWER);
1143 }
1144 
tryBuildingJammer(CGroup * group)1145 void CEconomy::tryBuildingJammer(CGroup* group) {
1146 	if (group->busy)
1147 		return;
1148 	if (ai->difficulty == DIFFICULTY_EASY)
1149 		return;
1150 	if (mstall || estall)
1151 		return;
1152 
1153 	buildOrAssist(*group, BUILD_MISC_DEFENSE, JAMMER);
1154 }
1155 
tryBuildingAntiNuke(CGroup * group)1156 void CEconomy::tryBuildingAntiNuke(CGroup* group) {
1157 	if (group->busy)
1158 		return;
1159 	if (ai->difficulty == DIFFICULTY_EASY)
1160 		return;
1161 	// TODO: avoid using unitCount()
1162 	if (ai->unittable->unitCount(ANTINUKE) >= ai->intel->enemies.getUnits(NUKE)->size())
1163 		return;
1164 
1165 	buildOrAssist(*group, BUILD_IMP_DEFENSE, ANTINUKE);
1166 }
1167 
tryAssistingFactory(CGroup * group)1168 void CEconomy::tryAssistingFactory(CGroup* group) {
1169 	if (group->busy)
1170 		return;
1171 	if (mstall || estall)
1172 		return;
1173 
1174 	ATask *task;
1175 	if ((task = canAssistFactory(*group)) != NULL) {
1176 		ai->tasks->addTask(new AssistTask(ai, *task, *group));
1177 	}
1178 }
1179 
tryAssist(CGroup * group,buildType bt)1180 void CEconomy::tryAssist(CGroup* group, buildType bt) {
1181 	if (group->busy)
1182 		return;
1183 	if (mstall || estall)
1184 		return;
1185 
1186 	ATask *task = canAssist(bt, *group);
1187 	if (task != NULL) {
1188 		ai->tasks->addTask(new AssistTask(ai, *task, *group));
1189 		return;
1190 	}
1191 }
1192 
tryFixingStall(CGroup * group)1193 void CEconomy::tryFixingStall(CGroup* group) {
1194 	bool mStall = (mstall && !mexceeding);
1195 	bool eStall = (estall && !eexceeding);
1196 	std::list<buildType> order;
1197 
1198 	if (group->busy)
1199 		return;
1200 
1201 	if (mStall && eStall
1202 	&& (((mIncome - mUsage) * METAL2ENERGY) < (eIncome - eUsage))) {
1203 		order.push_back(BUILD_MPROVIDER);
1204 		order.push_back(BUILD_EPROVIDER);
1205 	}
1206 	else {
1207 		if (eStall)
1208 			order.push_back(BUILD_EPROVIDER);
1209 		if (mStall)
1210 			order.push_back(BUILD_MPROVIDER);
1211 	}
1212 
1213 	for (std::list<buildType>::const_iterator it = order.begin(); it != order.end(); ++it) {
1214 		buildOrAssist(*group, *it, *it == BUILD_EPROVIDER ? EMAKER : MEXTRACTOR);
1215 		if (group->busy) break;
1216 	}
1217 }
1218 
canBuildWhere(unitCategory unitCats,bool strictly)1219 unitCategory CEconomy::canBuildWhere(unitCategory unitCats, bool strictly) {
1220 	unitCategory result;
1221 	UnitCategory2UnitCategoryMap::iterator it;
1222 
1223 	for (it = canBuildEnv.begin(); it != canBuildEnv.end(); ++it) {
1224 		if ((unitCats&it->first).any())
1225 			result |= it->second;
1226 	}
1227 
1228 	if (strictly)
1229 		return result;
1230 
1231 	// explicitly exclude useless units on specific maps...
1232 	if (ai->gamemap->IsLandFreeMap()) {
1233 		result &= ~(LAND);
1234 	}
1235 	else if (ai->gamemap->IsWaterFreeMap()) {
1236 		result &= ~(SEA|SUB);
1237 	}
1238 	else {
1239 		// TODO: detect unit's sector land/sea percent and enforce/remove tags
1240 		// accordingly
1241 	}
1242 
1243 	return result;
1244 }
1245