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