]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/common/mapinfo.qc
Prepare a "building map information" screen in menu; make server command "sv_cmd...
[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         if(_MapInfo_filtered)
87                 strunzone(_MapInfo_filtered);
88         _MapInfo_filtered = MapInfo_FilterGametype_Recursive(gametype, 0, _MapInfo_globcount);
89         if(!_MapInfo_filtered)
90         {
91                 dprint("Autogenerated a .mapinfo, doing the rest later.\n");
92                 return 0;
93         }
94         _MapInfo_filtered = strzone(_MapInfo_filtered);
95         MapInfo_count = HugeSetOfIntegers_length(_MapInfo_filtered);
96         dprint("Filter ", ftos(gametype), " results in ", _MapInfo_filtered, "\n");
97         return 1;
98 }
99
100 // load info about the i-th map into the MapInfo_Map_* globals
101 float MapInfo_Get_ByID(float i); // 1 on success, 0 on failure
102
103 float _MapInfo_Generate(string pFilename)
104 {
105         string fn;
106         float fh;
107         string s, v;
108         vector o;
109         float inWorldspawn, l;
110
111         float spawns, diameter;
112         vector mapMins, mapMaxs;
113
114         fn = strcat("maps/", pFilename, ".ent");
115         fh = fopen(fn, FILE_READ);
116         if(fh < 0)
117         {
118                 fn = strcat("maps/", pFilename, ".bsp");
119                 fh = fopen(fn, FILE_READ);
120         }
121         if(fh < 0)
122                 return 0;
123         dprint("Analyzing ", fn, " to generate initial mapinfo\n");
124
125         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;      // DM always works
126         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;       // Rune always works
127         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;             // LMS always works
128
129         inWorldspawn = 2;
130
131         for(;;)
132         {
133                 if not((s = fgets(fh)))
134                         break;
135                 if(inWorldspawn == 1)
136                         if(startsWith(s, "}"))
137                                 inWorldspawn = 0;
138                 if(inWorldspawn)
139                 {
140                         if(startsWith(s, "\"classname\" \"worldspawn\""))
141                                 inWorldspawn = 1;
142                         else if((v = extractRestOfLine(s, "\"message\" \"")))
143                         {
144                                 for(l = strlen(v) - 1; l > 0; --l)
145                                         if(substring(v, l, 1) == "\"")
146                                                 break;
147                                 MapInfo_Map_title = substring(v, 0, l);
148                         }
149                 }
150                 else
151                 {
152                         if((v = extractRestOfLine(s, "\"origin\" \"")))
153                         {
154                                 for(l = strlen(v) - 1; l > 0; --l)
155                                         if(substring(v, l, 1) == "\"")
156                                                 break;
157                                 o = stov(strcat("'", substring(v, 0, l), "'"));
158                                 mapMins_x = min(mapMins_x, o_x);
159                                 mapMins_y = min(mapMins_y, o_y);
160                                 mapMins_z = min(mapMins_z, o_z);
161                                 mapMaxs_x = max(mapMaxs_x, o_x);
162                                 mapMaxs_y = max(mapMaxs_y, o_y);
163                                 mapMaxs_z = max(mapMaxs_z, o_z);
164                         }
165                         else if((v = extractRestOfLine(s, "\"classname\" \"")))
166                         {
167                                 if(startsWith(v, "dom_controlpoint\""))
168                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
169                                 else if(startsWith(v, "item_flag_team2\""))
170                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
171                                 else if(startsWith(v, "runematch_spawn_point\""))
172                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
173                                 else if(startsWith(v, "target_assault_roundend\""))
174                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
175                                 else if(startsWith(v, "onslaught_generator\""))
176                                         MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
177                                 else if(startsWith(v, "info_player_team1\""))
178                                         ++spawns;
179                                 else if(startsWith(v, "info_player_team2\""))
180                                         ++spawns;
181                                 else if(startsWith(v, "info_player_deathmatch\""))
182                                         ++spawns;
183                                 else if(startsWith(v, "info_player_start\""))
184                                         ++spawns;
185                         }
186                 }
187         }
188         if(inWorldspawn)
189         {
190                 print(strcat(fn, " ended still in worldspawn, BUG"));
191                 return 0;
192         }
193         diameter = vlen(mapMaxs - mapMins);
194         if(spawns >= 8  && diameter > 2048)
195                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
196         if(                diameter < 4096)
197                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA;
198         if(spawns >= 16 && diameter > 4096)
199                 MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
200         fclose(fh);
201         dprint(fn, ": types = ", ftos(MapInfo_Map_supportedGametypes), " spawns ", ftos(spawns), " diameter ", ftos(diameter), "\n");
202         return 1;
203 }
204
205 // load info about a map by name into the MapInfo_Map_* globals
206 float MapInfo_Get_ByName(string pFilename, float pAllowGenerate)
207 {
208         string fn;
209         string s, t;
210         float fh;
211
212         // default all generic fields so they have "good" values in case something fails
213         MapInfo_Map_title = "Untitled1";
214         MapInfo_Map_description = "Bleh.";
215         MapInfo_Map_supportedGametypes = 0;
216
217         fn = strcat("maps/", pFilename, ".mapinfo");
218         fh = fopen(fn, FILE_READ);
219         if(fh < 0)
220         {
221                 if(!pAllowGenerate)
222                         return 0;
223                 if(!_MapInfo_Generate(pFilename))
224                         return 0;
225                 fh = fopen(fn, FILE_WRITE);
226                 fputs(fh, strcat("title ", MapInfo_Map_title, "\n"));
227                 fputs(fh, strcat("description ", MapInfo_Map_description, "\n"));
228                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH)      fputs(fh, "type dm 30 20\n");
229                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH) fputs(fh, "type tdm 50 20 2\n"); // TODO count tdm_team entities
230                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DOMINATION)      fputs(fh, "type dom 200 20 2\n"); // TODO count tdm_team entities
231                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_CTF)             fputs(fh, "type ctf 300 20\n");
232                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RUNEMATCH)       fputs(fh, "type rune 200 20\n");
233                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS)             fputs(fh, "type lms 9 20\n");
234                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ARENA)           fputs(fh, "type arena 10 20\n");
235                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEYHUNT)         fputs(fh, "type kh 1000 20\n");
236                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ASSAULT)         fputs(fh, "type as 20\n");
237                 if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ONSLAUGHT)       fputs(fh, "type ons 20\n");
238                 fclose(fh);
239                 return 2;
240         }
241         for(;;)
242         {
243                 if not((s = fgets(fh)))
244                         break;
245                 tokenize(s);
246                 t = argv(0);
247                 if(t == "title")
248                         MapInfo_Map_title = substring(s, 6, strlen(s) - 6); // without "title"
249                 else if(t == "description")
250                         MapInfo_Map_description = substring(s, 12, strlen(s) - 12);
251                 else if(t == "type")
252                 {
253                         t = argv(1);
254                         if     (t == "dm")    MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;
255                         else if(t == "tdm")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
256                         else if(t == "dom")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DOMINATION;
257                         else if(t == "ctf")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CTF;
258                         else if(t == "rune")  MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;
259                         else if(t == "lms")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;
260                         else if(t == "arena") MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ARENA;
261                         else if(t == "kh")    MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEYHUNT;
262                         else if(t == "as")    MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ASSAULT;
263                         else if(t == "ons")   MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_ONSLAUGHT;
264                         else
265                                 dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
266                 }
267                 else
268                         dprint("Map ", pFilename, " supports unknown game type ", t, ", ignored\n");
269         }
270         fclose(fh);
271         if(MapInfo_Map_supportedGametypes != 0)
272                 return 1;
273         dprint("Map ", pFilename, " supports no game types, ignored\n");
274         return 0;
275 }