]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/common/mapinfo.qc
start of mapinfo system; beware: menu-div0test now starts up longer for the first...
[divverent/nexuiz.git] / data / qcsrc / common / mapinfo.qc
1 // HUGE SET - stored in a string
2 string HugeSetOfIntegers_empty()
3 {
4         return "";
5 }
6 float HugeSetOfIntegers_get(string pArr, float i)
7 {
8         return stof(substring(pArr, i * 4, 4));
9 }
10 float HugeSetOfIntegers_length(string pArr)
11 {
12         return strlen(pArr) / 4;
13 }
14 string HugeSetOfIntegers_concat(string a1, string a2)
15 {
16         return strcat(a1, a2);
17 }
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
20 {
21         string s;
22         s = strcat("    ", ftos(n));
23         return strcat(a1, substring(s, strlen(s) - 4, 4), a2);
24 }
25
26 // generic string stuff
27 float startsWith(string haystack, string needle)
28 {
29         return substring(haystack, 0, strlen(needle)) == needle;
30 }
31 string extractRestOfLine(string haystack, string needle)
32 {
33         if(startsWith(haystack, needle))
34                 return substring(haystack, strlen(needle), strlen(haystack) - strlen(needle));
35         return string_null;
36 }
37
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)
43 {
44         string s;
45         s = search_getfilename(_MapInfo_globhandle, i);
46         return substring(s, 5, strlen(s) - 9); // without maps/ and .bsp
47 }
48
49 void MapInfo_Enumerate()
50 {
51         if(_MapInfo_globopen)
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;
56 }
57
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)
61 {
62         float m;
63         string l, r;
64
65         if(pBegin == pEnd)
66                 return HugeSetOfIntegers_empty();
67
68         m = floor((pBegin + pEnd) / 2);
69
70         l = MapInfo_FilterGametype_Recursive(pGametype, pBegin, m);
71         if not(l)
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);
76         if not(r)
77                 return string_null; // BAIL OUT
78
79         if(MapInfo_Map_supportedGametypes & pGametype)
80                 return HugeSetOfIntegers_insert(l, m, r);
81         else
82                 return HugeSetOfIntegers_concat(l, r);
83 }
84 float MapInfo_FilterGametype(float gametype)
85 {
86         _MapInfo_filtered = MapInfo_FilterGametype_Recursive(gametype, 0, _MapInfo_globcount);
87         if(!_MapInfo_filtered)
88         {
89                 dprint("Autogenerated a .mapinfo, bailing out to avoid loop counter\n");
90                 return 0;
91         }
92         MapInfo_count = HugeSetOfIntegers_length(_MapInfo_filtered);
93         dprint("Filter ", ftos(gametype), " results in ", _MapInfo_filtered, "\n");
94         return 1;
95 }
96
97 // load info about the i-th map into the MapInfo_Map_* globals
98 float MapInfo_Get_ByID(float i); // 1 on success, 0 on failure
99
100 float _MapInfo_Generate(string pFilename)
101 {
102         string fn;
103         float fh;
104         string s, v;
105         vector o;
106         float inWorldspawn, l;
107
108         float spawns, diameter;
109         vector mapMins, mapMaxs;
110
111         fn = strcat("maps/", pFilename, ".ent");
112         fh = fopen(fn, FILE_READ);
113         if(fh < 0)
114         {
115                 fn = strcat("maps/", pFilename, ".bsp");
116                 fh = fopen(fn, FILE_READ);
117         }
118         if(fh < 0)
119                 return 0;
120         dprint("Analyzing ", fn, " to generate initial mapinfo\n");
121
122         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;      // DM always works
123         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;       // Rune always works
124         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;             // LMS always works
125
126         inWorldspawn = 2;
127
128         for(;;)
129         {
130                 if not((s = fgets(fh)))
131                         break;
132                 if(inWorldspawn == 1)
133                         if(startsWith(s, "}"))
134                                 inWorldspawn = 0;
135                 if(inWorldspawn)
136                 {
137                         if(startsWith(s, "\"classname\" \"worldspawn\""))
138                                 inWorldspawn = 1;
139                         else if((v = extractRestOfLine(s, "\"message\" \"")))
140                         {
141                                 for(l = strlen(v) - 1; l > 0; --l)
142                                         if(substring(v, l, 1) == "\"")
143                                                 break;
144                                 MapInfo_Map_title = substring(v, 0, l);
145                         }
146                 }
147                 else
148                 {
149                         if((v = extractRestOfLine(s, "\"origin\" \"")))
150                         {
151                                 for(l = strlen(v) - 1; l > 0; --l)
152                                         if(substring(v, l, 1) == "\"")
153                                                 break;
154                                 o = stov(strcat("'", substring(v, 0, l), "'"));
155                                 mapMins_x = min(mapMins_x, o_x);
156                                 mapMins_y = min(mapMins_y, o_y);
157                                 mapMins_z = min(mapMins_z, o_z);
158                                 mapMaxs_x = max(mapMaxs_x, o_x);
159                                 mapMaxs_y = max(mapMaxs_y, o_y);
160                                 mapMaxs_z = max(mapMaxs_z, o_z);
161                         }
162                         else if((v = extractRestOfLine(s, "\"classname\" \"")))
163                         {
164                                 if(startsWith(v, "dom_controlpoint\""))
165                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
166                                 else if(startsWith(v, "item_flag_team2\""))
167                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
168                                 else if(startsWith(v, "runematch_spawn_point\""))
169                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
170                                 else if(startsWith(v, "target_assault_roundend\""))
171                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
172                                 else if(startsWith(v, "onslaught_generator\""))
173                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
174                                 else if(startsWith(v, "info_player_team1\""))
175                                         ++spawns;
176                                 else if(startsWith(v, "info_player_team2\""))
177                                         ++spawns;
178                                 else if(startsWith(v, "info_player_deathmatch\""))
179                                         ++spawns;
180                                 else if(startsWith(v, "info_player_start\""))
181                                         ++spawns;
182                         }
183                 }
184         }
185         if(inWorldspawn)
186         {
187                 print(strcat(fn, " ended still in worldspawn, BUG"));
188                 return 0;
189         }
190         diameter = vlen(mapMaxs - mapMins);
191         if(spawns >= 8  && diameter > 2048)
192                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
193         if(                diameter < 4096)
194                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA;
195         if(spawns >= 16 && diameter > 4096)
196                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
197         fclose(fh);
198         dprint(fn, ": types = ", ftos(MapInfo_Map_supportedGametypes), " spawns ", ftos(spawns), " diameter ", ftos(diameter), "\n");
199         return 1;
200 }
201
202 // load info about a map by name into the MapInfo_Map_* globals
203 float MapInfo_Get_ByName(string pFilename, float pAllowGenerate)
204 {
205         string fn;
206         string s, t;
207         float fh;
208
209         // default all generic fields so they have "good" values in case something fails
210         MapInfo_Map_title = "Untitled1";
211         MapInfo_Map_description = "Bleh.";
212         MapInfo_Map_supportedGametypes = 0;
213
214         fn = strcat("maps/", pFilename, ".mapinfo");
215         fh = fopen(fn, FILE_READ);
216         if(fh < 0)
217         {
218                 if(!pAllowGenerate)
219                         return 0;
220                 if(!_MapInfo_Generate(pFilename))
221                         return 0;
222                 fh = fopen(fn, FILE_WRITE);
223                 fputs(fh, strcat("title ", MapInfo_Map_title, "\n"));
224                 fputs(fh, strcat("description ", MapInfo_Map_description, "\n"));
225                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH)      fputs(fh, "type dm 30 20\n");
226                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH) fputs(fh, "type tdm 50 20 2\n"); // TODO count tdm_team entities
227                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DOMINATION)      fputs(fh, "type dom 200 20 2\n"); // TODO count tdm_team entities
228                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_CTF)             fputs(fh, "type ctf 300 20\n");
229                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RUNEMATCH)       fputs(fh, "type rune 200 20\n");
230                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS)             fputs(fh, "type lms 9 20\n");
231                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ARENA)           fputs(fh, "type arena 10 20\n");
232                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEYHUNT)         fputs(fh, "type kh 1000 20\n");
233                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ASSAULT)         fputs(fh, "type as 20\n");
234                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ONSLAUGHT)       fputs(fh, "type ons 20\n");
235                 fclose(fh);
236                 return 2;
237         }
238         for(;;)
239         {
240                 if not((s = fgets(fh)))
241                         break;
242                 tokenize(s);
243                 t = argv(0);
244                 if(t == "title")
245                         MapInfo_Map_title = substring(s, 6, strlen(s) - 6); // without "title"
246                 else if(t == "description")
247                         MapInfo_Map_description = substring(s, 12, strlen(s) - 12);
248                 else if(t == "type")
249                 {
250                         t = argv(1);
251                         if     (t == "dm")    MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;
252                         else if(t == "tdm")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
253                         else if(t == "dom")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
254                         else if(t == "ctf")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
255                         else if(t == "rune")  MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
256                         else if(t == "lms")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;
257                         else if(t == "arena") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA;
258                         else if(t == "kh")    MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
259                         else if(t == "as")    MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
260                         else if(t == "ons")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
261                         else
262                                 dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
263                 }
264                 else
265                         dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
266         }
267         fclose(fh);
268         if(MapInfo_Map_supportedGametypes != 0)
269                 return 1;
270         dprint("Map ", pFilename, " supports no game types, ignored\n");
271         return 0;
272 }