1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3
4 #include "GameSetup.h"
5 #include "System/TdfParser.h"
6 #include "System/FileSystem/ArchiveScanner.h"
7 #include "Map/MapParser.h"
8 #include "Sim/Misc/GlobalConstants.h"
9 #include "System/UnsyncedRNG.h"
10 #include "System/Exceptions.h"
11 #include "System/Util.h"
12 #include "System/Log/ILog.h"
13
14 #include <algorithm>
15 #include <map>
16 #include <cctype>
17 #include <cstring>
18 #include <boost/format.hpp>
19
20 CR_BIND(CGameSetup,)
21 CR_REG_METADATA(CGameSetup, (
22 CR_MEMBER(gameSetupText),
23 CR_POSTLOAD(PostLoad)
24 ))
25
26 CGameSetup* gameSetup = NULL;
27
28
29
LoadSavedScript(const std::string & file,const std::string & script)30 void CGameSetup::LoadSavedScript(const std::string& file, const std::string& script)
31 {
32 if (script.empty())
33 return;
34 if (gameSetup != NULL)
35 return;
36
37 CGameSetup* temp = new CGameSetup();
38
39 if (!temp->Init(script)) {
40 delete temp; temp = NULL;
41 } else {
42 temp->saveName = file;
43 // set the global instance
44 gameSetup = temp;
45 }
46 }
47
48
GetMapOptions()49 const std::map<std::string, std::string>& CGameSetup::GetMapOptions()
50 {
51 static std::map<std::string, std::string> dummyOptions;
52
53 if (gameSetup != NULL) {
54 return gameSetup->GetMapOptionsCont();
55 }
56
57 return dummyOptions;
58 }
59
GetModOptions()60 const std::map<std::string, std::string>& CGameSetup::GetModOptions()
61 {
62 static std::map<std::string, std::string> dummyOptions;
63
64 if (gameSetup != NULL) {
65 return gameSetup->GetModOptionsCont();
66 }
67
68 return dummyOptions;
69 }
70
71
GetPlayerStartingData()72 const std::vector<PlayerBase>& CGameSetup::GetPlayerStartingData()
73 {
74 static std::vector<PlayerBase> dummyData;
75
76 if (gameSetup != NULL) {
77 return gameSetup->GetPlayerStartingDataCont();
78 }
79
80 return dummyData;
81 }
82
GetTeamStartingData()83 const std::vector<TeamBase>& CGameSetup::GetTeamStartingData()
84 {
85 static std::vector<TeamBase> dummyData;
86
87 if (gameSetup != NULL) {
88 return gameSetup->GetTeamStartingDataCont();
89 }
90
91 return dummyData;
92 }
93
GetAllyStartingData()94 const std::vector<AllyTeam>& CGameSetup::GetAllyStartingData()
95 {
96 static std::vector<AllyTeam> dummyData;
97
98 if (gameSetup != NULL) {
99 return gameSetup->GetAllyStartingDataCont();
100 }
101
102 return dummyData;
103 }
104
105
106
CGameSetup()107 CGameSetup::CGameSetup()
108 : fixedAllies(true)
109 , useLuaGaia(true)
110 , noHelperAIs(false)
111
112 , ghostedBuildings(true)
113 , disableMapDamage(false)
114
115 , onlyLocal(false)
116 , hostDemo(false)
117
118 , mapHash(0)
119 , modHash(0)
120 , mapSeed(0)
121
122 , gameStartDelay(0)
123 , numDemoPlayers(0)
124 , maxUnitsPerTeam(1500)
125
126 , maxSpeed(0.0f)
127 , minSpeed(0.0f)
128
129 , startPosType(StartPos_Fixed)
130 {}
131
PostLoad()132 void CGameSetup::PostLoad()
133 {
134 Init(gameSetupText);
135 }
136
LoadUnitRestrictions(const TdfParser & file)137 void CGameSetup::LoadUnitRestrictions(const TdfParser& file)
138 {
139 int numRestrictions;
140 file.GetDef(numRestrictions, "0", "GAME\\NumRestrictions");
141
142 for (int i = 0; i < numRestrictions; ++i) {
143 char key[100];
144 sprintf(key, "GAME\\RESTRICT\\Unit%d", i);
145 string resName = file.SGetValueDef("", key);
146 sprintf(key, "GAME\\RESTRICT\\Limit%d", i);
147 int resLimit;
148 file.GetDef(resLimit, "0", key);
149
150 restrictedUnits[resName] = resLimit;
151 }
152 }
153
LoadStartPositionsFromMap()154 void CGameSetup::LoadStartPositionsFromMap()
155 {
156 MapParser mapParser(MapFile());
157 if (!mapParser.IsValid()) {
158 throw content_error("MapInfo: " + mapParser.GetErrorLog());
159 }
160
161 for (size_t a = 0; a < teamStartingData.size(); ++a) {
162 float3 pos;
163
164 // don't fail when playing with more players than
165 // start positions and we didn't use them anyway
166 if (!mapParser.GetStartPos(teamStartingData[a].teamStartNum, pos))
167 throw content_error(mapParser.GetErrorLog());
168
169 // map should ensure positions are valid
170 // (but clients will always do clamping)
171 teamStartingData[a].SetStartPos(pos);
172 }
173 }
174
LoadStartPositions(bool withoutMap)175 void CGameSetup::LoadStartPositions(bool withoutMap)
176 {
177 if (withoutMap && (startPosType == StartPos_Random || startPosType == StartPos_Fixed))
178 throw content_error("You need the map to use the map's startpositions");
179
180 if (startPosType == StartPos_Random) {
181 // Server syncs these later, so we can use unsynced rng
182 UnsyncedRNG rng;
183 rng.Seed(gameSetupText.length());
184 rng.Seed((size_t)gameSetupText.c_str());
185 std::vector<int> teamStartNum(teamStartingData.size());
186
187 for (size_t i = 0; i < teamStartingData.size(); ++i)
188 teamStartNum[i] = i;
189
190 std::random_shuffle(teamStartNum.begin(), teamStartNum.end(), rng);
191
192 for (size_t i = 0; i < teamStartingData.size(); ++i)
193 teamStartingData[i].teamStartNum = teamStartNum[i];
194 } else {
195 for (size_t a = 0; a < teamStartingData.size(); ++a) {
196 teamStartingData[a].teamStartNum = (int)a;
197 }
198 }
199
200 if (startPosType == StartPos_Fixed || startPosType == StartPos_Random) {
201 LoadStartPositionsFromMap();
202 }
203 }
204
LoadMutators(const TdfParser & file,std::vector<std::string> & mutatorsList)205 void CGameSetup::LoadMutators(const TdfParser& file, std::vector<std::string>& mutatorsList)
206 {
207 for (int a = 0; a < 10; ++a) {
208 std::string s = file.SGetValueDef("", IntToString(a, "GAME\\MUTATOR%i"));
209 if (s.empty()) break;
210 mutatorsList.push_back(s);
211 }
212 }
213
LoadPlayers(const TdfParser & file,std::set<std::string> & nameList)214 void CGameSetup::LoadPlayers(const TdfParser& file, std::set<std::string>& nameList)
215 {
216 numDemoPlayers = 0;
217 // i = player index in game (no gaps), a = player index in script
218 int i = 0;
219 for (int a = 0; a < MAX_PLAYERS; ++a) {
220 char section[50];
221 sprintf(section, "GAME\\PLAYER%i", a);
222 string s(section);
223
224 if (!file.SectionExist(s)) {
225 continue;
226 }
227 PlayerBase data;
228
229 // expects lines of form team=x rather than team=TEAMx
230 // team field is relocated in RemapTeams
231 std::map<std::string, std::string> setup = file.GetAllValues(s);
232 for (std::map<std::string, std::string>::const_iterator it = setup.begin(); it != setup.end(); ++it)
233 data.SetValue(it->first, it->second);
234
235 // do checks for sanity
236 if (data.name.empty())
237 throw content_error(str( boost::format("GameSetup: No name given for Player %i") %a ));
238 if (nameList.find(data.name) != nameList.end())
239 throw content_error(str(boost::format("GameSetup: Player %i has name %s which is already taken") %a %data.name.c_str() ));
240 nameList.insert(data.name);
241
242 if (data.isFromDemo)
243 numDemoPlayers++;
244
245 playerStartingData.push_back(data);
246 playerRemap[a] = i;
247 ++i;
248 }
249
250 unsigned playerCount = 0;
251 if (file.GetValue(playerCount, "GAME\\NumPlayers") && playerStartingData.size() != playerCount) {
252 LOG_L(L_WARNING,
253 _STPF_ " players in GameSetup script (NumPlayers says %i)",
254 playerStartingData.size(), playerCount);
255 }
256 }
257
LoadSkirmishAIs(const TdfParser & file,std::set<std::string> & nameList)258 void CGameSetup::LoadSkirmishAIs(const TdfParser& file, std::set<std::string>& nameList)
259 {
260 // i = AI index in game (no gaps), a = AI index in script
261 for (int a = 0; a < MAX_PLAYERS; ++a) {
262 char section[50];
263 sprintf(section, "GAME\\AI%i\\", a);
264 string s(section);
265
266 if (!file.SectionExist(s.substr(0, s.length() - 1))) {
267 continue;
268 }
269
270 SkirmishAIData data;
271
272 data.team = atoi(file.SGetValueDef("-1", s + "Team").c_str());
273 if (data.team == -1) {
274 throw content_error("missing AI.Team in GameSetup script");
275 }
276 data.hostPlayer = atoi(file.SGetValueDef("-1", s + "Host").c_str());
277 if (data.hostPlayer == -1) {
278 throw content_error("missing AI.Host in GameSetup script");
279 }
280
281 data.shortName = file.SGetValueDef("", s + "ShortName");
282 if (data.shortName == "") {
283 throw content_error("missing AI.ShortName in GameSetup script");
284 }
285
286 data.version = file.SGetValueDef("", s + "Version");
287 if (file.SectionExist(s + "Options")) {
288 data.options = file.GetAllValues(s + "Options");
289 std::map<std::string, std::string>::const_iterator kv;
290 for (kv = data.options.begin(); kv != data.options.end(); ++kv) {
291 data.optionKeys.push_back(kv->first);
292 }
293 }
294
295 // get the visible name (comparable to player-name)
296 std::string name = file.SGetValueDef(data.shortName, s + "Name");
297 int instanceIndex = 0;
298 std::string name_unique = name;
299 while (nameList.find(name_unique) != nameList.end()) {
300 name_unique = name + "_" + IntToString(instanceIndex++);
301 // so we possibly end up with something like myBot_0, or RAI_2
302 }
303 data.name = name_unique;
304 nameList.insert(data.name);
305
306 skirmishAIStartingData.push_back(data);
307 }
308 }
309
LoadTeams(const TdfParser & file)310 void CGameSetup::LoadTeams(const TdfParser& file)
311 {
312 // i = team index in game (no gaps), a = team index in script
313 int i = 0;
314 for (int a = 0; a < MAX_TEAMS; ++a) {
315 char section[50];
316 sprintf(section, "GAME\\TEAM%i", a);
317 string s(section);
318
319 if (!file.SectionExist(s.substr(0, s.length()))) {
320 continue;
321 }
322
323 TeamBase data;
324
325 // Get default color from palette (based on "color" tag)
326 for (size_t num = 0; num < 3; ++num) {
327 data.color[num] = TeamBase::teamDefaultColor[a][num];
328 }
329 data.color[3] = 255;
330
331 const std::map<std::string, std::string>& setup = file.GetAllValues(s);
332
333 for (std::map<std::string, std::string>::const_iterator it = setup.begin(); it != setup.end(); ++it)
334 data.SetValue(it->first, it->second);
335
336 teamStartingData.push_back(data);
337
338 teamRemap[a] = i;
339 ++i;
340 }
341
342 unsigned teamCount = 0;
343 if (file.GetValue(teamCount, "Game\\NumTeams") && teamStartingData.size() != teamCount) {
344 LOG_L(L_WARNING,
345 _STPF_ " teams in GameSetup script (NumTeams: %i)",
346 teamStartingData.size(), teamCount);
347 }
348 }
349
LoadAllyTeams(const TdfParser & file)350 void CGameSetup::LoadAllyTeams(const TdfParser& file)
351 {
352 // i = allyteam index in game (no gaps), a = allyteam index in script
353 int i = 0;
354 for (int a = 0; a < MAX_TEAMS; ++a) {
355 char section[50];
356 sprintf(section,"GAME\\ALLYTEAM%i",a);
357 string s(section);
358
359 if (!file.SectionExist(s))
360 continue;
361
362 AllyTeam data;
363 std::map<std::string, std::string> setup = file.GetAllValues(s);
364
365 for (std::map<std::string, std::string>::const_iterator it = setup.begin(); it != setup.end(); ++it)
366 data.SetValue(it->first, it->second);
367
368 allyStartingData.push_back(data);
369
370 allyteamRemap[a] = i;
371 ++i;
372 }
373
374 {
375 const size_t numAllyTeams = allyStartingData.size();
376 for (size_t a = 0; a < numAllyTeams; ++a) {
377 allyStartingData[a].allies.resize(numAllyTeams, false);
378 allyStartingData[a].allies[a] = true; // each team is allied with itself
379
380 std::ostringstream section;
381 section << "GAME\\ALLYTEAM" << a << "\\";
382
383 const size_t numAllies = atoi(file.SGetValueDef("0", section.str() + "NumAllies").c_str());
384
385 for (size_t b = 0; b < numAllies; ++b) {
386 std::ostringstream key;
387 key << "GAME\\ALLYTEAM" << a << "\\Ally" << b;
388 const int other = atoi(file.SGetValueDef("0",key.str()).c_str());
389 allyStartingData[a].allies[allyteamRemap[other]] = true;
390 }
391 }
392 }
393
394 unsigned allyCount = 0;
395 if (file.GetValue(allyCount, "GAME\\NumAllyTeams") && (allyStartingData.size() != allyCount)) {
396 LOG_L(L_WARNING, "Incorrect number of ally teams in GameSetup script");
397 }
398 }
399
RemapPlayers()400 void CGameSetup::RemapPlayers()
401 {
402 // relocate Team.TeamLeader field
403 for (size_t a = 0; a < teamStartingData.size(); ++a) {
404 if (playerRemap.find(teamStartingData[a].GetLeader()) == playerRemap.end()) {
405 std::ostringstream buf;
406 buf << "GameSetup: Team " << a << " has invalid leader: " << teamStartingData[a].GetLeader();
407 throw content_error(buf.str());
408 }
409 teamStartingData[a].SetLeader(playerRemap[teamStartingData[a].GetLeader()]);
410 }
411 // relocate AI.hostPlayer field
412 for (size_t a = 0; a < skirmishAIStartingData.size(); ++a) {
413 if (playerRemap.find(skirmishAIStartingData[a].hostPlayer) == playerRemap.end()) {
414 throw content_error("invalid AI.Host in GameSetup script");
415 }
416 skirmishAIStartingData[a].hostPlayer = playerRemap[skirmishAIStartingData[a].hostPlayer];
417 }
418 }
419
RemapTeams()420 void CGameSetup::RemapTeams()
421 {
422 // relocate Player.team field
423 for (size_t a = 0; a < playerStartingData.size(); ++a) {
424 if (playerStartingData[a].spectator) {
425 // start spectating the first team (0)
426 playerStartingData[a].team = 0;
427 } else {
428 if (teamRemap.find(playerStartingData[a].team) == teamRemap.end())
429 throw content_error( str(boost::format("GameSetup: Player %i belong to wrong team: %i") %a %playerStartingData[a].team) );
430
431 playerStartingData[a].team = teamRemap[playerStartingData[a].team];
432 }
433 }
434 // relocate AI.team field
435 for (size_t a = 0; a < skirmishAIStartingData.size(); ++a) {
436 if (teamRemap.find(skirmishAIStartingData[a].team) == teamRemap.end())
437 throw content_error("invalid AI.Team in GameSetup script");
438
439 skirmishAIStartingData[a].team = teamRemap[skirmishAIStartingData[a].team];
440 team_skirmishAI[skirmishAIStartingData[a].team] = &(skirmishAIStartingData[a]);
441 }
442 }
443
RemapAllyteams()444 void CGameSetup::RemapAllyteams()
445 {
446 // relocate Team.Allyteam field
447 for (size_t a = 0; a < teamStartingData.size(); ++a) {
448 if (allyteamRemap.find(teamStartingData[a].teamAllyteam) == allyteamRemap.end()) {
449 throw content_error("invalid Team.Allyteam in GameSetup script");
450 }
451 teamStartingData[a].teamAllyteam = allyteamRemap[teamStartingData[a].teamAllyteam];
452 }
453 }
454
455 // TODO: RemapSkirmishAIs()
Init(const std::string & buf)456 bool CGameSetup::Init(const std::string& buf)
457 {
458 // Copy buffer contents
459 gameSetupText = buf;
460
461 // Parse game parameters
462 TdfParser file(buf.c_str(),buf.size());
463
464 if (!file.SectionExist("GAME"))
465 return false;
466
467 // Used by dedicated server only
468 file.GetTDef(mapHash, unsigned(0), "GAME\\MapHash");
469 file.GetTDef(modHash, unsigned(0), "GAME\\ModHash");
470 file.GetTDef(mapSeed, unsigned(0), "GAME\\MapSeed");
471
472 gameID = file.SGetValueDef("", "GAME\\GameID");
473 modName = file.SGetValueDef("", "GAME\\Gametype");
474 mapName = file.SGetValueDef("", "GAME\\MapName");
475 saveName = file.SGetValueDef("", "GAME\\Savefile");
476 demoName = file.SGetValueDef("", "GAME\\Demofile");
477 hostDemo = !demoName.empty();
478
479 file.GetTDef(gameStartDelay, (unsigned int) 4, "GAME\\GameStartDelay");
480
481 file.GetDef(onlyLocal, "0", "GAME\\OnlyLocal");
482 file.GetDef(useLuaGaia, "1", "GAME\\ModOptions\\LuaGaia");
483 file.GetDef(noHelperAIs, "0", "GAME\\ModOptions\\NoHelperAIs");
484 file.GetDef(maxUnitsPerTeam, "32000", "GAME\\ModOptions\\MaxUnits");
485 file.GetDef(disableMapDamage, "0", "GAME\\ModOptions\\DisableMapDamage");
486 file.GetDef(ghostedBuildings, "1", "GAME\\ModOptions\\GhostedBuildings");
487
488 file.GetDef(maxSpeed, "3.0", "GAME\\ModOptions\\MaxSpeed");
489 file.GetDef(minSpeed, "0.3", "GAME\\ModOptions\\MinSpeed");
490
491 file.GetDef(fixedAllies, "1", "GAME\\ModOptions\\FixedAllies");
492
493 // Read the map & mod options
494 if (file.SectionExist("GAME\\MapOptions")) { mapOptions = file.GetAllValues("GAME\\MapOptions"); }
495 if (file.SectionExist("GAME\\ModOptions")) { modOptions = file.GetAllValues("GAME\\ModOptions"); }
496
497 // Read startPosType (with clamping)
498 int startPosTypeInt;
499 file.GetDef(startPosTypeInt, "0", "GAME\\StartPosType");
500 if (startPosTypeInt < 0 || startPosTypeInt > StartPos_Last)
501 startPosTypeInt = 0;
502 startPosType = (StartPosType)startPosTypeInt;
503
504 // Read subsections
505 std::set<std::string> playersNameList;
506 LoadPlayers(file, playersNameList);
507 LoadSkirmishAIs(file, playersNameList);
508 LoadTeams(file);
509 LoadAllyTeams(file);
510
511 // Relocate indices (for gap removing)
512 RemapPlayers();
513 RemapTeams();
514 RemapAllyteams();
515
516 LoadMutators(file, mutatorsList);
517 LoadUnitRestrictions(file);
518
519 // Postprocessing
520 modName = archiveScanner->NameFromArchive(modName);
521
522 return true;
523 }
524
MapFile() const525 const std::string CGameSetup::MapFile() const
526 {
527 return (archiveScanner->MapNameToMapFile(mapName));
528 }
529
530