1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include <iostream>
4 #include "StartScriptGen.h"
5 #include "AIScriptHandler.h"
6 #include "Game/GlobalUnsynced.h"
7 #include "System/TdfParser.h"
8 #include "System/Util.h"
9 #include "System/Config/ConfigHandler.h"
10 #include "System/FileSystem/ArchiveScanner.h"
11 #include "System/UriParser.h"
12 #include "System/FileSystem/RapidHandler.h"
13 #include "System/Log/ILog.h"
14 #include <boost/cstdint.hpp>
15 
16 
17 CONFIG(bool, NoHelperAIs).defaultValue(false);
18 
19 namespace StartScriptGen {
20 
21 //////////////////////////////////////////////////////////////////////////////
22 //
23 //  Helpers
24 //
25 
ExtractVersionNumber(const std::string & version)26 	static boost::uint64_t ExtractVersionNumber(const std::string& version)
27 	{
28 		std::istringstream iss(version);
29 		boost::uint64_t versionInt = 0;
30 		int num;
31 		while (true) {
32 			if (iss >> num) {
33 				versionInt = versionInt * 1000 + std::abs(num);
34 			} else
35 			if (iss.eof()) {
36 				break;
37 			} else
38 			if (iss.fail()) {
39 				iss.clear();
40 				iss.ignore();
41 			}
42 		}
43 		return versionInt;
44 	}
45 
46 
GetGameByExactName(const std::string & lazyName,std::string * applicableName)47 	static bool GetGameByExactName(const std::string& lazyName, std::string* applicableName)
48 	{
49 		const CArchiveScanner::ArchiveData& aData = archiveScanner->GetArchiveData(lazyName);
50 
51 		std::string error;
52 		if (aData.IsValid(error)) {
53 			if (aData.GetModType() == modtype::primary) {
54 				*applicableName = lazyName;
55 				return true;
56 			}
57 		}
58 
59 		return false;
60 	}
61 
62 
GetGameByShortName(const std::string & lazyName,std::string * applicableName)63 	static bool GetGameByShortName(const std::string& lazyName, std::string* applicableName)
64 	{
65 		const std::string lowerLazyName = StringToLower(lazyName);
66 		const std::vector<CArchiveScanner::ArchiveData>& found = archiveScanner->GetPrimaryMods();
67 
68 		std::string matchingName;
69 		std::string matchingVersion;
70 		boost::uint64_t matchingVersionInt = 0;
71 
72 		for (std::vector<CArchiveScanner::ArchiveData>::const_iterator it = found.begin(); it != found.end(); ++it) {
73 			if (lowerLazyName == StringToLower(it->GetShortName())) {
74 				// find latest version of the game
75 				boost::uint64_t versionInt = ExtractVersionNumber(it->GetVersion());
76 
77 				if (versionInt > matchingVersionInt) {
78 					matchingName = it->GetNameVersioned();
79 					matchingVersion = it->GetVersion();
80 					matchingVersionInt = versionInt;
81 					continue;
82 				}
83 
84 				if (versionInt == matchingVersionInt) {
85 					// very bad solution, fails with `10.0` vs. `9.10`
86 					const int compareInt = matchingVersion.compare(it->GetVersion());
87 					if (compareInt <= 0) {
88 						matchingName = it->GetNameVersioned();
89 						matchingVersion = it->GetVersion();
90 						//matchingVersionInt = versionInt;
91 					}
92 				}
93 			}
94 		}
95 
96 		if (!matchingName.empty()) {
97 			*applicableName = matchingName;
98 			return true;
99 		}
100 
101 		return false;
102 	}
103 
104 
GetRandomGame(const std::string & lazyName,std::string * applicableName)105 	static bool GetRandomGame(const std::string& lazyName, std::string* applicableName)
106 	{
107 		if (std::string("random").find(lazyName) != std::string::npos) {
108 			const std::vector<CArchiveScanner::ArchiveData>& games = archiveScanner->GetPrimaryMods();
109 			if (!games.empty()) {
110 				*applicableName = games[gu->RandInt() % games.size()].GetNameVersioned();
111 				return true;
112 			}
113 		}
114 		return false;
115 	}
116 
117 
GetMapByExactName(const std::string & lazyName,std::string * applicableName)118 	static bool GetMapByExactName(const std::string& lazyName, std::string* applicableName)
119 	{
120 		const CArchiveScanner::ArchiveData& aData = archiveScanner->GetArchiveData(lazyName);
121 
122 		std::string error;
123 		if (aData.IsValid(error)) {
124 			if (aData.GetModType() == modtype::map) {
125 				*applicableName = lazyName;
126 				return true;
127 			}
128 		}
129 
130 		return false;
131 	}
132 
133 
GetMapBySubString(const std::string & lazyName,std::string * applicableName)134 	static bool GetMapBySubString(const std::string& lazyName, std::string* applicableName)
135 	{
136 		const std::string lowerLazyName = StringToLower(lazyName);
137 		const std::vector<std::string>& found = archiveScanner->GetMaps();
138 
139 		std::vector<std::string> substrings;
140 		std::istringstream iss(lowerLazyName);
141 		std::string buf;
142 		while (iss >> buf) {
143 			substrings.push_back(buf);
144 		}
145 
146 		std::string matchingName;
147 		size_t matchingLength = 1e6;
148 
149 		for (std::vector<std::string>::const_iterator it = found.begin(); it != found.end(); ++it) {
150 			const std::string lowerMapName = StringToLower(*it);
151 
152 			// search for all wanted substrings
153 			bool fits = true;
154 			for (std::vector<std::string>::const_iterator jt = substrings.begin(); jt != substrings.end(); ++jt) {
155 				const std::string& substr = *jt;
156 				if (lowerMapName.find(substr) == std::string::npos) {
157 					fits = false;
158 					break;
159 				}
160 			}
161 
162 			if (fits) {
163 				// shortest fitting string wins
164 				const int nameLength = lowerMapName.length();
165 				if (nameLength < matchingLength) {
166 					matchingName = *it;
167 					matchingLength = nameLength;
168 				}
169 			}
170 		}
171 
172 		if (!matchingName.empty()) {
173 			*applicableName = matchingName;
174 			return true;
175 		}
176 
177 		return false;
178 	}
179 
180 
GetRandomMap(const std::string & lazyName,std::string * applicableName)181 	static bool GetRandomMap(const std::string& lazyName, std::string* applicableName)
182 	{
183 		if (std::string("random").find(lazyName) != std::string::npos) {
184 			const std::vector<std::string>& maps = archiveScanner->GetMaps();
185 			if (!maps.empty()) {
186 				*applicableName = maps[gu->RandInt() % maps.size()];
187 				return true;
188 			}
189 		}
190 		return false;
191 	}
192 
GetGameByRapidTag(const std::string & lazyName,std::string & tag)193 	static bool GetGameByRapidTag(const std::string& lazyName, std::string& tag)
194 	{
195 		if (!ParseRapidUri(lazyName, tag))
196 			return false;
197 		tag = GetRapidName(tag);
198 		return !tag.empty();
199 	}
200 
201 
GetGame(const std::string & lazyName)202 	static std::string GetGame(const std::string& lazyName)
203 	{
204 		std::string applicableName = lazyName;
205 		if (GetGameByExactName(lazyName, &applicableName)) return applicableName;
206 		if (GetGameByShortName(lazyName, &applicableName)) return applicableName;
207 		if (GetRandomGame(lazyName, &applicableName))      return applicableName;
208 		if (GetGameByRapidTag(lazyName, applicableName))   return applicableName;
209 
210 		return lazyName;
211 	}
212 
213 
GetMap(const std::string & lazyName)214 	static std::string GetMap(const std::string& lazyName)
215 	{
216 		std::string applicableName = lazyName;
217 		if (GetMapByExactName(lazyName, &applicableName)) return applicableName;
218 		if (GetMapBySubString(lazyName, &applicableName)) return applicableName;
219 		if (GetRandomMap(lazyName, &applicableName))      return applicableName;
220 		//TODO add a string similarity search?
221 
222 		return lazyName;
223 	}
224 
225 
226 //////////////////////////////////////////////////////////////////////////////
227 //
228 //  Interface
229 //
230 
CreateMinimalSetup(const std::string & game,const std::string & map)231 std::string CreateMinimalSetup(const std::string& game, const std::string& map)
232 {
233 	const std::string playername = configHandler->GetString("name");
234 	TdfParser::TdfSection setup;
235 	TdfParser::TdfSection* g = setup.construct_subsection("GAME");
236 
237 	g->add_name_value("Mapname", GetMap(map));
238 	g->add_name_value("Gametype", GetGame(game));
239 
240 	TdfParser::TdfSection* modopts = g->construct_subsection("MODOPTIONS");
241 	modopts->AddPair("MaxSpeed", 20);
242 	modopts->AddPair("MinimalSetup", 1); //use for ingame detecting this type of start
243 
244 	g->AddPair("IsHost", 1);
245 	g->add_name_value("MyPlayerName", playername);
246 
247 	TdfParser::TdfSection* player0 = g->construct_subsection("PLAYER0");
248 	player0->add_name_value("Name", playername);
249 	player0->AddPair("Team", 0);
250 
251 	TdfParser::TdfSection* team0 = g->construct_subsection("TEAM0");
252 	team0->AddPair("TeamLeader", 0);
253 	team0->AddPair("AllyTeam", 0);
254 
255 	TdfParser::TdfSection* ally0 = g->construct_subsection("ALLYTEAM0");
256 	ally0->AddPair("NumAllies", 0);
257 
258 
259 	std::ostringstream str;
260 	setup.print(str);
261 	printf("%s\n", str.str().c_str());
262 	return str.str();
263 }
264 
265 
CreateDefaultSetup(const std::string & map,const std::string & game,const std::string & ai,const std::string & playername)266 std::string CreateDefaultSetup(const std::string& map, const std::string& game, const std::string& ai,
267 			const std::string& playername)
268 {
269 	//FIXME:: duplicate code with CreateMinimalSetup
270 	TdfParser::TdfSection setup;
271 	TdfParser::TdfSection* g = setup.construct_subsection("GAME");
272 	g->add_name_value("Mapname", map);
273 	g->add_name_value("Gametype", game);
274 
275 	TdfParser::TdfSection* modopts = g->construct_subsection("MODOPTIONS");
276 	modopts->AddPair("MaxSpeed", 20);
277 
278 	g->AddPair("IsHost", 1);
279 	g->add_name_value("MyPlayerName", playername);
280 
281 	g->AddPair("NoHelperAIs", configHandler->GetBool("NoHelperAIs"));
282 
283 	TdfParser::TdfSection* player0 = g->construct_subsection("PLAYER0");
284 	player0->add_name_value("Name", playername);
285 	player0->AddPair("Team", 0);
286 
287 	const bool isSkirmishAITestScript = CAIScriptHandler::Instance().IsSkirmishAITestScript(ai);
288 	if (isSkirmishAITestScript) {
289 		SkirmishAIData aiData = CAIScriptHandler::Instance().GetSkirmishAIData(ai);
290 		TdfParser::TdfSection* ai = g->construct_subsection("AI0");
291 		ai->add_name_value("Name", "Enemy");
292 		ai->add_name_value("ShortName", aiData.shortName);
293 		ai->add_name_value("Version", aiData.version);
294 		ai->AddPair("Host", 0);
295 		ai->AddPair("Team", 1);
296 	} else if (!ai.empty()) { // is no native ai, try lua ai
297 		TdfParser::TdfSection* aisec = g->construct_subsection("AI0");
298 		aisec->add_name_value("Name", "AI: " + ai);
299 		aisec->add_name_value("ShortName", ai);
300 		aisec->AddPair("Host", 0);
301 		aisec->AddPair("Team", 1);
302 	} else {
303 		TdfParser::TdfSection* player1 = g->construct_subsection("PLAYER1");
304 		player1->add_name_value("Name", "Enemy");
305 		player1->AddPair("Team", 1);
306 	}
307 
308 	TdfParser::TdfSection* team0 = g->construct_subsection("TEAM0");
309 	team0->AddPair("TeamLeader", 0);
310 	team0->AddPair("AllyTeam", 0);
311 
312 	TdfParser::TdfSection* team1 = g->construct_subsection("TEAM1");
313 	if (isSkirmishAITestScript || !ai.empty()) {
314 		team1->AddPair("TeamLeader", 0);
315 	} else {
316 		team1->AddPair("TeamLeader", 1);
317 	}
318 	team1->AddPair("AllyTeam", 1);
319 
320 	TdfParser::TdfSection* ally0 = g->construct_subsection("ALLYTEAM0");
321 	ally0->AddPair("NumAllies", 0);
322 
323 	TdfParser::TdfSection* ally1 = g->construct_subsection("ALLYTEAM1");
324 	ally1->AddPair("NumAllies", 0);
325 
326 	std::ostringstream str;
327 	setup.print(str);
328 
329 	return str.str();
330 }
331 
332 } //namespace StartScriptGen
333