1 // -------------------------------------------------------------------------
2 // AAI
3 //
4 // A skirmish AI for the Spring engine.
5 // Copyright Alexander Seizinger
6 //
7 // Released under GPL license: see LICENSE.html for more information.
8 // -------------------------------------------------------------------------
9 
10 #include "AAIConfig.h"
11 #include "AAI.h"
12 #include "System/SafeCStrings.h"
13 #include "System/Util.h"
14 
15 // all paths
16 #define CFG_PATH "cfg/"
17 #define MOD_CFG_PATH CFG_PATH "mod/"
18 #define CONFIG_SUFFIX ".cfg"
19 #define GENERAL_CFG_FILE "general" CONFIG_SUFFIX
20 
21 
22 #include "LegacyCpp/UnitDef.h"
23 using namespace springLegacyAI;
24 
25 
IsFSGoodChar(const char c)26 static bool IsFSGoodChar(const char c) {
27 
28 	if ((c >= '0') && (c <= '9')) {
29 		return true;
30 	} else if ((c >= 'a') && (c <= 'z')) {
31 		return true;
32 	} else if ((c >= 'A') && (c <= 'Z')) {
33 		return true;
34 	} else if ((c == '.') || (c == '_') || (c == '-')) {
35 		return true;
36 	}
37 
38 	return false;
39 }
40 
41 // declaration is in aidef.h
MakeFileSystemCompatible(const std::string & str)42 std::string MakeFileSystemCompatible(const std::string& str) {
43 
44 	std::string cleaned = str;
45 
46 	for (std::string::size_type i=0; i < cleaned.size(); i++) {
47 		if (!IsFSGoodChar(cleaned[i])) {
48 			cleaned[i] = '_';
49 		}
50 	}
51 
52 	return cleaned;
53 }
54 
55 
GetInt(FILE * file)56 int AAIConfig::GetInt(FILE* file)
57 {
58 	int val = 0;
59 	int res = fscanf(file, "%i", &val);
60 	if (res != 1) {
61 		ai->Log("Error while parsing config");
62 	}
63 	return val;
64 }
65 
GetString(FILE * file)66 std::string AAIConfig::GetString(FILE* file)
67 {
68 	char buf[128];
69 	buf[0] = 0; //safety!
70 	int res = fscanf(file, "%s", buf);
71 	if (res != 1) {
72 		ai->Log("Error while parsing config");
73 	}
74 	return std::string(buf);
75 }
76 
GetFloat(FILE * file)77 float AAIConfig::GetFloat(FILE* file)
78 {
79 	float val = 0.0f;
80 	int res = fscanf(file, "%f", &val);
81 	if (res != 1) {
82 		ai->Log("Error while parsing config");
83 	}
84 	return val;
85 }
86 
87 
AAIConfig(void)88 AAIConfig::AAIConfig(void)
89 {
90 	SIDES = 2;
91 	SECTOR_SIZE = 100.0;
92 	MIN_ENERGY = 18;  // min energy make value to be considered beeing a power plant
93 	MAX_UNITS = 30000;
94 	MAX_SCOUTS = 4;
95 	MAX_SECTOR_IMPORTANCE = 6;
96 	MAX_XROW = 8;
97 	MAX_YROW = 8;
98 	X_SPACE = 16;
99 	Y_SPACE = 16;
100 	MAX_GROUP_SIZE = 12;
101 	MAX_AIR_GROUP_SIZE = 4;
102 	MAX_ANTI_AIR_GROUP_SIZE = 4;
103 	MAX_SUBMARINE_GROUP_SIZE = 4;
104 	MAX_NAVAL_GROUP_SIZE = 4;
105 	MAX_ARTY_GROUP_SIZE = 4;
106 	MIN_EFFICIENCY = 0.001f;
107 	MAX_BUILDERS = 50;
108 	MAX_BUILDERS_PER_TYPE = 5;
109 	MAX_FACTORIES_PER_TYPE = 3;
110 	MAX_BUILDQUE_SIZE = 12;
111 	MAX_ASSISTANTS = 4;
112 	MIN_ASSISTANCE_BUILDTIME = 15;
113 	MIN_ASSISTANCE_BUILDSPEED = 20;
114 	MAX_BASE_SIZE = 10;
115 	SCOUT_SPEED = 95.0;
116 	GROUND_ARTY_RANGE = 1000.0;
117 	SEA_ARTY_RANGE = 1300.0;
118 	HOVER_ARTY_RANGE = 1000.0;
119 	STATIONARY_ARTY_RANGE = 2000;
120 	AIR_DEFENCE = 8;
121 	MIN_ENERGY_STORAGE = 500;
122 	MIN_METAL_STORAGE = 100;
123 	MAX_METAL_COST = 10000;
124 	MIN_AIR_ATTACK_COST = 150;
125 	MAX_AIR_TARGETS = 20;
126 	AIRCRAFT_RATE = 6;
127 	HIGH_RANGE_UNITS_RATE = 4;
128 	FAST_UNITS_RATE = 5;
129 	METAL_ENERGY_RATIO = 25;
130 	MAX_DEFENCES = 12;
131 	MIN_SECTOR_THREAT = 6;
132 	MAX_STAT_ARTY = 3;
133 	MAX_STORAGE = 6;
134 	MAX_AIR_BASE = 1;
135 	AIR_ONLY_MOD = false;
136 	MAX_METAL_MAKERS = 20;
137 	MIN_METAL_MAKER_ENERGY = 100;
138 	MAX_MEX_DISTANCE = 7;
139 	MAX_MEX_DEFENCE_DISTANCE = 5;
140 	MIN_FACTORIES_FOR_DEFENCES = 1;
141 	MIN_FACTORIES_FOR_STORAGE = 2;
142 	MIN_FACTORIES_FOR_RADAR_JAMMER = 2;
143 	MIN_AIR_SUPPORT_EFFICIENCY = 2.5f;
144 	UNIT_SPEED_SUBGROUPS = 3;
145 	MIN_SUBMARINE_WATERLINE = 15;
146 	MAX_ATTACKS = 4;
147 
148 	NON_AMPHIB_MAX_WATERDEPTH = 15.0f;
149 
150 	MAX_COST_LIGHT_ASSAULT = 0.025f;
151 	MAX_COST_MEDIUM_ASSAULT = 0.13f;
152 	MAX_COST_HEAVY_ASSAULT = 0.55f;
153 
154 	LIGHT_ASSAULT_RATIO = 40.0f;
155 	MEDIUM_ASSAULT_RATIO = 30.0f;
156 	HEAVY_ASSAULT_RATIO = 25.0f;
157 	SUPER_HEAVY_ASSAULT_RATIO = 5.0f;
158 
159 	FALLBACK_DIST_RATIO = 0.9f;
160 	MIN_FALLBACK_RANGE = 450.0f;
161 	MAX_FALLBACK_RANGE = 800.0f;
162 	MIN_FALLBACK_TURNRATE = 250.0f;
163 
164 	LEARN_SPEED = 0.2f;
165 	LEARN_RATE = 5;
166 	CONSTRUCTION_TIMEOUT = 1500;
167 	CLIFF_SLOPE = 0.085f;
168 	SCOUT_UPDATE_FREQUENCY = 127;
169 	SCOUTING_MEMORY_FACTOR = 1.0f;
170 	WATER_MAP_RATIO = 0.8f;
171 	LAND_WATER_MAP_RATIO = 0.3f;
172 
173 	GAME_PERIODS = 4;
174 	ai = NULL;
175 	initialized = false;
176 }
177 
~AAIConfig(void)178 AAIConfig::~AAIConfig(void)
179 {
180 	START_UNITS.clear();
181 	SIDE_NAMES.clear();
182 }
183 
184 
GetFileName(const std::string & filename,const std::string & prefix,const std::string & suffix,bool write) const185 std::string AAIConfig::GetFileName(const std::string& filename, const std::string& prefix, const std::string& suffix, bool write) const
186 {
187 	std::string name = prefix + MakeFileSystemCompatible(filename) + suffix;
188 
189 	// this size equals the one used in "AIAICallback::GetValue(AIVAL_LOCATE_FILE_..."
190 	char buffer[2048];
191 	STRCPY_T(buffer, sizeof(buffer), name.c_str());
192 	if (write) {
193 		ai->Getcb()->GetValue(AIVAL_LOCATE_FILE_W, buffer);
194 	} else {
195 		ai->Getcb()->GetValue(AIVAL_LOCATE_FILE_R, buffer);
196 	}
197 	name.assign(buffer, sizeof(buffer));
198 	return name;
199 }
200 
LoadConfig(AAI * ai)201 void AAIConfig::LoadConfig(AAI *ai)
202 {
203 	this->ai = ai;
204 	MAX_UNITS = ai->Getcb()->GetMaxUnits();
205 
206 
207 	std::list<string> paths;
208 	paths.push_back(GetFileName(ai->Getcb()->GetModHumanName(), MOD_CFG_PATH, CONFIG_SUFFIX));
209 	paths.push_back(GetFileName(ai->Getcb()->GetModName(), MOD_CFG_PATH, CONFIG_SUFFIX));
210 	paths.push_back(GetFileName(ai->Getcb()->GetModShortName(), MOD_CFG_PATH, CONFIG_SUFFIX));
211 	FILE* file = NULL;
212 	std::string configfile;
213 	for(const std::string& path: paths) {
214 		file = fopen(path.c_str(), "r");
215 		if (file == NULL) {
216 			ai->Log("Couldn't open config file %s\n", path.c_str());
217 		} else {
218 			configfile = path;
219 			break;
220 		}
221 	}
222 
223 	if (file == NULL) {
224 		ai->Log("Give up trying to find mod config file (required).\n");
225 		initialized = false;
226 		return;
227 	}
228 
229 	char keyword[50];
230 
231 	bool error = false;
232 //	bool loaded = false;
233 
234 	if(file == NULL)
235 		return;
236 
237 	while(EOF != fscanf(file, "%s", keyword))
238 	{
239 		if(!strcmp(keyword,"SIDES")) {
240 			SIDES = GetInt(file);
241 		}
242 		else if(!strcmp(keyword, "START_UNITS")) {
243 			START_UNITS.resize(SIDES);
244 			for(int i = 0; i < SIDES; i++) {
245 				START_UNITS[i] = GetString(file);
246 				if(!GetUnitDef(START_UNITS[i].c_str())) {
247 					error = true;
248 					break;
249 				}
250 			}
251 		} else if(!strcmp(keyword, "SIDE_NAMES")) {
252 			SIDE_NAMES.resize(SIDES);
253 			for(int i = 0; i < SIDES; i++) {
254 				SIDE_NAMES[i] = GetString(file);
255 			}
256 		} else if(!strcmp(keyword, "SCOUTS")) {
257 			// get number of scouts
258 			const int count = GetInt(file);
259 			for(int i = 0; i < count; ++i) {
260 				const std::string unitdef = GetString(file);
261 				if(GetUnitDef(unitdef)) {
262 					SCOUTS.push_back(GetUnitDef(unitdef)->id);
263 				} else {
264 					error = true;
265 					break;
266 				}
267 			}
268 		} else if(!strcmp(keyword, "ATTACKERS")) {
269 			// get number of attackers
270 			const int count = GetInt(file);
271 
272 			for(int i = 0; i < count; ++i) {
273 				const std::string unitdef = GetString(file);
274 				if(GetUnitDef(unitdef))
275 					ATTACKERS.push_back(GetUnitDef(unitdef)->id);
276 				else {
277 					error = true;
278 					break;
279 				}
280 			}
281 		} else if(!strcmp(keyword, "TRANSPORTERS")) {
282 			// get number of transporters
283 			const int count = GetInt(file);
284 
285 			for(int i = 0; i < count; ++i) {
286 				const std::string unitdef = GetString(file);
287 				if(GetUnitDef(unitdef))
288 					TRANSPORTERS.push_back(GetUnitDef(unitdef)->id);
289 				else {
290 					error = true;
291 					break;
292 				}
293 			}
294 		} else if(!strcmp(keyword, "METAL_MAKERS")) {
295 			// get number of transporters
296 			const int count = GetInt(file);
297 			for(int i = 0; i < count; ++i) {
298 				const std::string unitdef = GetString(file);
299 				if(GetUnitDef(unitdef))
300 					METAL_MAKERS.push_back(GetUnitDef(unitdef)->id);
301 				else {
302 					error = true;
303 					break;
304 				}
305 			}
306 		} else if(!strcmp(keyword, "DONT_BUILD")) {
307 			// get number of units that should not be built
308 			const int count = GetInt(file);
309 			for(int i = 0; i < count; ++i) {
310 				const std::string unitdef = GetString(file);
311 				if(GetUnitDef(unitdef))
312 					DONT_BUILD.push_back(GetUnitDef(unitdef)->id);
313 				else {
314 					error = true;
315 					break;
316 				}
317 			}
318 		} else if(!strcmp(keyword, "COST_MULTIPLIER")) {
319 			// get the unit def
320 			const std::string unitdef = GetString(file);
321 			const UnitDef* def = GetUnitDef(unitdef);
322 
323 			if(def)
324 			{
325 				CostMultiplier temp;
326 				temp.id = def->id;
327 				temp.multiplier = GetFloat(file);
328 
329 				cost_multipliers.push_back(temp);
330 			} else {
331 				error = true;
332 				break;
333 			}
334 		} else if(!strcmp(keyword,"SECTOR_SIZE")) {
335 			SECTOR_SIZE = GetFloat(file);
336 			ai->Log("SECTOR_SIZE set to %f", SECTOR_SIZE);
337 		} else if(!strcmp(keyword,"MIN_ENERGY")) {
338 			MIN_ENERGY = GetInt(file);
339 		} else if(!strcmp(keyword, "MAX_UNITS")) {
340 			MAX_UNITS = GetInt(file);
341 		} else if(!strcmp(keyword, "MAX_SCOUTS")) {
342 			MAX_SCOUTS = GetInt(file);
343 		} else if(!strcmp(keyword, "MAX_SECTOR_IMPORTANCE")) {
344 			MAX_SECTOR_IMPORTANCE = GetInt(file);
345 		} else if(!strcmp(keyword, "MAX_XROW")) {
346 			MAX_XROW = GetInt(file);
347 		} else if(!strcmp(keyword, "MAX_YROW")) {
348 			MAX_YROW = GetInt(file);
349 		} else if(!strcmp(keyword, "X_SPACE")) {
350 			X_SPACE = GetInt(file);
351 		} else if(!strcmp(keyword, "Y_SPACE")) {
352 			Y_SPACE = GetInt(file);
353 		} else if(!strcmp(keyword, "MAX_GROUP_SIZE")) {
354 			MAX_GROUP_SIZE = GetInt(file);
355 		} else if(!strcmp(keyword, "MAX_AIR_GROUP_SIZE")) {
356 			MAX_AIR_GROUP_SIZE = GetInt(file);
357 		} else if(!strcmp(keyword, "MAX_NAVAL_GROUP_SIZE")) {
358 			MAX_NAVAL_GROUP_SIZE = GetInt(file);
359 		} else if(!strcmp(keyword, "MAX_SUBMARINE_GROUP_SIZE")) {
360 			MAX_SUBMARINE_GROUP_SIZE = GetInt(file);
361 		} else if(!strcmp(keyword, "MAX_ANTI_AIR_GROUP_SIZE")) {
362 			MAX_ANTI_AIR_GROUP_SIZE = GetInt(file);
363 		} else if(!strcmp(keyword, "MAX_ARTY_GROUP_SIZE")) {
364 			MAX_ARTY_GROUP_SIZE = GetInt(file);
365 		} else if(!strcmp(keyword, "UNIT_SPEED_SUBGROUPS")) {
366 			UNIT_SPEED_SUBGROUPS = GetInt(file);
367 		} else if(!strcmp(keyword, "FALLBACK_DIST_RATIO")) {
368 			FALLBACK_DIST_RATIO = GetInt(file);
369 		} else if(!strcmp(keyword, "MIN_FALLBACK_RANGE")) {
370 			MIN_FALLBACK_RANGE = GetInt(file);
371 		} else if(!strcmp(keyword, "MAX_FALLBACK_RANGE")) {
372 			MAX_FALLBACK_RANGE = GetInt(file);
373 		} else if(!strcmp(keyword, "MIN_FALLBACK_TURNRATE")) {
374 			MIN_FALLBACK_TURNRATE = GetFloat(file);
375 		} else if(!strcmp(keyword, "MIN_EFFICIENCY")) {
376 			MIN_EFFICIENCY = GetFloat(file);
377 		} else if(!strcmp(keyword, "MIN_AIR_SUPPORT_EFFICIENCY")) {
378 			MIN_AIR_SUPPORT_EFFICIENCY = GetFloat(file);
379 		} else if(!strcmp(keyword, "MAX_BUILDERS")) {
380 			MAX_BUILDERS = GetInt(file);
381 		} else if(!strcmp(keyword, "MAX_BUILDQUE_SIZE")) {
382 			MAX_BUILDQUE_SIZE = GetInt(file);
383 		} else if(!strcmp(keyword, "MAX_ASSISTANTS")) {
384 			MAX_ASSISTANTS = GetInt(file);
385 		} else if(!strcmp(keyword, "MIN_ASSISTANCE_BUILDSPEED")) {
386 			MIN_ASSISTANCE_BUILDSPEED = GetInt(file);
387 		} else if(!strcmp(keyword, "MAX_BASE_SIZE")) {
388 			MAX_BASE_SIZE = GetInt(file);
389 		} else if(!strcmp(keyword, "MAX_AIR_TARGETS")) {
390 			MAX_AIR_TARGETS = GetInt(file);
391 		} else if(!strcmp(keyword, "MIN_AIR_ATTACK_COST")) {
392 			MIN_AIR_ATTACK_COST = GetInt(file);
393 		} else if(!strcmp(keyword, "SCOUT_SPEED")) {
394 			SCOUT_SPEED = GetFloat(file);
395 		} else if(!strcmp(keyword, "GROUND_ARTY_RANGE")) {
396 			GROUND_ARTY_RANGE = GetInt(file);
397 		} else if(!strcmp(keyword, "SEA_ARTY_RANGE")) {
398 			SEA_ARTY_RANGE = GetFloat(file);
399 		} else if(!strcmp(keyword, "HOVER_ARTY_RANGE")) {
400 			HOVER_ARTY_RANGE = GetFloat(file);
401 		} else if(!strcmp(keyword, "STATIONARY_ARTY_RANGE")) {
402 			STATIONARY_ARTY_RANGE = GetFloat(file);
403 		} else if(!strcmp(keyword, "MAX_BUILDERS_PER_TYPE")) {
404 			MAX_BUILDERS_PER_TYPE = GetInt(file);
405 		} else if(!strcmp(keyword, "MAX_FACTORIES_PER_TYPE")) {
406 			MAX_FACTORIES_PER_TYPE = GetInt(file);
407 		} else if(!strcmp(keyword, "MIN_ASSISTANCE_BUILDTIME")) {
408 			MIN_ASSISTANCE_BUILDTIME = GetInt(file);
409 		} else if(!strcmp(keyword, "AIR_DEFENCE")) {
410 			AIR_DEFENCE = GetInt(file);
411 		} else if(!strcmp(keyword, "AIRCRAFT_RATE")) {
412 			AIRCRAFT_RATE = GetInt(file);
413 		} else if(!strcmp(keyword, "HIGH_RANGE_UNITS_RATE")) {
414 			HIGH_RANGE_UNITS_RATE = GetInt(file);
415 		} else if(!strcmp(keyword, "FAST_UNITS_RATE")) {
416 			FAST_UNITS_RATE = GetInt(file);
417 		} else if(!strcmp(keyword, "MAX_METAL_COST")) {
418 			MAX_METAL_COST = GetInt(file);
419 		} else if(!strcmp(keyword, "MAX_DEFENCES")) {
420 			MAX_DEFENCES = GetInt(file);
421 		} else if(!strcmp(keyword, "MIN_SECTOR_THREAT")) {
422 			MIN_SECTOR_THREAT = GetFloat(file);
423 		} else if(!strcmp(keyword, "MAX_STAT_ARTY")) {
424 			MAX_STAT_ARTY = GetInt(file);
425 		} else if(!strcmp(keyword, "MAX_AIR_BASE")) {
426 			MAX_AIR_BASE = GetInt(file);
427 		} else if(!strcmp(keyword, "AIR_ONLY_MOD")) {
428 			AIR_ONLY_MOD = (bool)GetInt(file);
429 		} else if(!strcmp(keyword, "METAL_ENERGY_RATIO")) {
430 			METAL_ENERGY_RATIO = GetFloat(file);
431 		} else if(!strcmp(keyword, "NON_AMPHIB_MAX_WATERDEPTH")) {
432 			NON_AMPHIB_MAX_WATERDEPTH = GetFloat(file);
433 		} else if(!strcmp(keyword, "MAX_METAL_MAKERS")) {
434 			MAX_METAL_MAKERS = GetInt(file);
435 		} else if(!strcmp(keyword, "MAX_STORAGE")) {
436 			MAX_STORAGE = GetInt(file);
437 		} else if(!strcmp(keyword, "MIN_METAL_MAKER_ENERGY")) {
438 			MIN_METAL_MAKER_ENERGY = GetFloat(file);
439 		} else if(!strcmp(keyword, "MAX_MEX_DISTANCE")) {
440 			MAX_MEX_DISTANCE = GetInt(file);
441 		} else if(!strcmp(keyword, "MAX_MEX_DEFENCE_DISTANCE")) {
442 			MAX_MEX_DEFENCE_DISTANCE = GetInt(file);
443 		} else if(!strcmp(keyword, "MIN_FACTORIES_FOR_DEFENCES")) {
444 			MIN_FACTORIES_FOR_DEFENCES = GetInt(file);
445 		} else if(!strcmp(keyword, "MIN_FACTORIES_FOR_STORAGE")) {
446 			MIN_FACTORIES_FOR_STORAGE = GetInt(file);
447 		} else if(!strcmp(keyword, "MIN_FACTORIES_FOR_RADAR_JAMMER")) {
448 			MIN_FACTORIES_FOR_RADAR_JAMMER = GetInt(file);
449 		} else if(!strcmp(keyword, "MIN_SUBMARINE_WATERLINE")) {
450 			MIN_SUBMARINE_WATERLINE = GetInt(file);
451 		} else if(!strcmp(keyword, "MAX_ATTACKS")) {
452 			MAX_ATTACKS = GetInt(file);
453 		} else {
454 			error = true;
455 			break;
456 		}
457 	}
458 
459 	if(error) {
460 		ai->Log("Mod config file %s contains erroneous keyword: %s\n", configfile.c_str(), keyword);
461 		initialized = false;
462 		return;
463 	}
464 
465 	fclose(file);
466 	ai->Log("Mod config file %s loaded\n", configfile.c_str());
467 
468 	// load general settings
469 	const std::string generalcfg = GetFileName(GENERAL_CFG_FILE, CFG_PATH);
470 	file = fopen(generalcfg.c_str(), "r");
471 	if(file == NULL) {
472 		ai->Log("Couldn't load general config file %s\n", generalcfg.c_str());
473 		return;
474 	}
475 
476 	while(EOF != fscanf(file, "%s", keyword))
477 	{
478 		if(!strcmp(keyword, "LEARN_RATE")) {
479 			LEARN_RATE = GetInt(file);
480 		} else if(!strcmp(keyword, "LEARN_SPEED")) {
481 			LEARN_SPEED = GetFloat(file);
482 		} else if(!strcmp(keyword, "WATER_MAP_RATIO")) {
483 			WATER_MAP_RATIO = GetFloat(file);
484 		} else if(!strcmp(keyword, "LAND_WATER_MAP_RATIO")) {
485 			LAND_WATER_MAP_RATIO = GetFloat(file);
486 		} else if(!strcmp(keyword, "SCOUT_UPDATE_FREQUENCY")) {
487 			SCOUT_UPDATE_FREQUENCY = GetInt(file);;
488 		} else if(!strcmp(keyword, "SCOUTING_MEMORY_FACTOR")) {
489 			SCOUTING_MEMORY_FACTOR = GetFloat(file);
490 		} else {
491 			error = true;
492 			break;
493 		}
494 	}
495 
496 	fclose(file);
497 
498 	if(error) {
499 		ai->Log("General config file contains erroneous keyword %s\n", keyword);
500 		return;
501 	}
502 	ai->Log("General config file loaded\n");
503 	initialized = true;
504 }
505 
GetUnitDef(const std::string & name)506 const UnitDef* AAIConfig::GetUnitDef(const std::string& name)
507 {
508 	const UnitDef* res = ai->Getcb()->GetUnitDef(name.c_str());
509 	if (res == NULL) {
510 		ai->Log("ERROR: loading unit - could not find unit %s\n", name.c_str());
511 	}
512 	return res;
513 }
514 
getUniqueName(bool game,bool gamehash,bool map,bool maphash) const515 std::string AAIConfig::getUniqueName(bool game, bool gamehash, bool map, bool maphash) const
516 {
517 	std::string res;
518 	if (map) {
519 		if (!res.empty())
520 			res += "-";
521 		std::string mapName = MakeFileSystemCompatible(ai->Getcb()->GetMapName());
522 		mapName.resize(mapName.size() - 4); // cut off extension
523 		res += mapName;
524 	}
525 	if (maphash) {
526 		if (!res.empty())
527 			res += "-";
528 		res += IntToString(ai->Getcb()->GetMapHash(), "%x");
529 	}
530 	if (game) {
531 		if (!res.empty())
532 			res += "_";
533 		res += MakeFileSystemCompatible(ai->Getcb()->GetModHumanName());
534 	}
535 	if (gamehash) {
536 		if (!res.empty())
537 			res += "-";
538 		res += IntToString(ai->Getcb()->GetModHash(), "%x");
539 	}
540 	return res;
541 }
542 
543 AAIConfig *cfg = new AAIConfig();
544