1 #include <sstream>
2 
3 #include "IncCREG.h"
4 #include "IncExternAI.h"
5 #include "IncGlobalAI.h"
6 
7 #define K_MEANS_ELEVATION 40
8 #define IDLE_GROUP_ID 0
9 #define STUCK_GROUP_ID 1
10 #define AIR_GROUP_ID 2
11 #define GROUND_GROUP_ID_START 1000
12 #define SAFE_SPOT_DISTANCE 300
13 //#define SAFE_SPOT_DISTANCE_SLACK 700
14 #define KMEANS_ENEMY_MAX_K 32
15 #define KMEANS_BASE_MAX_K 32
16 #define KMEANS_MINIMUM_LINE_LENGTH (THREATRES * SQUARE_SIZE)
17 
18 
19 
20 CR_BIND(CAttackHandler, (NULL))
21 CR_REG_METADATA(CAttackHandler, (
22 	CR_MEMBER(ai),
23 
24 	CR_MEMBER(attackUnits),
25 	CR_MEMBER(stuckUnits),
26 	CR_MEMBER(unarmedAirUnits),
27 	CR_MEMBER(armedAirUnits),
28 
29 	CR_MEMBER(airIsAttacking),
30 	CR_MEMBER(airPatrolOrdersGiven),
31 	CR_MEMBER(airTarget),
32 
33 	CR_MEMBER(newGroupID),
34 	CR_MEMBER(attackGroups),
35 	// CR_MEMBER(defenseGroups),
36 
37 	CR_MEMBER(kMeansBase),
38 	CR_MEMBER(kMeansK),
39 	CR_MEMBER(kMeansEnemyBase),
40 	CR_MEMBER(kMeansEnemyK),
41 	CR_RESERVED(16)
42 ))
43 
44 
45 
CAttackHandler(AIClasses * aic)46 CAttackHandler::CAttackHandler(AIClasses* aic): ai(aic) {
47 	if (ai) {
48 		// setting a position to the middle of the map
49 		float mapWidth = ai->cb->GetMapWidth() * SQUARE_SIZE;
50 		float mapHeight = ai->cb->GetMapHeight() * SQUARE_SIZE;
51 		newGroupID = GROUND_GROUP_ID_START;
52 
53 		this->kMeansK = 1;
54 		this->kMeansBase.push_back(float3(mapWidth / 2.0f, K_MEANS_ELEVATION, mapHeight / 2.0f));
55 		this->kMeansEnemyK = 1;
56 		this->kMeansEnemyBase.push_back(float3(mapWidth / 2.0f, K_MEANS_ELEVATION, mapHeight / 2.0f));
57 
58 		UpdateKMeans();
59 	}
60 
61 	airIsAttacking = false;
62 	airPatrolOrdersGiven = false;
63 	airTarget = -1;
64 }
65 
~CAttackHandler(void)66 CAttackHandler::~CAttackHandler(void) {
67 }
68 
69 
AddUnit(int unitID)70 void CAttackHandler::AddUnit(int unitID) {
71 	CUNIT* unit = ai->GetUnit(unitID);
72 
73 	if ((unit->def())->canfly) {
74 		// the groupID of this "group" is 0, to separate them from other idle units
75 		unit->groupID = AIR_GROUP_ID;
76 		// this might be a new unit with the same id as an older dead unit
77 		unit->stuckCounter = 0;
78 
79 		// do some checking then essentially add it to defense group
80 		if ((unit->def())->weapons.size() == 0) {
81 			unarmedAirUnits.push_back(unitID);
82 		} else {
83 			armedAirUnits.push_back(unitID);
84 		}
85 
86 		// patrol orders need to be updated
87 		airPatrolOrdersGiven = false;
88 	} else {
89 		// the groupID of this "group" is 0, to separate them from other idle units
90 		unit->groupID = IDLE_GROUP_ID;
91 		// this might be a new unit with the same id as an older dead unit
92 		unit->stuckCounter = 0;
93 		// do some checking then essentially add it to defense group
94 		attackUnits.push_back(unitID);
95 
96 		// TODO: not that good
97 		PlaceIdleUnit(unitID);
98 	}
99 }
100 
101 
UnitDestroyed(int unitID)102 void CAttackHandler::UnitDestroyed(int unitID) {
103 	int attackGroupID = ai->GetUnit(unitID)->groupID;
104 
105 	// if it's in the defense group
106 	if (attackGroupID == IDLE_GROUP_ID) {
107 		bool foundDeadUnit = false;
108 
109 		for (std::list<int>::iterator it = attackUnits.begin(); it != attackUnits.end(); it++) {
110 			if (*it == unitID) {
111 				attackUnits.erase(it);
112 				foundDeadUnit = true;
113 				break;
114 			}
115 		}
116 
117 		if (foundDeadUnit) {
118 			// one of our (idle) attack units died but
119 			// we somehow have lost track of it before
120 			std::stringstream msg;
121 				msg << "[CAttackHandler::UnitDestroyed()][frame=" << ai->cb->GetCurrentFrame() << "]\n";
122 				msg << "\tidle attack unit " << unitID << " was destroyed but already erased\n";
123 			ai->GetLogger()->Log(msg.str());
124 		}
125 	}
126 
127 	else if (attackGroupID >= GROUND_GROUP_ID_START) {
128 		// unit in an attack-group died
129 		bool foundGroup = false;
130 		bool removedDeadUnit = false;
131 		std::list<CAttackGroup>::iterator it;
132 
133 		for (it = attackGroups.begin(); it != attackGroups.end(); it++) {
134 			if (it->GetGroupID() == attackGroupID) {
135 				removedDeadUnit = it->RemoveUnit(unitID);
136 				foundGroup = true;
137 				break;
138 			}
139 		}
140 
141 		assert(foundGroup);
142 		assert(removedDeadUnit);
143 
144 		// check if the group is now empty
145 		if (it->Size() == 0) {
146 			attackGroups.erase(it);
147 		}
148 	}
149 
150 	else if (attackGroupID == AIR_GROUP_ID) {
151 		// unit in air-group died
152 		bool armed = true;
153 		std::list<int>::iterator unarmedAirIt = unarmedAirUnits.begin();
154 		std::list<int>::iterator armedAirIt = armedAirUnits.begin();
155 
156 		for (; unarmedAirIt != unarmedAirUnits.end(); unarmedAirIt++) {
157 			if (unitID == *unarmedAirIt) {
158 				unarmedAirUnits.erase(unarmedAirIt);
159 				armed = false;
160 				break;
161 			}
162 		}
163 		if (armed) {
164 			for (; armedAirIt != armedAirUnits.end(); armedAirIt++) {
165 				if (unitID == *armedAirIt) {
166 					armedAirUnits.erase(armedAirIt);
167 					break;
168 				}
169 			}
170 		}
171 	}
172 
173 	else {
174 		// unit in stuck-units group
175 		bool found_dead_in_stuck_units = false;
176 		std::list<std::pair<int, float3> >::iterator it;
177 
178 		for (it = stuckUnits.begin(); it != stuckUnits.end(); it++) {
179 			if (it->first == unitID) {
180 				stuckUnits.erase(it);
181 				found_dead_in_stuck_units = true;
182 				break;
183 			}
184 		}
185 		assert(found_dead_in_stuck_units);
186 	}
187 }
188 
189 
PlaceIdleUnit(int unit)190 bool CAttackHandler::PlaceIdleUnit(int unit) {
191 	if (ai->cb->GetUnitDef(unit) != NULL) {
192 		const float3& pos = FindUnsafeArea(ai->cb->GetUnitPos(unit));
193 
194 		if (pos != ZeroVector) {
195             ai->GetUnit(unit)->Move(pos);
196 			return true;
197 		}
198 	}
199 
200 	return false;
201 }
202 
203 
204 // is it ok to build at CBS from this position?
IsSafeBuildSpot(float3 mypos)205 bool CAttackHandler::IsSafeBuildSpot(float3 mypos) {
206 	// TODO: get a subset of the k-means, then
207 	// iterate the lines along the path that they
208 	// make with a min distance slightly lower than
209 	// the area radius definition
210 
211 	mypos = mypos;
212 	return false;
213 }
214 
215 
216 
217 // returns a safe spot from k-means, adjacent to myPos, safety params are (0..1).
218 // change to: decide on the random float 0...1 first, then find it (easier)
FindSafeSpot(float3 myPos,float minSafety,float maxSafety)219 float3 CAttackHandler::FindSafeSpot(float3 myPos, float minSafety, float maxSafety) {
220 	// find a safe spot
221 	myPos = myPos;
222 	int startIndex = int(minSafety * this->kMeansK);
223 	if (startIndex < 0)
224 		startIndex = 0;
225 //	if (startIndex >= kMeansK)
226 //		startIndex = kMeansK - 1;
227 
228 	int endIndex = int(maxSafety * this->kMeansK);
229 	if (endIndex < 0)
230 		startIndex = 0;
231 //	if (endIndex >= kMeansK)
232 //		endIndex = kMeansK - 1;
233 	if (startIndex > endIndex)
234 		startIndex = endIndex;
235 
236 	if (kMeansK <= 1 || startIndex == endIndex) {
237 		if (startIndex >= kMeansK)
238 			startIndex = kMeansK - 1;
239 
240 		float3 pos = kMeansBase[startIndex] + float3((RANDINT % SAFE_SPOT_DISTANCE), 0, (RANDINT % SAFE_SPOT_DISTANCE));
241 		pos.y = ai->cb->GetElevation(pos.x, pos.z);
242 
243 		return pos;
244 	}
245 
246 	assert(startIndex < endIndex);
247 	assert(startIndex < kMeansK);
248 	assert(endIndex <= kMeansK);
249 
250 	// get a subset of the k-means
251 	std::vector<float3> subset;
252 
253 	for (int i = startIndex; i < endIndex; i++) {
254 		// subset[i] = kMeansBase[startIndex + i];
255 		assert(i < kMeansK);
256 		subset.push_back(kMeansBase[i]);
257 	}
258 
259 	// then find a position on one of the lines between those points (pather)
260 	int whichPath;
261 	if (subset.size() > 1) {
262 		whichPath = (RANDINT % (int) subset.size());
263 	} else {
264 		whichPath = 0;
265 	}
266 
267 	assert(whichPath < (int) subset.size());
268 	assert(subset.size() > 0);
269 
270 	if ((whichPath + 1) < (int) subset.size() && subset[whichPath].distance2D(subset[whichPath + 1]) > KMEANS_MINIMUM_LINE_LENGTH) {
271 		std::vector<float3> posPath;
272 
273 		//TODO: implement one in pathfinder without radius (or unit ID)
274 		//	if (size > (int)kMeansBase.size())
275 		//		size = kMeansBase.size();
276 
277 		float dist = ai->pather->MakePath(posPath, subset[whichPath], subset[whichPath + 1], THREATRES * SQUARE_SIZE);
278 		float3 res;
279 
280 		if (dist > 0) {
281 			// dist > 0 from path, use res from pather
282 			int whichPos = RANDINT % (int) posPath.size();
283 			res = posPath[whichPos];
284 		} else {
285 			// dist == 0 from path, using first point
286 			res = subset[whichPath];
287 		}
288 
289 		return res;
290 	} else {
291 		assert(whichPath < (int) subset.size());
292 		return (subset[whichPath]);
293 	}
294 }
295 
296 
FindSafeArea(float3 pos)297 float3 CAttackHandler::FindSafeArea(float3 pos) {
298 	if (this->DistanceToBase(pos) < SAFE_SPOT_DISTANCE)
299 		return pos;
300 
301 	float min = 0.6f;
302 	float max = 0.95f;
303 	float3 safe = this->FindSafeSpot(pos, min, max);
304 	// HACK
305 	safe += pos;
306 	safe /= 2;
307 
308 	return safe;
309 }
310 
FindVerySafeArea(float3 pos)311 float3 CAttackHandler::FindVerySafeArea(float3 pos) {
312 	float min = 0.9f;
313 	float max = 1.0f;
314 	return (FindSafeSpot(pos, min, max));
315 }
316 
FindUnsafeArea(float3 pos)317 float3 CAttackHandler::FindUnsafeArea(float3 pos) {
318 	float min = 0.1f;
319 	float max = 0.3f;
320 	return (FindSafeSpot(pos, min, max));
321 }
322 
323 
DistanceToBase(float3 pos)324 float CAttackHandler::DistanceToBase(float3 pos) {
325 	float closestDistance = MY_FLT_MAX;
326 
327 	for (int i = 0; i < this->kMeansK; i++) {
328 		float3 mean = this->kMeansBase[i];
329 		float distance = pos.distance2D(mean);
330 		closestDistance = std::min(distance, closestDistance);
331 	}
332 
333 	return closestDistance;
334 }
335 
336 
GetClosestBaseSpot(float3 pos)337 float3 CAttackHandler::GetClosestBaseSpot(float3 pos) {
338 	float closestDistance = MY_FLT_MAX;
339 	int index = 0;
340 
341 	for (int i = 0; i < this->kMeansK; i++) {
342 		float3 mean = this->kMeansBase[i];
343 		float distance = pos.distance2D(mean);
344 
345 		if (distance < closestDistance) {
346 			closestDistance = distance;
347 			index = i;
348 		}
349 	}
350 
351 	return kMeansBase[index];
352 }
353 
354 
KMeansIteration(std::vector<float3> means,std::vector<float3> unitPositions,int newK)355 std::vector<float3> CAttackHandler::KMeansIteration(std::vector<float3> means, std::vector<float3> unitPositions, int newK) {
356 	assert(newK > 0 && means.size() > 0);
357 	int numUnits = unitPositions.size();
358 	// change the number of means according to newK
359 	int oldK = means.size();
360 	means.resize(newK);
361 	// add a new means, just use one of the positions
362 	float3 newMeansPosition = unitPositions[0];
363 	newMeansPosition.y = ai->cb->GetElevation(newMeansPosition.x, newMeansPosition.z) + K_MEANS_ELEVATION;
364 
365 	for (int i = oldK; i < newK; i++) {
366 		means[i] = newMeansPosition;
367 	}
368 
369 	// check all positions and assign them to means, complexity n*k for one iteration
370 	std::vector<int> unitsClosestMeanID(numUnits, -1);
371 	std::vector<int> numUnitsAssignedToMean(newK, 0);
372 
373 	for (int i = 0; i < numUnits; i++) {
374 		float3 unitPos = unitPositions.at(i);
375 		float closestDistance = MY_FLT_MAX;
376 		int closestIndex = -1;
377 
378 		for (int m = 0; m < newK; m++) {
379 			float3 mean = means[m];
380 			float distance = unitPos.distance2D(mean);
381 
382 			if (distance < closestDistance) {
383 				closestDistance = distance;
384 				closestIndex = m;
385 			}
386 		}
387 
388 		// position i is closest to the mean at closestIndex
389 		unitsClosestMeanID[i] = closestIndex;
390 		numUnitsAssignedToMean[closestIndex]++;
391 	}
392 
393 	// change the means according to which positions are assigned to them
394 	// use meanAverage for indexes with 0 pos'es assigned
395 	// make a new means list
396 	std::vector<float3> newMeans(newK, float3(0, 0, 0));
397 
398 	for (int i = 0; i < numUnits; i++) {
399 		int meanIndex = unitsClosestMeanID[i];
400 		 // don't divide by 0
401 		float num = std::max(1, numUnitsAssignedToMean[meanIndex]);
402 		newMeans[meanIndex] += unitPositions[i] / num;
403 	}
404 
405 	// do a check and see if there are any empty means and set the height
406 	for (int i = 0; i < newK; i++) {
407 		// if a newmean is unchanged, set it to the new means pos instead of (0, 0, 0)
408 		if (newMeans[i] == float3(0, 0, 0)) {
409 			newMeans[i] = newMeansPosition;
410 		}
411 		else {
412 			// get the proper elevation for the y-coord
413 			newMeans[i].y = ai->cb->GetElevation(newMeans[i].x, newMeans[i].z) + K_MEANS_ELEVATION;
414 		}
415 	}
416 
417 	return newMeans;
418 }
419 
420 
UpdateKMeans(void)421 void CAttackHandler::UpdateKMeans(void) {
422 	// we want local variable definitions
423 	{
424 		// get positions of all friendly units and put them in a vector (completed buildings only)
425 		std::vector<float3> friendlyPositions;
426 		int numFriendlies = ai->cb->GetFriendlyUnits(&ai->unitIDs[0]);
427 
428 		for (int i = 0; i < numFriendlies; i++) {
429 			CUNIT* u = ai->GetUnit(ai->unitIDs[i]);
430 
431 			// its a building, it has hp, and its mine (0)
432 			if (UnitBuildingFilter(u->def()) && UnitReadyFilter(u->uid) && u->owner() == 0) {
433 				friendlyPositions.push_back(u->pos());
434 			}
435 		}
436 
437 		// hack to make it at least 1 unit, should only happen when you have no base
438 		if (friendlyPositions.empty()) {
439 			// it has to be a proper position, unless there are no proper positions
440 			// in the latter case, use the position of the unit with ID 0 (?)
441 			// HOIJUI_NOTE - isn't it simply the first unit in the list?
442 			const CUNIT*   unit    = NULL;
443 			const UnitDef* unitDef = NULL;
444 			if (ai->unitIDs[0] != -1) {
445 				unit    = ai->GetUnit(ai->unitIDs[0]);
446 				unitDef = ai->cb->GetUnitDef(ai->unitIDs[0]);
447 			}
448 
449 			if (numFriendlies > 0 && unitDef != NULL && unit != NULL && unit->owner() == 0) {
450 				friendlyPositions.push_back(unit->pos());
451 			} else {
452 				// when everything is dead
453 				friendlyPositions.push_back(float3(RANDINT % (ai->cb->GetMapWidth() * SQUARE_SIZE), 1000, RANDINT % (ai->cb->GetMapHeight() * SQUARE_SIZE)));
454 			}
455 		}
456 
457 		// calculate a new K. change the formula to adjust max K, needs to be 1 minimum.
458 		kMeansK = int(std::min((float) (KMEANS_BASE_MAX_K), 1.0f + sqrtf((float) numFriendlies + 0.01f)));
459 		// iterate k-means algo over these positions and move the means
460 		kMeansBase = KMeansIteration(kMeansBase, friendlyPositions, kMeansK);
461 	}
462 
463 	// update enemy position k-means
464 	// get positions of all enemy units and put them in a vector (completed buildings only)
465 	std::vector<float3> enemyPositions;
466 	const int numEnemies = ai->ccb->GetEnemyUnits(&ai->unitIDs[0]);
467 
468 	for (int i = 0; i < numEnemies; i++) {
469 		const UnitDef* ud = ai->ccb->GetUnitDef(ai->unitIDs[i]);
470 
471 		if (UnitBuildingFilter(ud)) {
472 			// if (this->UnitReadyFilter(unit)) { ... }
473 			enemyPositions.push_back(ai->ccb->GetUnitPos(ai->unitIDs[i]));
474 		}
475 	}
476 
477 	// hack to make it at least 1 unit, should only happen when you have no base
478 	if (enemyPositions.size() < 1) {
479 		// it has to be a proper position, unless there are no proper positions
480 		if (numEnemies > 0 && ai->ccb->GetUnitDef(ai->unitIDs[0])) {
481 			enemyPositions.push_back(ai->ccb->GetUnitPos(ai->unitIDs[0]));
482 		}
483 		else {
484 			// when everything is dead
485 			enemyPositions.push_back(float3(RANDINT % (ai->cb->GetMapWidth() * SQUARE_SIZE), 1000, RANDINT % (ai->cb->GetMapHeight() * SQUARE_SIZE)));
486 		}
487 	}
488 
489 	// calculate a new K. change the formula to adjust max K, needs to be 1 minimum
490 	this->kMeansEnemyK = int(std::min(float(KMEANS_ENEMY_MAX_K), 1.0f + sqrtf((float) numEnemies + 0.01f)));
491 
492 	// iterate k-means algo over these positions and move the means
493 	this->kMeansEnemyBase = KMeansIteration(this->kMeansEnemyBase, enemyPositions, this->kMeansEnemyK);
494 
495 	// base k-means and enemy base k-means are updated
496 	// approach: add up (max - distance) to enemies
497 	std::vector<float> proximity(kMeansK, 0.0000001f);
498 	const float mapDiagonal = sqrt(pow((float) ai->cb->GetMapHeight() * SQUARE_SIZE, 2) + pow((float) ai->cb->GetMapWidth() * SQUARE_SIZE, 2) + 1.0f);
499 
500 	for (int f = 0; f < kMeansK; f++) {
501 		for (int e = 0; e < kMeansEnemyK; e++) {
502 			proximity[f] += mapDiagonal - kMeansBase[f].distance2D(kMeansEnemyBase[e]);
503 		}
504 	}
505 
506 	// sort kMeans by the proximity score
507 	float3 tempPos;
508 	float temp;
509 
510 	for (int i = 1; i < kMeansK; i++) {
511 		for (int j = 0; j < i; j++) {
512 			// switch
513 			if (proximity[i] > proximity[j]) {
514 				tempPos = kMeansBase[i];
515 				kMeansBase[i] = kMeansBase[j];
516 				kMeansBase[j] = tempPos;
517 				temp = proximity[i];
518 				proximity[i] = proximity[j];
519 				proximity[j] = temp;
520 			}
521 		}
522 	}
523 
524 	// now we have a kMeans list sorted by distance
525 	// to enemies, 0 being risky and k being safest
526 }
527 
528 
UnitGroundAttackFilter(int unitID)529 bool CAttackHandler::UnitGroundAttackFilter(int unitID) {
530 	const CUNIT*   unit    = ai->GetUnit(unitID);
531 	const UnitDef* unitDef = unit->def();
532 
533 	return ((unitDef != NULL) && (unitDef->canmove) && (unit->category() == CAT_G_ATTACK));
534 }
535 
UnitBuildingFilter(const UnitDef * ud)536 bool CAttackHandler::UnitBuildingFilter(const UnitDef* ud) {
537 	return ((ud != NULL) && (ud->speed <= 0));
538 }
539 
UnitReadyFilter(int unitID)540 bool CAttackHandler::UnitReadyFilter(int unitID) {
541 	const CUNIT*   unit    = ai->GetUnit(unitID);
542 	const UnitDef* unitDef = unit->def();
543 
544 	return (
545 		(unitDef != NULL) &&
546 		(!ai->cb->UnitBeingBuilt(unitID)) &&
547 		((ai->cb->GetUnitHealth(unitID)) > (ai->cb->GetUnitMaxHealth(unitID) * 0.8f))
548 	);
549 }
550 
551 
552 
553 
554 
AirAttack(int currentFrame)555 void CAttackHandler::AirAttack(int currentFrame) {
556 	int numEnemies = ai->ccb->GetEnemyUnits(&ai->unitIDs[0]);
557 	int bestTargetID = -1;
558 	float bestTargetCost = -1.0f;
559 
560 	// TODO: if enemy has antinuke and we have nuke, etc
561 	for (int i = 0; i < numEnemies; i++) {
562 		int enemyID = ai->unitIDs[i];
563 		const UnitDef* udef = (enemyID >= 0)? ai->ccb->GetUnitDef(enemyID): 0;
564 
565 		if (udef) {
566 			float mCost = udef->metalCost;
567 			float eCost = udef->energyCost;
568 			float baseCost = mCost + eCost * 0.1f;
569 			bool isStaticTarget = (udef->speed < 0.1f);
570 			float targetCost = isStaticTarget? baseCost: baseCost * 0.01f;
571 
572 			if (targetCost > bestTargetCost) {
573 				bestTargetID = enemyID;
574 				bestTargetCost = targetCost;
575 			}
576 		}
577 	}
578 
579 	if (bestTargetID != -1) {
580 		// attack en-masse, regardless of AA
581 		for (std::list<int>::iterator it = armedAirUnits.begin(); it != armedAirUnits.end(); it++) {
582 			ai->GetUnit(*it)->Attack(bestTargetID);
583 		}
584 
585 		airIsAttacking = true;
586 		airTarget = bestTargetID;
587 	}
588 }
589 
AirPatrol(int currentFrame)590 void CAttackHandler::AirPatrol(int currentFrame) {
591 	// get and make up some outer base perimeter
592 	// points for air patrol route updates (if we
593 	// aren't attacking)
594 	std::vector<float3> outerMeans;
595 	const unsigned int numClusters = 3;
596 	outerMeans.reserve(numClusters);
597 
598 	if (kMeansK > 1) {
599 		// offset the outermost one
600 		int counter = (kMeansK / 8);
601 
602 		for (unsigned int i = 0; i < numClusters; i++) {
603 			outerMeans.push_back(kMeansBase[counter]);
604 
605 			if (counter < kMeansK - 1)
606 				counter++;
607 		}
608 	} else {
609 		// there is just 1 k-means cluster and we need three
610 		for (unsigned int i = 0; i < numClusters; i++) {
611 			outerMeans.push_back(kMeansBase[0] + float3(250 * i, 0, 0));
612 		}
613 	}
614 
615 	if (outerMeans.size() < numClusters) {
616 		// there were two kMeansK clusters?
617 		return;
618 	}
619 
620 	for (std::list<int>::iterator it = armedAirUnits.begin(); it != armedAirUnits.end(); it++) {
621 		CUNIT* u = ai->GetUnit(*it);
622 
623 		// do this first in case we are in the enemy base
624 		u->Move(outerMeans[0] + float3(0, 50, 0));
625 
626 		for (unsigned int i = 0; i < outerMeans.size(); i++) {
627 			u->PatrolShift(outerMeans[i]);
628 		}
629 	}
630 
631 	airPatrolOrdersGiven = true;
632 }
633 
634 
UpdateAir(int currentFrame)635 void CAttackHandler::UpdateAir(int currentFrame) {
636 	airIsAttacking = (airIsAttacking && !armedAirUnits.empty());
637 
638 	if (airIsAttacking) {
639 		if (airTarget == -1) {
640 			// we are attacking an invalid target, or
641 			// have no more attack-capable planes left
642 			airIsAttacking = false;
643 		} else {
644 			// if we are attacking but our target is dead
645 			if (ai->ccb->GetUnitHealth(airTarget) <= 0.0f) {
646 				airIsAttacking = false;
647 				airTarget = -1;
648 			}
649 		}
650 	}
651 
652 	if (!airIsAttacking) {
653 		if (armedAirUnits.size() >= 16) {
654 			// start or continue attacking
655 			// if we have 16 or more armed
656 			// planes and no target
657 			AirAttack(currentFrame);
658 		} else {
659 			// return to base
660 			airTarget = -1;
661 		}
662 	}
663 
664 
665 	if (currentFrame % 1800 == 0) {
666 		// clear patrol orders every 60 seconds
667 		airPatrolOrdersGiven = false;
668 	}
669 
670 	if (!airPatrolOrdersGiven && !airIsAttacking) {
671 		AirPatrol(currentFrame);
672 	}
673 }
674 
UpdateSea(int currentFrame)675 void CAttackHandler::UpdateSea(int currentFrame) {
676 	// TODO
677 }
678 
679 
680 
UpdateNukeSilos(int currentFrame)681 void CAttackHandler::UpdateNukeSilos(int currentFrame) {
682 	if ((currentFrame % 300) == 0 && ai->uh->NukeSilos.size() > 0) {
683 		std::vector<std::pair<int, float> > potentialTargets;
684 		GetNukeSiloTargets(potentialTargets);
685 
686 		for (std::list<NukeSilo>::iterator i = ai->uh->NukeSilos.begin(); i != ai->uh->NukeSilos.end(); i++) {
687 			NukeSilo* silo = &*i;
688 
689 			if (silo->numNukesReady > 0) {
690 				int targetID = PickNukeSiloTarget(potentialTargets);
691 
692 				if (targetID != -1) {
693 					ai->GetUnit(silo->id)->Attack(targetID);
694 				}
695 			}
696 		}
697 	}
698 }
699 
700 // pick a nuke-silo target from a vector of potential ones
701 // (if there are more than MAX_NUKE_SILOS/2 targets to choose
702 // from, pick one of the first <MAX_NUKE_SILOS/2>, else pick
703 // from the full size of the vector)
PickNukeSiloTarget(std::vector<std::pair<int,float>> & potentialTargets)704 int CAttackHandler::PickNukeSiloTarget(std::vector<std::pair<int, float> >& potentialTargets) {
705 	int s = potentialTargets.size();
706 	int n = ((s > (MAX_NUKE_SILOS >> 1))? (MAX_NUKE_SILOS >> 1): s);
707 	return ((s > 0)? potentialTargets[RANDINT % n].first: -1);
708 }
709 
710 
ComparePairs(const std::pair<int,float> & l,const std::pair<int,float> & r)711 inline bool ComparePairs(const std::pair<int, float>& l, const std::pair<int, float>& r) {
712 	return (l.second > r.second);
713 }
714 
715 // sort all enemy targets in decreasing order by unit value
GetNukeSiloTargets(std::vector<std::pair<int,float>> & potentialTargets)716 void CAttackHandler::GetNukeSiloTargets(std::vector<std::pair<int, float> >& potentialTargets) {
717 	int numEnemies = ai->ccb->GetEnemyUnits(&ai->unitIDs[0]);
718 	float minTargetValue = 500.0f;
719 
720 	std::vector<std::pair<int, float> > staticTargets;
721 	std::vector<std::pair<int, float> > mobileTargets;
722 
723 	for (int i = 0; i < numEnemies; i++) {
724 		int targetID = ai->unitIDs[i];
725 		const UnitDef* udef = ai->ccb->GetUnitDef(targetID);
726 
727 		if (udef) {
728 			float mCost = ai->ccb->GetUnitDef(targetID)->metalCost;
729 			float eCost = ai->ccb->GetUnitDef(targetID)->energyCost;
730 			float targetValue = mCost + eCost * 0.1f;
731 			bool isMobileTarget = (udef->speed > 0.0f);
732 
733 			if (targetValue > minTargetValue) {
734 				// don't waste nukes on radar towers
735 				if (isMobileTarget) {
736 					mobileTargets.push_back(std::make_pair(targetID, targetValue));
737 				} else {
738 					staticTargets.push_back(std::make_pair(targetID, targetValue));
739 				}
740 			}
741 		}
742 	}
743 
744 	std::sort(staticTargets.begin(), staticTargets.end(), &ComparePairs);
745 	std::sort(mobileTargets.begin(), mobileTargets.end(), &ComparePairs);
746 
747 	// copy over all static targets
748 	for (unsigned int i = 0; i < staticTargets.size(); i++) {
749 		potentialTargets.push_back(staticTargets[i]);
750 	}
751 
752 	// if there weren't any static targets
753 	// then copy over all the mobile ones
754 	if (staticTargets.size() == 0) {
755 		for (unsigned int i = 0; i < mobileTargets.size(); i++) {
756 			potentialTargets.push_back(mobileTargets[i]);
757 		}
758 	}
759 }
760 
761 
762 
763 
764 
765 
AssignTarget(CAttackGroup * group_in)766 void CAttackHandler::AssignTarget(CAttackGroup* group_in) {
767 	int numEnemies = ai->ccb->GetEnemyUnits(&ai->unitIDs[0]);
768 
769 	if (numEnemies > 0) {
770 		std::vector<int> allEligibleEnemies;
771 		allEligibleEnemies.reserve(numEnemies);
772 
773 		// make a vector with the positions of all
774 		// non-air and non-cloaked (non-dead) enemies
775 		for (int i = 0; i < numEnemies; i++) {
776 			if (ai->unitIDs[i] != -1) {
777 				const UnitDef* ud = ai->ccb->GetUnitDef(ai->unitIDs[i]);
778 
779 				if (ud) {
780 					bool canFly = ud->canfly;
781 					bool isCloaked = ud->canCloak && ud->startCloaked;
782 					bool goodPos = !(ai->ccb->GetUnitPos(ai->unitIDs[i]) == ZeroVector);
783 
784 					if (!canFly && !isCloaked && goodPos) {
785 						allEligibleEnemies.push_back(ai->unitIDs[i]);
786 					}
787 				}
788 			}
789 		}
790 
791 		std::vector<int> availableEnemies;
792 		std::vector<float3> enemyPositions;
793 		availableEnemies.reserve(allEligibleEnemies.size());
794 
795 		// make a list of all enemies already assigned to (non-defending) groups
796 		std::list<int> takenEnemies;
797 		for (std::list<CAttackGroup>::iterator groupIt = attackGroups.begin(); groupIt != attackGroups.end(); groupIt++) {
798 			if ((!groupIt->defending) && (groupIt->GetGroupID() != group_in->GetGroupID())) {
799 				std::list<int> assignedEnemies = groupIt->GetAssignedEnemies();
800 				takenEnemies.merge(assignedEnemies);
801 			}
802 		}
803 
804 		// filter out assigned enemies from eligible enemies
805 		for (std::vector<int>::iterator enemy = allEligibleEnemies.begin(); enemy != allEligibleEnemies.end(); enemy++) {
806 			int enemyID = *enemy;
807 			bool taken = false;
808 
809 			for (std::list<int>::iterator it = takenEnemies.begin(); it != takenEnemies.end(); it++) {
810 				if (*it == enemyID) {
811 					taken = true;
812 					break;
813 				}
814 			}
815 
816 			if (!taken) {
817 				availableEnemies.push_back(enemyID);
818 				enemyPositions.push_back(ai->ccb->GetUnitPos(enemyID));
819 			}
820 		}
821 
822 		if (availableEnemies.size() == 0) {
823 			return;
824 		}
825 
826 		// find cheapest (best) target for this group
827 		std::vector<float3> pathToTarget;
828 		float3 groupPos = group_in->GetGroupPos();
829 
830 		ai->pather->micropather->SetMapData(
831 			ai->pather->MoveArrays[group_in->GetWorstMoveType()],
832 			ai->thm->GetThreatArray(),
833 			ai->thm->GetThreatMapWidth(),
834 			ai->thm->GetThreatMapHeight()
835 		);
836 
837 
838 		// pick an enemy position and path to it
839 		// KLOOTNOTE: should be more like KAI 0.23 by passing group DPS to FindBestPath()
840 		ai->pather->FindBestPath(pathToTarget, groupPos, THREATRES * SQUARE_SIZE, enemyPositions);
841 
842 
843 
844 		if (pathToTarget.size() > 2) {
845 			const int ATTACKED_AREA_RADIUS = 800;
846 			const float3 endPos = pathToTarget[pathToTarget.size() - 1];
847 
848 			// get all enemies surrounding endpoint of found path
849 			int enemiesInArea = ai->ccb->GetEnemyUnits(&ai->unitIDs[0], endPos, ATTACKED_AREA_RADIUS);
850 			float powerOfEnemies = 0.000001f;
851 
852 			// calculate combined "firepower" of armed enemies near endpoint
853 			for (int i = 0; i < enemiesInArea; i++) {
854 				if (ai->ccb->GetUnitDef(ai->unitIDs[i])->weapons.size() > 0) {
855 					powerOfEnemies += ai->ccb->GetUnitPower(ai->unitIDs[i]);
856 				}
857 			}
858 
859 			if ((enemiesInArea > 0) && group_in->Size() >= 4 && (group_in->Power() > powerOfEnemies * 1.25f)) {
860 				// assign target to this group
861 				group_in->AssignTarget(pathToTarget, pathToTarget.back(), ATTACKED_AREA_RADIUS);
862 			} else {
863 				// group too weak, forget about this target
864 				group_in->ClearTarget();
865 			}
866 		}
867 	}
868 }
869 
870 
AssignTargets(int frameNr)871 void CAttackHandler::AssignTargets(int frameNr) {
872 	if (frameNr % 120 == 0) {
873 		// for each attack-group check whether it needs new target, if so assign one
874 		for (std::list<CAttackGroup>::iterator it = attackGroups.begin(); it != attackGroups.end(); it++) {
875 			CAttackGroup* group = &(*it);
876 			// force group target updates every 300 frames
877 			if (group->NeedsNewTarget() || frameNr % 300 == 0) {
878 				AssignTarget(group);
879 			}
880 		}
881 	}
882 }
883 
884 
885 
CombineGroups(void)886 void CAttackHandler::CombineGroups(void) {
887 	bool removedSomething = false;
888 
889 	// pick a group A
890 	for (std::list<CAttackGroup>::iterator groupA = attackGroups.begin(); groupA != attackGroups.end(); groupA++) {
891 		// if it is defending
892 		if (groupA->defending) {
893 			int groupAid = groupA->GetGroupID();
894 			float3 groupApos = groupA->GetGroupPos();
895 			// look for other groups that are defending
896 			for (std::list<CAttackGroup>::iterator groupB = attackGroups.begin(); groupB != attackGroups.end(); groupB++) {
897 				// if they are close, combine
898 				float3 groupBpos = groupB->GetGroupPos();
899 				int groupBid = groupB->GetGroupID();
900 
901 				if ((groupB->defending) && (groupAid != groupBid) && (groupApos.distance2D(groupBpos) < 1500)) {
902 					std::vector<int>* bUnits = groupB->GetAllUnits();
903 
904 					for (std::vector<int>::iterator groupBunit = bUnits->begin(); groupBunit != bUnits->end(); groupBunit++) {
905 						groupA->AddUnit(*groupBunit);
906 					}
907 
908 					this->attackGroups.erase(groupB);
909 					removedSomething = true;
910 					break;
911 				}
912 			}
913 		}
914 
915 		if (removedSomething)
916 			break;
917 	}
918 }
919 
920 
921 
922 
Update(int frameNr)923 void CAttackHandler::Update(int frameNr) {
924 	int frameSpread = 300;
925 
926 	if (frameNr < 2)
927 		UpdateKMeans();
928 
929 	// set map data here so it doesn't have to be done
930 	// in each group (movement map PATHTOUSE is hack)
931 	ai->pather->micropather->SetMapData(
932 		ai->pather->MoveArrays[PATHTOUSE],
933 		ai->thm->GetThreatArray(),
934 		ai->thm->GetThreatMapWidth(),
935 		ai->thm->GetThreatMapHeight()
936 	);
937 
938 	// calculate and draw k-means for the base perimeters every 10 seconds
939 	if (frameNr % frameSpread == 0) {
940 		UpdateKMeans();
941 
942 		int num = ai->uh->NumIdleUnits(CAT_G_ATTACK);
943 
944 		for (int i = 0; i < num; i++) {
945 			int unit = ai->uh->GetIU(CAT_G_ATTACK);
946 
947 			if (PlaceIdleUnit(unit) && !ai->cb->GetUnitDef(unit)->canfly)  {
948 				ai->uh->IdleUnitRemove(unit);
949 			}
950 		}
951 	}
952 
953 	// check for stuck units in each attack group every second
954 	if (frameNr % 30 == 0) {
955 		for (std::list<CAttackGroup>::iterator it = attackGroups.begin(); it != attackGroups.end(); it++) {
956 			int stuckUnitID = it->PopStuckUnit();
957 
958 			if (stuckUnitID != -1 && ai->cb->GetUnitDef(stuckUnitID) != NULL) {
959 				std::pair<int, float3> foo;
960 					foo.first = stuckUnitID;
961 					foo.second = ai->cb->GetUnitPos(stuckUnitID);
962 				stuckUnits.push_back(foo);
963 				// popped a stuck unit from attack group it->GetGroupID()
964 				ai->GetUnit(stuckUnitID)->Stop();
965 				ai->GetUnit(stuckUnitID)->groupID = STUCK_GROUP_ID;
966 			}
967 
968 			// if attack group now empty then kill it
969 			if (it->Size() == 0) {
970 				attackGroups.erase(it);
971 				break;
972 			}
973 		}
974 	}
975 
976 	// combine groups that are defending and too weak to attack anything
977 	if (frameNr % frameSpread == 0) {
978 		CombineGroups();
979 	}
980 
981 	// check if we have any new units, add them to a
982 	// nearby defending group of less than 16 units
983 	if (frameNr % 30 == 0 && attackUnits.size() > 0) {
984 		CAttackGroup* existingGroup = NULL;
985 		for (std::list<CAttackGroup>::iterator it = attackGroups.begin(); it != attackGroups.end(); it++) {
986 			if (it->Size() < 16 && it->defending && this->DistanceToBase(it->GetGroupPos()) < 300) {
987 				// KLOOTNOTE: pick the first valid group, not the last
988 				existingGroup = &(*it);
989 				break;
990 			}
991 		}
992 
993 		if (existingGroup != NULL) {
994 			// add all new units to found group
995 			for (std::list<int>::iterator it = attackUnits.begin(); it != attackUnits.end(); it++) {
996 				int unit = *it;
997 
998 				if (ai->cb->GetUnitDef(unit) != NULL) {
999 					existingGroup->AddUnit(unit);
1000 				}
1001 			}
1002 
1003 			attackUnits.clear();
1004 		}
1005 		else {
1006 			// no suitable group found, make new defending one
1007 			newGroupID++;
1008 			CAttackGroup newGroup(ai, newGroupID);
1009 			newGroup.defending = true;
1010 
1011 			for (std::list<int>::iterator it = attackUnits.begin(); it != attackUnits.end(); it++) {
1012 				int unit = *it;
1013 				if (ai->cb->GetUnitDef(unit) != NULL) {
1014 					newGroup.AddUnit(unit);
1015 				}
1016 			}
1017 
1018 			attackUnits.clear();
1019 			attackGroups.push_back(newGroup);
1020 		}
1021 	}
1022 
1023 
1024 	// do basic attack group formation from defense units
1025 	UpdateAir(frameNr);
1026 	UpdateSea(frameNr);
1027 	UpdateNukeSilos(frameNr);
1028 	AssignTargets(frameNr);
1029 
1030 	// update current groups
1031 	for (std::list<CAttackGroup>::iterator it = attackGroups.begin(); it != attackGroups.end(); it++) {
1032 		it->Update(frameNr);
1033 	}
1034 }
1035