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