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