1 #include "CGroup.h"
2 
3 #include <sstream>
4 #include <iostream>
5 #include <string>
6 
7 #include "CAI.h"
8 #include "CUnit.h"
9 #include "CUnitTable.h"
10 #include "CTaskHandler.h"
11 #include "CPathfinder.h"
12 #include "CDefenseMatrix.h"
13 
14 int CGroup::counter = 0;
15 
addUnit(CUnit & unit)16 void CGroup::addUnit(CUnit &unit) {
17 	if (unit.group) {
18 		if (unit.group == this) {
19 			LOG_WW("CGroup::addUnit " << unit << " already registered in " << (*this))
20 			return; // already registered
21 		} else {
22 			// NOTE: unit can exist in one and only group
23 			//LOG_II("CGroup::addUnit " << unit << " still in " << (*(unit.group)))
24 			unit.group->remove(unit);
25 		}
26 	}
27 
28 	assert(unit.group == NULL);
29 
30 	units[unit.key] = &unit;
31 	unit.reg(*this);
32 	unit.group = this;
33 
34 	recalcProperties(&unit);
35 
36 	LOG_II("CGroup::addUnit " << unit << " to " << (*this))
37 	// TODO: if group is busy invoke new unit to community process?
38 }
39 
remove()40 void CGroup::remove() {
41 	LOG_II("CGroup::remove " << (*this))
42 
43 	// NOTE: removal order below is important
44 
45 	std::list<ARegistrar*>::iterator j = records.begin();
46 	while(j != records.end()) {
47 		ARegistrar *regobj = *j; ++j;
48 		// remove from CEconomy, CPathfinder, ATask, CTaskHandler
49 		regobj->remove(*this);
50 	}
51 
52 	std::map<int, CUnit*>::iterator i;
53 	for (i = units.begin(); i != units.end(); i++) {
54 		i->second->unreg(*this);
55 		i->second->group = NULL;
56 	}
57 	units.clear();
58 	badTargets.clear();
59 
60 	assert(records.empty());
61 }
62 
remove(ARegistrar & object)63 void CGroup::remove(ARegistrar &object) {
64 	CUnit *unit = dynamic_cast<CUnit*>(&object);
65 
66 	LOG_II("CGroup::remove " << (*unit) << " from " << (*this))
67 
68 	assert(units.find(unit->key) != units.end());
69 
70 	unit->group = NULL;
71 	unit->unreg(*this);
72 	units.erase(unit->key);
73 
74 	if (unit == latecomerUnit)
75 		removeLatecomer();
76 
77 	badTargets.clear();
78 
79 	/* If no more units remain in this group, remove the group */
80 	if (units.empty()) {
81 		remove();
82 	} else {
83 		/* Recalculate properties of the current group */
84 		recalcProperties(NULL, true);
85 
86 		std::map<int, CUnit*>::iterator i;
87 		for (i = units.begin(); i != units.end(); ++i) {
88 			recalcProperties(i->second);
89 		}
90 	}
91 }
92 
reclaim(int entity,bool feature)93 void CGroup::reclaim(int entity, bool feature) {
94 	float3 pos;
95 
96 	if (feature) {
97 		pos = ai->cb->GetFeaturePos(entity);
98 		if (pos == ZeroVector)
99 			return;
100 	}
101 
102 	std::map<int, CUnit*>::iterator i;
103 	for (i = units.begin(); i != units.end(); ++i) {
104 		if (i->second->def->canReclaim) {
105 			if (feature)
106 				i->second->reclaim(pos, 16.0f);
107 			else
108 				i->second->reclaim(entity);
109 		}
110 	}
111 }
112 
repair(int target)113 void CGroup::repair(int target) {
114 	std::map<int, CUnit*>::iterator i;
115 	for (i = units.begin(); i != units.end(); ++i) {
116 		if (i->second->def->canRepair)
117 			i->second->repair(target);
118 	}
119 }
120 
abilities(bool on)121 void CGroup::abilities(bool on) {
122 	std::map<int, CUnit*>::iterator i;
123 	for (i = units.begin(); i != units.end(); ++i) {
124 		if (i->second->def->canCloak)
125 			i->second->cloak(on);
126 	}
127 }
128 
micro(bool on)129 void CGroup::micro(bool on) {
130 	std::map<int, CUnit*>::iterator i;
131 	for (i = units.begin(); i != units.end(); ++i)
132 		i->second->micro(on);
133 }
134 
isMicroing()135 bool CGroup::isMicroing() {
136 	std::map<int, CUnit*>::iterator i;
137 	for (i = units.begin(); i != units.end(); ++i) {
138 		if (i->second->isMicroing())
139 			return true;
140 	}
141 	return false;
142 }
143 
isIdle()144 bool CGroup::isIdle() {
145 	bool idle = true;
146 	std::map<int, CUnit*>::iterator i;
147 	for (i = units.begin(); i != units.end(); ++i) {
148 		if (!ai->unittable->idle[i->second->key]) {
149 			idle = false;
150 			break;
151 		}
152 	}
153 	return idle;
154 }
155 
reset()156 void CGroup::reset() {
157 	LOG_II("CGroup::reset " << (*this))
158 
159 	assert(units.empty());
160 	recalcProperties(NULL, true);
161 	busy = false;
162 	micro(false);
163 	abilities(false);
164 	units.clear();
165 	records.clear();
166 	badTargets.clear();
167 }
168 
recalcProperties(CUnit * unit,bool reset)169 void CGroup::recalcProperties(CUnit* unit, bool reset)
170 {
171 	if (reset) {
172 		strength   = 0.0f;
173 		speed      = std::numeric_limits<float>::max();
174 		size       = 0;
175 		buildSpeed = 0.0f;
176 		range      = 0.0f;
177 		buildRange = 0.0f;
178 		los        = 0.0f;
179 		maxSlope   = 1.0f;
180 		pathType   = -1; // emulate NONE
181 		techlvl    = TECH1;
182 		cats.reset();
183 		groupRadius = 0.0f;
184 		radiusUpdateRequired = false;
185 		cost       = 0.0f;
186 		costMetal  = 0.0f;
187 		worstSpeedUnit = NULL;
188 		worstSlopeUnit = NULL;
189 		latecomerUnit = NULL;
190 		latecomerWeight = 0;
191 	}
192 
193 	if (unit == NULL)
194 		return;
195 
196 	if (unit->builtBy >= 0) {
197 		for (int i = MIN_TECHLEVEL - 1; i < MAX_TECHLEVEL; i++) {
198 			if (unit->techlvl[i]) {
199 				for (int j = MIN_TECHLEVEL - 1; i < MAX_TECHLEVEL; j++) {
200 					if (techlvl[j]) {
201 						if (i > j) {
202 							techlvl.reset();
203 							techlvl.set(i);
204 						}
205 					}
206 					break;
207 				}
208 				break;
209 			}
210 		}
211 	}
212 
213 	mergeCats(unit->type->cats);
214 
215 	// NOTE: aircraft & static units do not have movedata
216 	MoveData* md = unit->def->movedata;
217 	if (md) {
218 		bool validCandidate = true;
219 
220 		if (units.size() > 1) {
221 			unitCategory
222 				envCatsOfGroup = CATS_ENV&cats,
223 				envCatsOfUnit = CATS_ENV&unit->type->cats;
224 			validCandidate = (envCatsOfGroup == envCatsOfUnit);
225 		}
226 
227 		if (validCandidate) {
228 			// select base path type with the lowerest slope, so pos(true) will
229 			// return valid postition for all units in a group...
230 			if (md->maxSlope <= maxSlope) {
231 				pathType = md->pathType;
232 				maxSlope = md->maxSlope;
233 				worstSlopeUnit = unit;
234 			}
235 		}
236 	}
237 
238 	strength += unit->type->dps;
239 	buildSpeed += unit->def->buildSpeed;
240 	size += FOOTPRINT2REAL * std::max<int>(unit->def->xsize, unit->def->zsize);
241 	range = std::max<float>(ai->cb->GetUnitMaxRange(unit->key), range);
242 	buildRange = std::max<float>(unit->def->buildDistance, buildRange);
243 	los = std::max<float>(unit->def->losRadius, los);
244 	cost += unit->type->cost;
245 	costMetal += unit->type->costMetal;
246 
247 	float temp;
248 	temp = ai->cb->GetUnitSpeed(unit->key);
249 	if (temp < speed) {
250 		speed = temp;
251 		worstSpeedUnit = unit;
252 	}
253 
254 	radiusUpdateRequired = true;
255 }
256 
merge(CGroup & group)257 void CGroup::merge(CGroup &group) {
258 	std::map<int, CUnit*>::iterator i = group.units.begin();
259 	// NOTE: "group" will automatically be removed when last unit
260 	// is transferred
261 	while(i != group.units.end()) {
262 		CUnit *unit = i->second; ++i;
263 		assert(unit->group == &group);
264 		addUnit(*unit);
265 	}
266 }
267 
pos(bool force_valid)268 float3 CGroup::pos(bool force_valid) {
269 	std::map<int, CUnit*>::iterator i;
270 	float3 pos(0.0f, 0.0f, 0.0f);
271 
272 	for (i = units.begin(); i != units.end(); ++i)
273 		pos += ai->cb->GetUnitPos(i->first);
274 
275 	pos /= units.size();
276 
277 	if (force_valid) {
278 		if (ai->pathfinder->isBlocked(pos.x, pos.z, pathType)) {
279 			float3 posValid = ai->pathfinder->getClosestPos(pos, PATH2REAL, this);
280 			if (posValid == ERRORVECTOR) {
281 				float bestDistance = std::numeric_limits<float>::max();
282 				for (i = units.begin(); i != units.end(); ++i) {
283 					float3 pos2 = ai->cb->GetUnitPos(i->first);
284 					if (ai->pathfinder->isBlocked(pos2.x, pos2.z, pathType))
285 						pos2 = ai->pathfinder->getClosestPos(pos2, PATH2REAL, this);
286 					if (pos2 != ERRORVECTOR) {
287 						float distance = pos.distance2D(pos2);
288 						if (distance < bestDistance) {
289 							posValid = pos2;
290 							bestDistance = distance;
291 						}
292 					}
293 
294 				}
295 			}
296 			return posValid;
297 		}
298 	}
299 
300 	return pos;
301 }
302 
radius()303 float CGroup::radius() {
304 	if (radiusUpdateRequired) {
305 		int i;
306 		// get number of units per leg length in a square
307 		for(i = 1; units.size() > i * i; i++);
308 		// calculate length of leg of square
309 		float sqLeg = maxLength() * i / (float)units.size();
310 		sqLeg *= sqLeg;
311 		// calculate half of hypotenuse
312 		groupRadius = sqrt(sqLeg + sqLeg) / 2.0f;
313 
314 		radiusUpdateRequired = false;
315 	}
316 	return groupRadius;
317 }
318 
maxLength()319 int CGroup::maxLength() {
320 	return size + units.size() * FOOTPRINT2REAL;
321 }
322 
assist(ATask & t)323 void CGroup::assist(ATask &t) {
324 	// t->addAssister(
325 	switch(t.t) {
326 		case TASK_BUILD: {
327 			BuildTask *task = dynamic_cast<BuildTask*>(&t);
328 			CUnit *unit = task->firstGroup()->firstUnit();
329 			guard(unit->key);
330 			break;
331 		}
332 
333 		case TASK_ATTACK: {
334 			// TODO: Calculate the flanking pos and attack from there
335 			AttackTask *task = dynamic_cast<AttackTask*>(&t);
336 			attack(task->target);
337 			break;
338 		}
339 
340 		case TASK_FACTORY: {
341 			FactoryTask *task = dynamic_cast<FactoryTask*>(&t);
342 			CUnit *unit = task->firstGroup()->firstUnit();
343 			guard(unit->key);
344 			break;
345 		}
346 
347 		default: return;
348 	}
349 }
350 
move(float3 & pos,bool enqueue)351 void CGroup::move(float3 &pos, bool enqueue) {
352 	std::map<int, CUnit*>::iterator i;
353 	for (i = units.begin(); i != units.end(); ++i)
354 		i->second->move(pos, enqueue);
355 }
356 
wait()357 void CGroup::wait() {
358 	std::map<int, CUnit*>::iterator i;
359 	for (i = units.begin(); i != units.end(); ++i)
360 		i->second->wait();
361 }
362 
unwait()363 void CGroup::unwait() {
364 	std::map<int, CUnit*>::iterator i;
365 	for (i = units.begin(); i != units.end(); ++i)
366 		i->second->unwait();
367 }
368 
attack(int target,bool enqueue)369 void CGroup::attack(int target, bool enqueue) {
370 	std::map<int, CUnit*>::iterator i;
371 	for (i = units.begin(); i != units.end(); ++i)
372 		i->second->attack(target, enqueue);
373 }
374 
build(float3 & pos,UnitType * ut)375 bool CGroup::build(float3& pos, UnitType* ut) {
376 	std::map<int, CUnit*>::iterator alpha, i;
377 	alpha = units.begin();
378 	if (alpha->second->build(ut, pos)) {
379 		for (i = ++alpha; i != units.end(); ++i)
380 			i->second->guard(alpha->first);
381 		return true;
382 	}
383 	return false;
384 }
385 
stop()386 void CGroup::stop() {
387 	std::map<int, CUnit*>::iterator i;
388 	for (i = units.begin(); i != units.end(); ++i)
389 		i->second->stop();
390 }
391 
guard(int target,bool enqueue)392 void CGroup::guard(int target, bool enqueue) {
393 	std::map<int, CUnit*>::iterator i;
394 	for (i = units.begin(); i != units.end(); ++i)
395 		i->second->guard(target, enqueue);
396 }
397 
canTouch(const float3 & goal)398 bool CGroup::canTouch(const float3 &goal) {
399 	return !ai->pathfinder->isBlocked(goal.x, goal.z, pathType);
400 }
401 
canReach(const float3 & goal)402 bool CGroup::canReach(const float3 &goal) {
403 	if (!canTouch(goal))
404 		return false;
405 	if (pathType < 0)
406 		return true;
407 
408 	float3 gpos = pos(true);
409 
410 	return ai->pathfinder->pathExists(*this, gpos, goal);
411 }
412 
canAttack(int uid)413 bool CGroup::canAttack(int uid) {
414 	if ((cats&ATTACKER).none() && !firstUnit()->def->canReclaim)
415 		return false;
416 
417 	const UnitDef *ud = ai->cbc->GetUnitDef(uid);
418 
419 	if (ud == NULL || ai->cbc->IsUnitCloaked(uid))
420 		return false;
421 
422 	const unitCategory ecats = UC(ud->id);
423 	float3 epos = ai->cbc->GetUnitPos(uid);
424 
425 	if ((ecats&AIR).any() && (cats&ANTIAIR).none())
426 		return false;
427 
428 	const float enemyRadiusDiv2 = ai->cb->GetUnitDefRadius(ud->id) / 2.0f;
429 	bool underwater = (epos.y + enemyRadiusDiv2) < 0.0f;
430 	bool outofwater = (epos.y - enemyRadiusDiv2) >= 0.0f;
431 
432 	if (underwater && (cats&TORPEDO).none())
433 		return false;
434 	if (outofwater && (CATS_ENV&cats) == SUB)
435 		return false;
436 
437 	// TODO: add more tweaks based on physical weapon possibilities
438 
439 	return true;
440 }
441 
canAdd(CUnit * unit)442 bool CGroup::canAdd(CUnit* unit) {
443 	return canBeMerged(cats, unit->type->cats);
444 }
445 
canAssist(UnitType * type)446 bool CGroup::canAssist(UnitType* type) {
447 	if (type) {
448 		if (!type->def->canBeAssisted)
449 			return false;
450 		if ((type->cats&(SEA|SUB)).any() && (cats&(SEA|SUB|AIR)).none())
451 			return false;
452 		if ((type->cats&(LAND)).any() && (cats&(LAND|AIR)).none())
453 			return false;
454 	}
455 
456 	std::map<int, CUnit*>::const_iterator i;
457 	for (i = units.begin(); i != units.end(); ++i)
458 		if (i->second->type->def->canAssist)
459 			return true;
460 
461 	return false;
462 }
463 
canMerge(CGroup * group)464 bool CGroup::canMerge(CGroup* group) {
465 	return canBeMerged(cats, group->cats);
466 }
467 
firstUnit()468 CUnit* CGroup::firstUnit() {
469 	if (units.empty())
470 		return NULL;
471 	return units.begin()->second;
472 }
473 
mergeCats(unitCategory newcats)474 void CGroup::mergeCats(unitCategory newcats) {
475 	if (cats == 0)
476 		cats = newcats;
477 	else {
478 		// NOTE: here is a list of cats which can't be left after merging
479 		// if they are absent initially, i.e. LAND + LAND|SEA = LAND
480 		static unitCategory nonXorCats[] = {SEA, SUB, LAND, AIR, STATIC, MOBILE, SCOUTER};
481 		unitCategory oldcats = cats;
482 		cats |= newcats;
483 		for (int i = 0; i < sizeof(nonXorCats) / sizeof(unitCategory); i++) {
484 			unitCategory tag = nonXorCats[i];
485 			if ((oldcats&tag).none() && (cats&tag).any())
486 				cats &= ~tag;
487 		}
488 	}
489 }
490 
getThreat(float3 & target,float radius)491 float CGroup::getThreat(float3& target, float radius) {
492 	return ai->threatmap->getThreat(target, radius, this);
493 }
494 
addBadTarget(int id)495 bool CGroup::addBadTarget(int id) {
496 	const UnitDef* ud = ai->cbc->GetUnitDef(id);
497 	if (ud == NULL)
498 		return false;
499 
500 	LOG_WW("CGroup::addBadTarget " << ud->humanName << "(" << id << ") to " << (*this))
501 
502 	const unitCategory ecats = UC(ud->id);
503 	if ((ecats&STATIC).any())
504 		badTargets[id] = -1;
505 	else
506 		badTargets[id] = ai->cb->GetCurrentFrame();
507 
508 	return true;
509 }
510 
selectTarget(const std::map<int,UnitType * > & targets,TargetsFilter & tf)511 int CGroup::selectTarget(const std::map<int, UnitType*>& targets, TargetsFilter &tf) {
512 	std::vector<int> targetsVec;
513 	targetsVec.reserve(targets.size());
514 	std::map<int, UnitType*>::const_iterator ti = targets.begin();
515 	for (ti = targets.begin(); ti != targets.end(); ++ti) {
516 		targetsVec.push_back(ti->first);
517 	}
518 
519 	return selectTarget(targetsVec, tf);
520 }
521 
selectTarget(const std::vector<int> & targets,TargetsFilter & tf)522 int CGroup::selectTarget(const std::vector<int>& targets, TargetsFilter &tf) {
523 	bool scout = (cats&SCOUTER).any();
524 	bool bomber = !scout && (cats&AIR).any() && (cats&ARTILLERY).any();
525 	int frame = ai->cb->GetCurrentFrame();
526 	float unitDamageK;
527 	float3 gpos = pos();
528 
529 	if (targets.empty() || tf.candidatesLimit == 0)
530 		return tf.bestTarget;
531 
532 	std::vector<int>::const_iterator ti = targets.begin();
533 	for (int i = 0; ti != targets.end() && i < tf.candidatesLimit; ++ti, i++) {
534 		const int t = *ti;
535 
536 		if (!canAttack(t) || (tf.excludeId && (*(tf.excludeId))[t]))
537 			continue;
538 
539 		if (!badTargets.empty()) {
540 			std::map<int, int>::iterator it = badTargets.find(t);
541 			if (it != badTargets.end()) {
542 				if (it->second < 0)
543 					continue; // permanent bad target
544 				if ((frame - it->second) < BAD_TARGET_TIMEOUT)
545 					continue; // temporary bad target
546 				else {
547 					badTargets.erase(it->first);
548 					LOG_II("CGroup::selectTarget bad target Unit(" << t << ") timed out for " << (*this))
549 				}
550 			}
551 		}
552 
553 		const UnitDef* ud = ai->cbc->GetUnitDef(t);
554 		if (ud == NULL)
555 			continue;
556 
557 		const unitCategory ecats = UC(ud->id);
558 		if ((tf.include&ecats).none() || (tf.exclude&ecats).any())
559 			continue;
560 
561 		float3 epos = ai->cbc->GetUnitPos(t);
562 		float threat = getThreat(epos, tf.threatRadius);
563 		if (threat > tf.threatCeiling)
564 			continue;
565 
566 		float unitMaxHealth = ai->cbc->GetUnitMaxHealth(t);
567 		if (unitMaxHealth > EPS)
568 			unitDamageK = (unitMaxHealth - ai->cbc->GetUnitHealth(t)) / unitMaxHealth;
569 		else
570 			unitDamageK = 0.0f;
571 
572 		float score = gpos.distance2D(epos);
573 		score += tf.threatFactor * threat;
574 		score += tf.damageFactor * unitDamageK;
575 		score += tf.powerFactor * ud->power;
576 
577 		// TODO: refactor so params below are moved into TargetFilter?
578 		if (ai->defensematrix->isPosInBounds(epos))
579 			// boost in priority enemy at our base, even scout units
580 			score -= 1000.0f; // TODO: better change value to the length a group can pass for 1 min (40 sec?)?
581 		else if(!scout && (ecats&SCOUTER).any()) {
582 			// remote scouts are not interesting for non-scout groups
583 			score += 10000.0f;
584 		}
585 
586 		if (bomber && (ecats&STATIC).any() && (ecats&ANTIAIR).any())
587 			score -= 500.0f;
588 
589 		// do not allow land units chase after air units...
590 		if ((cats&AIR).none() && (ecats&AIR).any())
591 			score += 3000.0f;
592 
593 		// air fighters prefer aircraft...
594 		if ((cats&AIR).any() && (cats&ANTIAIR).any() && (ecats&AIR).none())
595 			score += 5000.0f;
596 
597 		if (score < tf.scoreCeiling) {
598 			tf.bestTarget = t;
599 			tf.scoreCeiling = score;
600 			tf.threatValue = threat;
601 		}
602 	}
603 
604 	return tf.bestTarget;
605 }
606 
selectTarget(float search_radius,TargetsFilter & tf)607 int CGroup::selectTarget(float search_radius, TargetsFilter &tf) {
608 	int limit = std::min<int>(MAX_ENEMIES, tf.candidatesLimit);
609 	int numEnemies = ai->cbc->GetEnemyUnits(&ai->unitIDs[0], pos(), search_radius, limit);
610 	if (numEnemies > 0) {
611 		tf.candidatesLimit = numEnemies;
612 		tf.bestTarget = selectTarget(ai->unitIDs, tf);
613 	}
614 	return tf.bestTarget;
615 }
616 
getScanRange()617 float CGroup::getScanRange() {
618 	float result = radius();
619 
620 	if ((cats&STATIC).any())
621 		result = getRange();
622 	else if ((cats&BUILDER).any())
623 		result += buildRange * 1.5f;
624 	else if ((cats&SNIPER).any())
625 		result += range * 1.05f;
626 	else if ((cats&SCOUTER).any())
627 		result += range * 3.0f;
628 	else if ((cats&ATTACKER).any())
629 		result += range * 1.4f;
630 
631 	return result;
632 }
633 
getRange() const634 float CGroup::getRange() const {
635 	if ((cats&BUILDER).any())
636 		return buildRange;
637 	return range;
638 }
639 
canBeMerged(unitCategory bcats,unitCategory mcats)640 bool CGroup::canBeMerged(unitCategory bcats, unitCategory mcats) {
641 	static unitCategory nonMergableCats[] = { AIR, LAND, ATTACKER, STATIC, MOBILE, BUILDER, SCOUTER };
642 
643 	if (bcats.none())
644 		return true;
645 
646 	unitCategory c = (bcats&mcats); // common cats between two groups of cats
647 
648 	if (c.none() || (c&CATS_ENV).none())
649 		return false;
650 
651 	// NOTE: if one tag of "nonMergableCats" has been disappeared after logical
652 	// addition then unit or group with "mcats" can't be merged
653 	for (int i = 0; i < sizeof(nonMergableCats) / sizeof(unitCategory); i++) {
654 		unitCategory tag = nonMergableCats[i];
655 		if ((bcats&tag).any() && (c&tag).none())
656 			return false;
657 	}
658 
659 	if ((bcats&SCOUTER).none() && (mcats&SCOUTER).any()) {
660 		// merging scout group with non-scout group...
661 		static unitCategory attackCats = ANTIAIR|ARTILLERY|SNIPER|ASSAULT;
662 		if ((c&attackCats).none())
663 			return false;
664 	}
665 
666 	// NOTE: aircraft units have more restricted merge rules
667 	// TODO: refactor with introducing Group behaviour property?
668 	if ((bcats&AIR).any()) {
669 		if ((bcats&ASSAULT).any() && (c&ASSAULT).none())
670 			return false;
671 		if ((bcats&ARTILLERY).any() && (c&ARTILLERY).none())
672 			return false;
673 	}
674 
675 	return true;
676 }
677 
canPerformTasks()678 bool CGroup::canPerformTasks() {
679 	std::map<int, CUnit*>::iterator i;
680 	for (i = units.begin(); i != units.end(); ++i)
681 		if (!i->second->canPerformTasks())
682 			return false;
683 	return true;
684 }
685 
updateLatecomer(CUnit * unit)686 void CGroup::updateLatecomer(CUnit* unit) {
687 	if (units.size() <= 1)
688 		return; // to less units to register latecomer
689 
690 	if (latecomerUnit && latecomerUnit != unit) {
691 		removeLatecomer();
692 	}
693 
694 	if (latecomerUnit == NULL) {
695 		latecomerUnit = unit;
696 		latecomerPos = unit->pos();
697 		return;
698 	}
699 
700 	float3 newPos = unit->pos();
701 	if (latecomerPos.distance2D(newPos) < 32.0f) {
702 		latecomerWeight++;
703 		if (latecomerWeight > 10) {
704 			LOG_WW("CGroup::updateLatecomer " << unit << " is stuck")
705 			unit->stop();
706 			remove(*unit);
707 			// on idle code will take unit back in action
708 			ai->unittable->unitsUnderPlayerControl[unit->key] = unit;
709 		}
710 	}
711 	else {
712 		latecomerPos = newPos;
713 		latecomerWeight = 0;
714 	}
715 }
716 
removeLatecomer()717 void CGroup::removeLatecomer() {
718 	if (latecomerUnit) {
719 		latecomerUnit = NULL;
720 		latecomerWeight = 0;
721 	}
722 }
723 
operator <<(std::ostream & out,const CGroup & group)724 std::ostream& operator<<(std::ostream &out, const CGroup &group) {
725 	std::stringstream ss;
726 
727 	ss << "Group(" << group.key
728 		<< "): range(" << group.getRange()
729 		<< "), speed(" << group.speed
730 		<< "), strength(" << group.strength
731 		<< "), amount(" << group.units.size() << ") [";
732 
733 	std::map<int, CUnit*>::const_iterator i = group.units.begin();
734 	for (i = group.units.begin(); i != group.units.end(); ++i) {
735 		ss << (*i->second) << ", ";
736 	}
737 	std::string s = ss.str();
738 	s = s.substr(0, s.size() - 2);
739 	s += "]";
740 	out << s;
741 
742 	return out;
743 }
744