1 #include <string>
2 #include <sstream>
3
4 #include "System/Util.h"
5
6 #include "IncExternAI.h"
7 #include "IncGlobalAI.h"
8
CMetalMap(AIClasses * ai)9 CMetalMap::CMetalMap(AIClasses* ai) {
10 this->ai = ai;
11
12 // from 0-255, the minimum percentage of metal a spot needs to have from
13 // the maximum to be saved, prevents crappier spots in between taken spaces
14 // (they are still perfectly valid and will generate metal mind you!)
15 MinMetalForSpot = 50;
16 // if more spots than that are found the map is considered a metalmap, tweak this as needed
17 MaxSpots = 10000;
18
19 // metal map has 1/2 resolution of normal map
20 MetalMapHeight = ai->cb->GetMapHeight() / 2;
21 MetalMapWidth = ai->cb->GetMapWidth() / 2;
22
23 TotalCells = MetalMapHeight * MetalMapWidth;
24 XtractorRadiusOrg = ai->cb->GetExtractorRadius();
25 XtractorRadius = int(ai->cb->GetExtractorRadius() / 16.0f);
26 DoubleRadius = XtractorRadius * 2;
27 SquareRadius = XtractorRadius * XtractorRadius;
28 DoubleSquareRadius = DoubleRadius * DoubleRadius;
29
30 MexArrayA = new unsigned char [TotalCells];
31 MexArrayB = new unsigned char [TotalCells];
32 // used for drawing the TGA, not really needed with a couple of changes
33 MexArrayC = new unsigned char [TotalCells];
34
35 TempAverage = new int [TotalCells];
36 TotalMetal = MaxMetal = NumSpotsFound = 0;
37 Stopme = false;
38 }
39
~CMetalMap()40 CMetalMap::~CMetalMap() {
41 delete[] MexArrayA;
42 delete[] MexArrayB;
43 delete[] MexArrayC;
44 delete[] TempAverage;
45 }
46
47 // KLOOTNOTE: this needs to ignore spots already taken by allies
GetNearestMetalSpot(int builderid,const UnitDef * extractor)48 float3 CMetalMap::GetNearestMetalSpot(int builderid, const UnitDef* extractor) {
49 float TempScore = 0.0f;
50 float MaxDivergence = 16.0f;
51 float3 spotCoords = ERRORVECTOR;
52 float3 bestSpot = ERRORVECTOR;
53
54 if (VectoredSpots.size()) {
55 for (unsigned int i = 0; i != VectoredSpots.size(); i++) {
56 spotCoords = ai->cb->ClosestBuildSite(extractor, VectoredSpots[i], MaxDivergence, 2);
57
58 if (spotCoords.x >= 0.0f) {
59 float distance = spotCoords.distance2D(ai->cb->GetUnitPos(builderid)) + 150;
60 float myThreat = ai->thm->ThreatAtThisPoint(spotCoords);
61 float spotScore = VectoredSpots[i].y / distance / (myThreat + 10);
62
63 // along with threatmap try to search for enemy armed units around cause
64 // there could be armored MEX nearby which gives the highest threat anyway...
65 int numEnemies = ai->ccb->GetEnemyUnits(&ai->unitIDs[0], spotCoords, XtractorRadiusOrg * 1.5f);
66 while(numEnemies > 0)
67 {
68 numEnemies--;
69 const UnitDef* ud = ai->ccb->GetUnitDef(ai->unitIDs[numEnemies]);
70 if(ud && !ud->weapons.empty())
71 {
72 numEnemies++;
73 break;
74 }
75 }
76
77 bool bOccupied = false; // flag: metal spot is occupied by allied unit
78 if(NumSpotsFound < 100)
79 {
80 int numAllies = ai->cb->GetFriendlyUnits(&ai->unitIDs[0], spotCoords, XtractorRadiusOrg * 1.5f);
81 for(unsigned int j = 0; j < numAllies; j++)
82 {
83 if(ai->ut->GetCategory(ai->unitIDs[j]) == CAT_MEX)
84 {
85 bOccupied = true;
86 break;
87 }
88 }
89 }
90
91 // NOTE: threat at spotCoords is determined
92 // by presence of ARMED enemy units or buildings
93 bool b1 = (TempScore < spotScore);
94 bool b2 = (numEnemies == 0);
95 bool b3 = (myThreat <= (ai->thm->GetAverageThreat() * 1.5));
96 bool b4 = (ai->uh->TaskPlanExist(spotCoords, extractor));
97
98 if (b1 && b2 && b3 && !b4 && !bOccupied) {
99 TempScore = spotScore;
100 bestSpot = spotCoords;
101 bestSpot.y = VectoredSpots[i].y;
102 }
103 }
104 }
105 }
106
107 // no spot found if TempScore is zero
108 return bestSpot;
109 }
110
Init()111 void CMetalMap::Init() {
112 //const int frame = ai->cb->GetCurrentFrame();
113
114 // leave this line if you want to use this class in your AI
115 ai->cb->SendTextMsg("KAI Metal Class by Krogothe", 0);
116
117 // if there's no available load file, create one and save it
118 if (!LoadMetalMap()) {
119 GetMetalPoints();
120 SaveMetalMap();
121
122 // std::string mapname = std::string("Metal - ") + ai->cb->GetMapName();
123 // mapname.resize(mapname.size() - 4);
124 // ai->debug->MakeBWTGA(MexArrayC, MetalMapWidth, MetalMapHeight, mapname);
125 }
126
127 std::stringstream msg;
128 msg << "[CMetalMap::Init()] number of metal spots found: " << NumSpotsFound << "\n";
129 ai->GetLogger()->Log(msg.str());
130 }
131
132
GetMetalPoints()133 void CMetalMap::GetMetalPoints() {
134 int* xend = new int[DoubleRadius + 1];
135
136 for (int a = 0; a < DoubleRadius + 1; a++) {
137 float z = a - XtractorRadius;
138 float floatsqrradius = SquareRadius;
139 xend[a] = int(sqrtf(floatsqrradius - z * z));
140 }
141
142 // load up the metal values in each pixel
143 const unsigned char* metalMapArray = ai->cb->GetMetalMap();
144 double TotalMetalDouble = 0;
145
146 for (int i = 0; i < TotalCells; i++) {
147 // count the total metal so you can work out an average of the whole map
148 TotalMetalDouble += MexArrayA[i] = metalMapArray[i];
149 }
150
151 // do the average
152 AverageMetal = TotalMetalDouble / TotalCells;
153
154 // quick test for no metal map:
155 if (TotalMetalDouble < 0.9) {
156 // the map doesn't have any metal, just stop
157 NumSpotsFound = 0;
158 delete[] xend;
159 return;
160 }
161
162 // Now work out how much metal each spot can make by adding up the metal from nearby spots
163 for (int y = 0; y < MetalMapHeight; y++) {
164 for (int x = 0; x < MetalMapWidth; x++) {
165 TotalMetal = 0;
166
167 // first spot needs full calculation
168 if (x == 0 && y == 0)
169 for (int sy = y - XtractorRadius, a = 0; sy <= y + XtractorRadius; sy++, a++) {
170 if (sy >= 0 && sy < MetalMapHeight){
171 for (int sx = x - xend[a]; sx <= x + xend[a]; sx++) {
172 if (sx >= 0 && sx < MetalMapWidth) {
173 // get the metal from all pixels around the extractor radius
174 TotalMetal += MexArrayA[sy * MetalMapWidth + sx];
175 }
176 }
177 }
178 }
179
180 // quick calc test
181 if (x > 0) {
182 TotalMetal = TempAverage[y * MetalMapWidth + x - 1];
183 for (int sy = y - XtractorRadius, a = 0; sy <= y + XtractorRadius; sy++, a++) {
184 if (sy >= 0 && sy < MetalMapHeight) {
185 int addX = x + xend[a];
186 int remX = x - xend[a] - 1;
187
188 if (addX < MetalMapWidth)
189 TotalMetal += MexArrayA[sy * MetalMapWidth + addX];
190 if (remX >= 0)
191 TotalMetal -= MexArrayA[sy * MetalMapWidth + remX];
192 }
193 }
194 }
195 else if (y > 0) {
196 // x == 0 here
197 TotalMetal = TempAverage[(y - 1) * MetalMapWidth];
198 // remove the top half
199 int a = XtractorRadius;
200
201 for (int sx = 0; sx <= XtractorRadius; sx++, a++) {
202 if (sx < MetalMapWidth) {
203 int remY = y - xend[a] - 1;
204
205 if (remY >= 0)
206 TotalMetal -= MexArrayA[remY * MetalMapWidth + sx];
207 }
208 }
209
210 // add the bottom half
211 a = XtractorRadius;
212
213 for (int sx = 0; sx <= XtractorRadius; sx++, a++) {
214 if (sx < MetalMapWidth) {
215 int addY = y + xend[a];
216
217 if (addY < MetalMapHeight)
218 TotalMetal += MexArrayA[addY * MetalMapWidth + sx];
219 }
220 }
221
222 }
223
224 // set that spot's metal making ability (divide by cells to values are small)
225 TempAverage[y * MetalMapWidth + x] = TotalMetal;
226
227 if (MaxMetal < TotalMetal) {
228 // find the spot with the highest metal to set as the map's max
229 MaxMetal = TotalMetal;
230 }
231 }
232 }
233
234 // make a list for the distribution of values
235 int* valueDist = new int[256];
236
237 for (int i = 0; i < 256; i++) {
238 // clear the array (useless?)
239 valueDist[i] = 0;
240 }
241
242 // this will get the total metal a mex placed at each spot would make
243 for (int i = 0; i < TotalCells; i++) {
244 // scale the metal so any map will have values 0-255, no matter how much metal it has
245 MexArrayB[i] = TempAverage[i] * 255 / MaxMetal;
246 // clear out the array since it has never been used
247 MexArrayC[i] = 0;
248
249 int value = MexArrayB[i];
250 valueDist[value]++;
251 }
252
253 // find the current best value
254 int bestValue = 0;
255 int numberOfValues = 0;
256 int usedSpots = 0;
257
258 for (int i = 255; i >= 0; i--) {
259 if (valueDist[i] != 0) {
260 bestValue = i;
261 numberOfValues = valueDist[i];
262 break;
263 }
264 }
265
266 // make a list of the indexes of the best spots
267 // (make sure that the list wont be too big)
268 if (numberOfValues > 256)
269 numberOfValues = 256;
270
271 int* bestSpotList = new int[numberOfValues];
272
273 for (int i = 0; i < TotalCells; i++) {
274 if (MexArrayB[i] == bestValue) {
275 // add the index of this spot to the list
276 bestSpotList[usedSpots] = i;
277 usedSpots++;
278
279 if (usedSpots == numberOfValues) {
280 // the list is filled, stop the loop
281 usedSpots = 0;
282 break;
283 }
284 }
285 }
286
287 for (int a = 0; a < MaxSpots; a++) {
288 if (!Stopme) {
289 // reset tempmetal so it can find new spots
290 TempMetal = 0;
291 // take the first spot
292 int speedTempMetal_x = 0;
293 int speedTempMetal_y = 0;
294 int speedTempMetal = 0;
295 bool found = false;
296
297 while (!found) {
298 if (usedSpots == numberOfValues) {
299 // the list is empty now, refill it
300
301 // make a list of all the best spots
302 for (int i = 0; i < 256; i++) {
303 // clear the array
304 valueDist[i] = 0;
305 }
306
307 // find the metal distribution
308 for (int i = 0; i < TotalCells; i++) {
309 int value = MexArrayB[i];
310 valueDist[value]++;
311 }
312
313 // find the current best value
314 bestValue = 0;
315 numberOfValues = 0;
316 usedSpots = 0;
317
318 for (int i = 255; i >= 0; i--) {
319 if (valueDist[i] != 0) {
320 bestValue = i;
321 numberOfValues = valueDist[i];
322 break;
323 }
324 }
325
326 // make a list of the indexes of the best spots
327 // (make sure that the list wont be too big)
328 if (numberOfValues > 256)
329 numberOfValues = 256;
330
331 delete[] bestSpotList;
332 bestSpotList = new int[numberOfValues];
333
334 for (int i = 0; i < TotalCells; i++) {
335 if (MexArrayB[i] == bestValue) {
336 // add the index of this spot to the list
337 bestSpotList[usedSpots] = i;
338 usedSpots++;
339
340 if (usedSpots == numberOfValues) {
341 // the list is filled, stop the loop
342 usedSpots = 0;
343 break;
344 }
345 }
346 }
347 }
348
349 // The list is not empty now.
350 int spotIndex = bestSpotList[usedSpots];
351
352 if (MexArrayB[spotIndex] == bestValue) {
353 // the spot is still valid, so use it
354 speedTempMetal_x = spotIndex % MetalMapWidth;
355 speedTempMetal_y = spotIndex / MetalMapWidth;
356 speedTempMetal = bestValue;
357 found = true;
358 }
359
360 // update the bestSpotList index
361 usedSpots++;
362 }
363
364 coordx = speedTempMetal_x;
365 coordy = speedTempMetal_y;
366 TempMetal = speedTempMetal;
367 }
368
369 if (TempMetal < MinMetalForSpot) {
370 // if the spots get too crappy it will stop running the loops to speed it all up
371 Stopme = 1;
372 }
373
374 if (!Stopme) {
375 // format metal coords to game-coords
376 BufferSpot.x = coordx * 16 + 8;
377 BufferSpot.z = coordy * 16 + 8;
378 // gets the actual amount of metal an extractor can make
379 BufferSpot.y = TempMetal * (ai->cb->GetMaxMetal()) * MaxMetal / 255;
380 VectoredSpots.push_back(BufferSpot);
381
382 // plot TGA array (not necessary) for debug
383 MexArrayC[coordy * MetalMapWidth + coordx] = TempMetal;
384 NumSpotsFound += 1;
385
386 // small speedup of "wipes the metal around the spot so its not counted twice"
387 for (int sy = coordy - XtractorRadius, a = 0; sy <= coordy + XtractorRadius; sy++, a++) {
388 if (sy >= 0 && sy < MetalMapHeight) {
389 int clearXStart = coordx - xend[a];
390 int clearXEnd = coordx + xend[a];
391
392 if (clearXStart < 0)
393 clearXStart = 0;
394 if (clearXEnd >= MetalMapWidth)
395 clearXEnd = MetalMapWidth - 1;
396
397 for (int xClear = clearXStart; xClear <= clearXEnd; xClear++) {
398 // wipes the metal around the spot so it's not counted twice
399 MexArrayA[sy * MetalMapWidth + xClear] = 0;
400 MexArrayB[sy * MetalMapWidth + xClear] = 0;
401 TempAverage[sy * MetalMapWidth + xClear] = 0;
402 }
403 }
404 }
405
406 // redo the whole averaging process around the picked spot so other spots can be found around it
407 for (int y = coordy - DoubleRadius; y <= coordy + DoubleRadius; y++) {
408 if (y >=0 && y < MetalMapHeight) {
409 for (int x = coordx - DoubleRadius; x <= coordx + DoubleRadius; x++) {
410 if (x >=0 && x < MetalMapWidth) {
411 TotalMetal = 0;
412
413 // comment out for debug
414 if (x == 0 && y == 0)
415 for (int sy = y - XtractorRadius, a = 0; sy <= y + XtractorRadius; sy++, a++) {
416 if (sy >= 0 && sy < MetalMapHeight) {
417 for (int sx = x - xend[a]; sx <= x + xend[a]; sx++) {
418 if (sx >= 0 && sx < MetalMapWidth) {
419 // get the metal from all pixels around the extractor radius
420 TotalMetal += MexArrayA[sy * MetalMapWidth + sx];
421 }
422 }
423 }
424 }
425
426 // quick calc test
427 if (x > 0) {
428 TotalMetal = TempAverage[y * MetalMapWidth + x - 1];
429
430 for (int sy = y - XtractorRadius, a = 0; sy <= y + XtractorRadius; sy++, a++) {
431 if (sy >= 0 && sy < MetalMapHeight) {
432 int addX = x + xend[a];
433 int remX = x - xend[a] - 1;
434
435 if (addX < MetalMapWidth)
436 TotalMetal += MexArrayA[sy * MetalMapWidth + addX];
437 if (remX >= 0)
438 TotalMetal -= MexArrayA[sy * MetalMapWidth + remX];
439 }
440 }
441 }
442 else if (y > 0) {
443 // x == 0 here
444 TotalMetal = TempAverage[(y - 1) * MetalMapWidth];
445 // remove the top half
446 int a = XtractorRadius;
447
448 for (int sx = 0; sx <= XtractorRadius; sx++, a++) {
449 if (sx < MetalMapWidth) {
450 int remY = y - xend[a] - 1;
451
452 if (remY >= 0)
453 TotalMetal -= MexArrayA[remY * MetalMapWidth + sx];
454 }
455 }
456
457 // add the bottom half
458 a = XtractorRadius;
459
460 for (int sx = 0; sx <= XtractorRadius; sx++, a++) {
461 if (sx < MetalMapWidth) {
462 int addY = y + xend[a];
463
464 if (addY < MetalMapHeight)
465 TotalMetal += MexArrayA[addY * MetalMapWidth + sx];
466 }
467 }
468 }
469
470 TempAverage[y * MetalMapWidth + x] = TotalMetal;
471 // set that spot's metal amount
472 MexArrayB[y * MetalMapWidth + x] = TotalMetal * 255 / MaxMetal;
473 }
474 }
475 }
476 }
477 }
478 }
479
480 // kill the lists
481 delete[] bestSpotList;
482 delete[] valueDist;
483 delete[] xend;
484
485 // 0.95 used for for reliability
486 // bool isMetalMap = (NumSpotsFound > MaxSpots * 0.95);
487 }
488
489
SaveMetalMap()490 void CMetalMap::SaveMetalMap() {
491 std::string map = GetCacheName();
492 FILE* saveFile = fopen(map.c_str(), "wb");
493
494 assert(saveFile != NULL);
495
496 fwrite(&NumSpotsFound, sizeof(int), 1, saveFile);
497 fwrite(&AverageMetal, sizeof(float), 1, saveFile);
498
499 for (int i = 0; i < NumSpotsFound; i++) {
500 fwrite(&VectoredSpots[i], sizeof(float3), 1, saveFile);
501 }
502
503 fclose(saveFile);
504 }
505
LoadMetalMap()506 bool CMetalMap::LoadMetalMap() {
507 std::string map = GetCacheName();
508 FILE* loadFile = fopen(map.c_str(), "rb");
509
510 if (loadFile != NULL) {
511 fread(&NumSpotsFound, sizeof(int), 1, loadFile);
512 VectoredSpots.resize(NumSpotsFound);
513 fread(&AverageMetal, sizeof(float), 1, loadFile);
514
515 for (int i = 0; i < NumSpotsFound; i++) {
516 fread(&VectoredSpots[i], sizeof(float3), 1, loadFile);
517 }
518
519 fclose(loadFile);
520 return true;
521 }
522
523 return false;
524 }
525
526
527
GetCacheName() const528 std::string CMetalMap::GetCacheName() const {
529 // name is used for human readability,
530 // while hash is used for uniqueness
531 // (in case the map maker forgets changing the name inbetween versions)
532 std::string relFile =
533 std::string(METALFOLDER) +
534 AIUtil::MakeFileSystemCompatible(ai->cb->GetMapName()) +
535 "-" + IntToString(ai->cb->GetMapHash(), "%x") +
536 ".Metal";
537 std::string absFile = AIUtil::GetAbsFileName(ai->cb, relFile);
538
539 return absFile;
540 }
541