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