1 // HUGE SET - stored in a string
2 string HugeSetOfIntegers_empty()
6 float HugeSetOfIntegers_get(string pArr, float i)
8 return stof(substring(pArr, i * 4, 4));
10 float HugeSetOfIntegers_length(string pArr)
12 return strlen(pArr) / 4;
14 string HugeSetOfIntegers_concat(string a1, string a2)
16 return strcat(a1, a2);
18 string HugeSetOfIntegers_insert(string a1, float n, string a2)
19 // special concat function to build up large lists in less time by binary concatenation
22 s = strcat(" ", ftos(n));
23 return strcat(a1, substring(s, strlen(s) - 4, 4), a2);
26 // generic string stuff
27 float startsWith(string haystack, string needle)
29 return substring(haystack, 0, strlen(needle)) == needle;
31 string extractRestOfLine(string haystack, string needle)
33 if(startsWith(haystack, needle))
34 return substring(haystack, strlen(needle), strlen(haystack) - strlen(needle));
38 // GLOB HANDLING (for all BSP files)
39 float _MapInfo_globopen;
40 float _MapInfo_globcount;
41 float _MapInfo_globhandle;
42 string _MapInfo_GlobItem(float i)
45 s = search_getfilename(_MapInfo_globhandle, i);
46 return substring(s, 5, strlen(s) - 9); // without maps/ and .bsp
49 void MapInfo_Enumerate()
52 search_end(_MapInfo_globhandle);
53 _MapInfo_globhandle = search_begin("maps/*.bsp", TRUE, TRUE);
54 _MapInfo_globcount = search_getsize(_MapInfo_globhandle);
55 _MapInfo_globopen = 1;
58 // filter the info by game type mask (updates MapInfo_count)
59 string _MapInfo_filtered;
60 string MapInfo_FilterGametype_Recursive(float pGametype, float pBegin, float pEnd)
66 return HugeSetOfIntegers_empty();
68 m = floor((pBegin + pEnd) / 2);
70 l = MapInfo_FilterGametype_Recursive(pGametype, pBegin, m);
72 return string_null; // BAIL OUT
73 if(MapInfo_Get_ByName(_MapInfo_GlobItem(m), 1) == 2) // if we generated one... BAIL OUT and let the caller continue in the next frame.
74 return string_null; // BAIL OUT
75 r = MapInfo_FilterGametype_Recursive(pGametype, m + 1, pEnd);
77 return string_null; // BAIL OUT
79 if(MapInfo_Map_supportedGametypes & pGametype)
80 return HugeSetOfIntegers_insert(l, m, r);
82 return HugeSetOfIntegers_concat(l, r);
84 float MapInfo_FilterGametype(float pGametype)
87 strunzone(_MapInfo_filtered);
88 _MapInfo_filtered = MapInfo_FilterGametype_Recursive(pGametype, 0, _MapInfo_globcount);
89 if(!_MapInfo_filtered)
91 dprint("Autogenerated a .mapinfo, doing the rest later.\n");
94 _MapInfo_filtered = strzone(_MapInfo_filtered);
95 MapInfo_count = HugeSetOfIntegers_length(_MapInfo_filtered);
96 dprint("Filter ", ftos(pGametype), " results in ", _MapInfo_filtered, "\n");
101 // load info about the i-th map into the MapInfo_Map_* globals
102 float MapInfo_Get_ByID(float i)
105 if(MapInfo_Get_ByName(_MapInfo_GlobItem(HugeSetOfIntegers_get(_MapInfo_filtered, i)), 0))
107 // TODO save in cache
113 float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
119 float inWorldspawn, l;
123 vector mapMins, mapMaxs;
126 fn = strcat("maps/", pFilename, ".ent");
127 fh = fopen(fn, FILE_READ);
131 fn = strcat("maps/", pFilename, ".bsp");
132 fh = fopen(fn, FILE_READ);
136 print("Analyzing ", fn, " to generate initial mapinfo; please edit that file later\n");
140 MapInfo_Map_supportedGametypes = 0;
141 MapInfo_Map_diameter = 0;
142 MapInfo_Map_spawnpoints = 0;
145 if not((s = fgets(fh)))
147 if(inWorldspawn == 1)
148 if(startsWith(s, "}"))
152 if(startsWith(s, "\"classname\" \"worldspawn\""))
154 else if((v = extractRestOfLine(s, "\"message\" \"")))
156 for(l = strlen(v) - 1; l > 0; --l)
157 if(substring(v, l, 1) == "\"")
159 MapInfo_Map_title = substring(v, 0, l);
164 if((v = extractRestOfLine(s, "\"origin\" \"")))
166 for(l = strlen(v) - 1; l > 0; --l)
167 if(substring(v, l, 1) == "\"")
169 o = stov(strcat("'", substring(v, 0, l), "'"));
170 mapMins_x = min(mapMins_x, o_x);
171 mapMins_y = min(mapMins_y, o_y);
172 mapMins_z = min(mapMins_z, o_z);
173 mapMaxs_x = max(mapMaxs_x, o_x);
174 mapMaxs_y = max(mapMaxs_y, o_y);
175 mapMaxs_z = max(mapMaxs_z, o_z);
177 else if((v = extractRestOfLine(s, "\"classname\" \"")))
179 if(startsWith(v, "dom_controlpoint\""))
180 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
181 else if(startsWith(v, "item_flag_team2\""))
182 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
183 else if(startsWith(v, "runematch_spawn_point\""))
184 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
185 else if(startsWith(v, "target_assault_roundend\""))
186 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
187 else if(startsWith(v, "onslaught_generator\""))
188 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
189 else if(startsWith(v, "info_player_team1\""))
190 ++MapInfo_Map_spawnpoints;
191 else if(startsWith(v, "info_player_team2\""))
192 ++MapInfo_Map_spawnpoints;
193 else if(startsWith(v, "info_player_deathmatch\""))
194 ++MapInfo_Map_spawnpoints;
195 else if(startsWith(v, "info_player_start\""))
196 ++MapInfo_Map_spawnpoints;
202 print(fn, " ended still in worldspawn, BUG\n");
205 MapInfo_Map_diameter = vlen(mapMaxs - mapMins);
207 twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT);
208 if(twoBaseModes && (MapInfo_Map_supportedGametypes == twoBaseModes))
210 // we have a CTF-only or Assault-only map. Don't add other modes then,
211 // as the map is too symmetric for them.
215 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH; // DM always works
216 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH; // Rune always works
217 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS; // LMS always works
219 if(MapInfo_Map_spawnpoints >= 8 && MapInfo_Map_diameter > 4096)
220 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
221 if( MapInfo_Map_diameter < 4096)
222 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA;
223 if(MapInfo_Map_spawnpoints >= 12 && MapInfo_Map_diameter > 5120)
224 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
228 dprint(fn, ": types = ", ftos(MapInfo_Map_supportedGametypes), " MapInfo_Map_spawnpoints ", ftos(MapInfo_Map_spawnpoints), " MapInfo_Map_diameter ", ftos(MapInfo_Map_diameter), "\n");
232 // load info about a map by name into the MapInfo_Map_* globals
233 float MapInfo_Get_ByName(string pFilename, float pAllowGenerate)
240 // default all generic fields so they have "good" values in case something fails
241 MapInfo_Map_title = "Untitled1";
242 MapInfo_Map_description = "Bleh.";
243 MapInfo_Map_supportedGametypes = 0;
244 MapInfo_Map_diameter = 0;
245 MapInfo_Map_spawnpoints = 0;
247 fn = strcat("maps/", pFilename, ".mapinfo");
248 fh = fopen(fn, FILE_READ);
253 r = _MapInfo_Generate(pFilename);
256 fh = fopen(fn, FILE_WRITE);
257 fputs(fh, strcat("title ", MapInfo_Map_title, "\n"));
258 fputs(fh, strcat("description ", MapInfo_Map_description, "\n"));
259 fputs(fh, strcat("_diameter ", ftos(MapInfo_Map_diameter), "\n"));
260 fputs(fh, strcat("_spawnpoints ", ftos(MapInfo_Map_spawnpoints), "\n"));
261 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH) fputs(fh, "type dm 30 20\n");
262 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH) fputs(fh, "type tdm 50 20 2\n"); // TODO count tdm_team entities
263 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DOMINATION) fputs(fh, "type dom 200 20 2\n"); // TODO count tdm_team entities
264 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_CTF) fputs(fh, "type ctf 300 20\n");
265 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RUNEMATCH) fputs(fh, "type rune 200 20\n");
266 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS) fputs(fh, "type lms 9 20\n");
267 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ARENA) fputs(fh, "type arena 10 20\n");
268 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEYHUNT) fputs(fh, "type kh 1000 20 3\n");
269 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ASSAULT) fputs(fh, "type as 20\n");
270 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ONSLAUGHT) fputs(fh, "type ons 20\n");
277 if not((s = fgets(fh)))
282 MapInfo_Map_title = substring(s, 6, strlen(s) - 6); // without "title"
283 else if(t == "description")
284 MapInfo_Map_description = substring(s, 12, strlen(s) - 12);
285 else if(t == "_diameter")
286 MapInfo_Map_diameter = stof(argv(1));
287 else if(t == "_spawnpoints")
288 MapInfo_Map_spawnpoints = stof(argv(1));
292 if (t == "dm") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;
293 else if(t == "tdm") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
294 else if(t == "dom") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
295 else if(t == "ctf") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
296 else if(t == "rune") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
297 else if(t == "lms") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;
298 else if(t == "arena") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA;
299 else if(t == "kh") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
300 else if(t == "as") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
301 else if(t == "ons") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
303 dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
306 dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
309 if(MapInfo_Map_supportedGametypes != 0)
311 dprint("Map ", pFilename, " supports no game types, ignored\n");
315 string MapInfo_FixName(string s)
317 // if there is exactly one map of prefix s, return it
318 // if not, return the null string
319 // note that DP sorts glob results... so I can use a binary search
324 // invariants: r is behind s, l-1 is equal or before
327 m = floor((l + r) / 2);
328 cmp = strcasecmp(_MapInfo_GlobItem(HugeSetOfIntegers_get(_MapInfo_filtered, m)), s);
330 return s; // found and good
332 l = m + 1; // l-1 is before s
336 // r == l, so: l is behind s, l-1 is before
337 // SO: if there is any, l is the one with the right prefix
338 // and l+1 may be one too
339 if(l == MapInfo_count)
340 return string_null; // no match, behind last item
341 match = _MapInfo_GlobItem(HugeSetOfIntegers_get(_MapInfo_filtered, l));
342 if(!startsWith(match, s))
343 return string_null; // wrong prefix
344 if(l == MapInfo_count - 1)
345 return match; // last one, nothing can follow => unique
346 if(startsWith(_MapInfo_GlobItem(HugeSetOfIntegers_get(_MapInfo_filtered, l + 1)), s))
347 return string_null; // ambigous match