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