]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/mapvoting.qc
Minor cleanup of world.qc, move most intermission and map handling code into intermis...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mapvoting.qc
1 #include "mapvoting.qh"
2
3 #include <server/client.qh>
4 #include <common/weapons/_all.qh>
5 #include <common/stats.qh>
6 #include <server/gamelog.qh>
7 #include <server/intermission.qh>
8 #include <server/miscfunctions.qh>
9 #include "world.qh"
10 #include "command/cmd.qh"
11 #include "command/getreplies.qh"
12 #include "../common/constants.qh"
13 #include <common/net_linked.qh>
14 #include "../common/mapinfo.qh"
15 #include "../common/playerstats.qh"
16 #include <common/state.qh>
17 #include "../common/util.qh"
18
19
20 // definitions
21
22 float mapvote_nextthink;
23 float mapvote_keeptwotime;
24 float mapvote_timeout;
25 const int MAPVOTE_SCREENSHOT_DIRS_COUNT = 4;
26 string mapvote_screenshot_dirs[MAPVOTE_SCREENSHOT_DIRS_COUNT];
27 int mapvote_screenshot_dirs_count;
28
29 int mapvote_count;
30 int mapvote_count_real;
31 string mapvote_maps[MAPVOTE_COUNT];
32 int mapvote_maps_screenshot_dir[MAPVOTE_COUNT];
33 string mapvote_maps_pakfile[MAPVOTE_COUNT];
34 bool mapvote_maps_suggested[MAPVOTE_COUNT];
35 string mapvote_suggestions[MAPVOTE_COUNT];
36 int mapvote_suggestion_ptr;
37 int mapvote_voters;
38 int mapvote_selections[MAPVOTE_COUNT];
39 int mapvote_maps_flags[MAPVOTE_COUNT];
40 bool mapvote_run;
41 bool mapvote_detail;
42 bool mapvote_abstain;
43 .int mapvote;
44
45 entity mapvote_ent;
46
47 /**
48  * Returns the gamtype ID from its name, if type_name isn't a real gametype it
49  * checks for sv_vote_gametype_(type_name)_type
50  */
51 Gametype GameTypeVote_Type_FromString(string type_name)
52 {
53         Gametype type = MapInfo_Type_FromString(type_name, false);
54         if (type == NULL)
55                 type = MapInfo_Type_FromString(cvar_string(
56                         strcat("sv_vote_gametype_",type_name,"_type")), false);
57         return type;
58 }
59
60 int GameTypeVote_AvailabilityStatus(string type_name)
61 {
62         int flag = GTV_FORBIDDEN;
63
64         Gametype type = MapInfo_Type_FromString(type_name, false);
65         if ( type == NULL )
66         {
67                 type = MapInfo_Type_FromString(cvar_string(
68                         strcat("sv_vote_gametype_",type_name,"_type")), false);
69                 flag |= GTV_CUSTOM;
70         }
71
72         if( type == NULL )
73                 return flag;
74
75         if ( autocvar_nextmap != "" )
76         {
77                 if ( !MapInfo_Get_ByName(autocvar_nextmap, false, NULL) )
78                         return flag;
79                 if (!(MapInfo_Map_supportedGametypes & type.m_flags))
80                         return flag;
81         }
82
83         return flag | GTV_AVAILABLE;
84 }
85
86 int GameTypeVote_GetMask()
87 {
88         int n, j, gametype_mask;
89         n = tokenizebyseparator(autocvar_sv_vote_gametype_options, " ");
90         n = min(MAPVOTE_COUNT, n);
91         gametype_mask = 0;
92         for(j = 0; j < n; ++j)
93                 gametype_mask |= GameTypeVote_Type_FromString(argv(j)).m_flags;
94         return gametype_mask;
95 }
96
97 string GameTypeVote_MapInfo_FixName(string m)
98 {
99         if ( autocvar_sv_vote_gametype )
100         {
101                 MapInfo_Enumerate();
102                 _MapInfo_FilterGametype(GameTypeVote_GetMask(), 0, MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
103         }
104         return MapInfo_FixName(m);
105 }
106
107 void MapVote_ClearAllVotes()
108 {
109         FOREACH_CLIENT(true, { it.mapvote = 0; });
110 }
111
112 void MapVote_UnzoneStrings()
113 {
114         for(int j = 0; j < mapvote_count; ++j)
115         {
116                 strfree(mapvote_maps[j]);
117                 strfree(mapvote_maps_pakfile[j]);
118         }
119 }
120
121 string MapVote_Suggest(entity this, string m)
122 {
123         int i;
124         if(m == "")
125                 return "That's not how to use this command.";
126         if(!autocvar_g_maplist_votable_suggestions)
127                 return "Suggestions are not accepted on this server.";
128         if(mapvote_initialized)
129         if(!gametypevote)
130                 return "Can't suggest - voting is already in progress!";
131         m = GameTypeVote_MapInfo_FixName(m);
132         if (!m)
133                 return "The map you suggested is not available on this server.";
134         if(!autocvar_g_maplist_votable_suggestions_override_mostrecent)
135                 if(Map_IsRecent(m))
136                         return "This server does not allow for recent maps to be played again. Please be patient for some rounds.";
137
138         if (!autocvar_sv_vote_gametype)
139         if(!MapInfo_CheckMap(m))
140                 return "The map you suggested does not support the current game mode.";
141         for(i = 0; i < mapvote_suggestion_ptr; ++i)
142                 if(mapvote_suggestions[i] == m)
143                         return "This map was already suggested.";
144         if(mapvote_suggestion_ptr >= MAPVOTE_COUNT)
145         {
146                 i = floor(random() * mapvote_suggestion_ptr);
147         }
148         else
149         {
150                 i = mapvote_suggestion_ptr;
151                 mapvote_suggestion_ptr += 1;
152         }
153         if(mapvote_suggestions[i] != "")
154                 strunzone(mapvote_suggestions[i]);
155         mapvote_suggestions[i] = strzone(m);
156         if(autocvar_sv_eventlog)
157                 GameLogEcho(strcat(":vote:suggested:", m, ":", ftos(this.playerid)));
158         return strcat("Suggestion of ", m, " accepted.");
159 }
160
161 void MapVote_AddVotable(string nextMap, bool isSuggestion)
162 {
163         int j, i, o;
164         string pakfile, mapfile;
165
166         if(nextMap == "")
167                 return;
168         for(j = 0; j < mapvote_count; ++j)
169                 if(mapvote_maps[j] == nextMap)
170                         return;
171         // suggestions might be no longer valid/allowed after gametype switch!
172         if(isSuggestion)
173                 if(!MapInfo_CheckMap(nextMap))
174                         return;
175         mapvote_maps[mapvote_count] = strzone(nextMap);
176         mapvote_maps_suggested[mapvote_count] = isSuggestion;
177
178         pakfile = string_null;
179         for(i = 0; i < mapvote_screenshot_dirs_count; ++i)
180         {
181                 mapfile = strcat(mapvote_screenshot_dirs[i], "/", nextMap);
182                 pakfile = whichpack(strcat(mapfile, ".tga"));
183                 if(pakfile == "")
184                         pakfile = whichpack(strcat(mapfile, ".jpg"));
185                 if(pakfile == "")
186                         pakfile = whichpack(strcat(mapfile, ".png"));
187                 if(pakfile != "")
188                         break;
189         }
190         if(i >= mapvote_screenshot_dirs_count)
191                 i = 0; // FIXME maybe network this error case, as that means there is no mapshot on the server?
192         for(o = strstrofs(pakfile, "/", 0)+1; o > 0; o = strstrofs(pakfile, "/", 0)+1)
193                 pakfile = substring(pakfile, o, -1);
194
195         mapvote_maps_screenshot_dir[mapvote_count] = i;
196         mapvote_maps_pakfile[mapvote_count] = strzone(pakfile);
197         mapvote_maps_flags[mapvote_count] = GTV_AVAILABLE;
198
199         mapvote_count += 1;
200 }
201
202 void MapVote_Init()
203 {
204         int i;
205         int nmax, smax;
206
207         MapVote_ClearAllVotes();
208         MapVote_UnzoneStrings();
209
210         mapvote_count = 0;
211         mapvote_detail = !autocvar_g_maplist_votable_nodetail;
212         mapvote_abstain = boolean(autocvar_g_maplist_votable_abstain);
213
214         if(mapvote_abstain)
215                 nmax = min(MAPVOTE_COUNT - 1, autocvar_g_maplist_votable);
216         else
217                 nmax = min(MAPVOTE_COUNT, autocvar_g_maplist_votable);
218         smax = min3(nmax, autocvar_g_maplist_votable_suggestions, mapvote_suggestion_ptr);
219
220         // we need this for AddVotable, as that cycles through the screenshot dirs
221         mapvote_screenshot_dirs_count = tokenize_console(autocvar_g_maplist_votable_screenshot_dir);
222         if(mapvote_screenshot_dirs_count == 0)
223                 mapvote_screenshot_dirs_count = tokenize_console("maps levelshots");
224         mapvote_screenshot_dirs_count = min(mapvote_screenshot_dirs_count, MAPVOTE_SCREENSHOT_DIRS_COUNT);
225         for(i = 0; i < mapvote_screenshot_dirs_count; ++i)
226                 mapvote_screenshot_dirs[i] = strzone(argv(i));
227
228         if(mapvote_suggestion_ptr)
229                 for(i = 0; i < 100 && mapvote_count < smax; ++i)
230                         MapVote_AddVotable(mapvote_suggestions[floor(random() * mapvote_suggestion_ptr)], true);
231
232         for(i = 0; i < 100 && mapvote_count < nmax; ++i)
233                 MapVote_AddVotable(GetNextMap(), false);
234
235         if(mapvote_count == 0)
236         {
237                 bprint( "Maplist contains no single playable map!  Resetting it to default map list.\n" );
238                 cvar_set("g_maplist", MapInfo_ListAllowedMaps(MapInfo_CurrentGametype(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()));
239                 if(autocvar_g_maplist_shuffle)
240                         ShuffleMaplist();
241                 localcmd("\nmenu_cmd sync\n");
242                 for(i = 0; i < 100 && mapvote_count < nmax; ++i)
243                         MapVote_AddVotable(GetNextMap(), false);
244         }
245
246         mapvote_count_real = mapvote_count;
247         if(mapvote_abstain)
248                 MapVote_AddVotable("don't care", false);
249
250         //dprint("mapvote count is ", ftos(mapvote_count), "\n");
251
252         mapvote_keeptwotime = time + autocvar_g_maplist_votable_keeptwotime;
253         mapvote_timeout = time + autocvar_g_maplist_votable_timeout;
254         if(mapvote_count_real < 3 || mapvote_keeptwotime <= time)
255                 mapvote_keeptwotime = 0;
256
257         MapVote_Spawn();
258 }
259
260 void MapVote_SendPicture(entity to, int id)
261 {
262         msg_entity = to;
263         WriteHeader(MSG_ONE, TE_CSQC_PICTURE);
264         WriteByte(MSG_ONE, id);
265         WritePicture(MSG_ONE, strcat(mapvote_screenshot_dirs[mapvote_maps_screenshot_dir[id]], "/", mapvote_maps[id]), 3072);
266 }
267
268
269 void MapVote_WriteMask()
270 {
271         if ( mapvote_count < 24 )
272         {
273                 int mask = 0;
274                 for(int j = 0; j < mapvote_count; ++j)
275                 {
276                         if(mapvote_maps_flags[j] & GTV_AVAILABLE)
277                                 mask |= BIT(j);
278                 }
279
280                 if(mapvote_count < 8)
281                         WriteByte(MSG_ENTITY, mask);
282                 else if (mapvote_count < 16)
283                         WriteShort(MSG_ENTITY,mask);
284                 else
285                         WriteLong(MSG_ENTITY, mask);
286         }
287         else
288         {
289                 for (int j = 0; j < mapvote_count; ++j)
290                         WriteByte(MSG_ENTITY, mapvote_maps_flags[j]);
291         }
292 }
293
294 /*
295  * Sends a single map vote option to the client
296  */
297 void MapVote_SendOption(int i)
298 {
299         // abstain
300         if(mapvote_abstain && i == mapvote_count - 1)
301         {
302                 WriteString(MSG_ENTITY, ""); // abstain needs no text
303                 WriteString(MSG_ENTITY, ""); // abstain needs no pack
304                 WriteByte(MSG_ENTITY, 0); // abstain needs no screenshot dir
305         }
306         else
307         {
308                 WriteString(MSG_ENTITY, mapvote_maps[i]);
309                 WriteString(MSG_ENTITY, mapvote_maps_pakfile[i]);
310                 WriteByte(MSG_ENTITY, mapvote_maps_screenshot_dir[i]);
311         }
312 }
313
314 /*
315  * Sends a single gametype vote option to the client
316  */
317 void GameTypeVote_SendOption(int i)
318 {
319         // abstain
320         if(mapvote_abstain && i == mapvote_count - 1)
321         {
322                 WriteString(MSG_ENTITY, ""); // abstain needs no text
323                 WriteByte(MSG_ENTITY, GTV_AVAILABLE);
324         }
325         else
326         {
327                 string type_name = mapvote_maps[i];
328                 WriteString(MSG_ENTITY, type_name);
329                 WriteByte(MSG_ENTITY, mapvote_maps_flags[i]);
330                 if ( mapvote_maps_flags[i] & GTV_CUSTOM )
331                 {
332                         WriteString(MSG_ENTITY, cvar_string(
333                                 strcat("sv_vote_gametype_",type_name,"_name")));
334                         WriteString(MSG_ENTITY, cvar_string(
335                                 strcat("sv_vote_gametype_",type_name,"_description")));
336                         WriteString(MSG_ENTITY, cvar_string(
337                                 strcat("sv_vote_gametype_",type_name,"_type")));
338                 }
339         }
340 }
341
342 bool MapVote_SendEntity(entity this, entity to, int sf)
343 {
344         int i;
345
346         if(sf & 1)
347                 sf &= ~2; // if we send 1, we don't need to also send 2
348
349         WriteHeader(MSG_ENTITY, ENT_CLIENT_MAPVOTE);
350         WriteByte(MSG_ENTITY, sf);
351
352         if(sf & 1)
353         {
354                 // flag 1 == initialization
355                 for(i = 0; i < mapvote_screenshot_dirs_count; ++i)
356                         WriteString(MSG_ENTITY, mapvote_screenshot_dirs[i]);
357                 WriteString(MSG_ENTITY, "");
358                 WriteByte(MSG_ENTITY, mapvote_count);
359                 WriteByte(MSG_ENTITY, mapvote_abstain);
360                 WriteByte(MSG_ENTITY, mapvote_detail);
361                 WriteCoord(MSG_ENTITY, mapvote_timeout);
362
363                 if ( gametypevote )
364                 {
365                         // gametype vote
366                         WriteByte(MSG_ENTITY, 1);
367                         WriteString(MSG_ENTITY, autocvar_nextmap);
368                 }
369                 else if ( autocvar_sv_vote_gametype )
370                 {
371                         // map vote but gametype has been chosen via voting screen
372                         WriteByte(MSG_ENTITY, 2);
373                         WriteString(MSG_ENTITY, MapInfo_Type_ToText(MapInfo_CurrentGametype()));
374                 }
375                 else
376                         WriteByte(MSG_ENTITY, 0); // map vote
377
378                 MapVote_WriteMask();
379
380                 // Send data for the vote options
381                 for(i = 0; i < mapvote_count; ++i)
382                 {
383                         if(gametypevote)
384                                 GameTypeVote_SendOption(i);
385                         else
386                                 MapVote_SendOption(i);
387                 }
388         }
389
390         if(sf & 2)
391         {
392                 // flag 2 == update of mask
393                 MapVote_WriteMask();
394         }
395
396         if(sf & 4)
397         {
398                 if(mapvote_detail)
399                         for(i = 0; i < mapvote_count; ++i)
400                                 if ( mapvote_maps_flags[i] & GTV_AVAILABLE )
401                                         WriteByte(MSG_ENTITY, mapvote_selections[i]);
402
403                 WriteByte(MSG_ENTITY, to.mapvote);
404         }
405
406         return true;
407 }
408
409 void MapVote_Spawn()
410 {
411         Net_LinkEntity(mapvote_ent = spawn(), false, 0, MapVote_SendEntity);
412 }
413
414 void MapVote_TouchMask()
415 {
416         mapvote_ent.SendFlags |= 2;
417 }
418
419 void MapVote_TouchVotes(entity voter)
420 {
421         mapvote_ent.SendFlags |= 4;
422 }
423
424 bool MapVote_Finished(int mappos)
425 {
426         if(alreadychangedlevel)
427                 return false;
428
429         string result;
430         int i;
431         int didntvote;
432
433         if(autocvar_sv_eventlog)
434         {
435                 result = strcat(":vote:finished:", mapvote_maps[mappos]);
436                 result = strcat(result, ":", ftos(mapvote_selections[mappos]), "::");
437                 didntvote = mapvote_voters;
438                 for(i = 0; i < mapvote_count; ++i)
439                         if(mapvote_maps_flags[i] & GTV_AVAILABLE )
440                         {
441                                 didntvote -= mapvote_selections[i];
442                                 if(i != mappos)
443                                 {
444                                         result = strcat(result, ":", mapvote_maps[i]);
445                                         result = strcat(result, ":", ftos(mapvote_selections[i]));
446                                 }
447                         }
448                 result = strcat(result, ":didn't vote:", ftos(didntvote));
449
450                 GameLogEcho(result);
451                 if(mapvote_maps_suggested[mappos])
452                         GameLogEcho(strcat(":vote:suggestion_accepted:", mapvote_maps[mappos]));
453         }
454
455         FOREACH_CLIENT(IS_REAL_CLIENT(it), { FixClientCvars(it); });
456
457         if(gametypevote)
458         {
459                 if ( GameTypeVote_Finished(mappos) )
460                 {
461                         gametypevote = false;
462                         if(autocvar_nextmap != "")
463                         {
464                                 Map_Goto_SetStr(autocvar_nextmap);
465                                 Map_Goto(0);
466                                 alreadychangedlevel = true;
467                                 return true;
468                         }
469                         else
470                                 MapVote_Init();
471                 }
472                 return false;
473         }
474
475         Map_Goto_SetStr(mapvote_maps[mappos]);
476         Map_Goto(0);
477         alreadychangedlevel = true;
478
479         return true;
480 }
481
482 void MapVote_CheckRules_1()
483 {
484         for (int i = 0; i < mapvote_count; ++i)
485                 if (mapvote_maps_flags[i] & GTV_AVAILABLE)
486                 {
487                         //dprint("Map ", ftos(i), ": "); dprint(mapvote_maps[i], "\n");
488                         mapvote_selections[i] = 0;
489                 }
490
491         mapvote_voters = 0;
492         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
493                 ++mapvote_voters;
494                 if (it.mapvote)
495                 {
496                         int idx = it.mapvote - 1;
497                         //dprint("Player ", it.netname, " vote = ", ftos(idx), "\n");
498                         ++mapvote_selections[idx];
499                 }
500         });
501 }
502
503 bool MapVote_CheckRules_2()
504 {
505         int i;
506         int firstPlace, secondPlace, currentPlace;
507         int firstPlaceVotes, secondPlaceVotes, currentVotes;
508         int mapvote_voters_real;
509         string result;
510
511         if(mapvote_count_real == 1)
512                 return MapVote_Finished(0);
513
514         mapvote_voters_real = mapvote_voters;
515         if(mapvote_abstain)
516                 mapvote_voters_real -= mapvote_selections[mapvote_count - 1];
517
518         RandomSelection_Init();
519         currentPlace = 0;
520         currentVotes = -1;
521         for(i = 0; i < mapvote_count_real; ++i)
522                 if ( mapvote_maps_flags[i] & GTV_AVAILABLE )
523                 {
524                         RandomSelection_AddFloat(i, 1, mapvote_selections[i]);
525                         if ( gametypevote &&  mapvote_maps[i] == MapInfo_Type_ToString(MapInfo_CurrentGametype()) )
526                         {
527                                 currentVotes = mapvote_selections[i];
528                                 currentPlace = i;
529                         }
530                 }
531         firstPlaceVotes = RandomSelection_best_priority;
532         if ( autocvar_sv_vote_gametype_default_current && firstPlaceVotes == 0 )
533                 firstPlace = currentPlace;
534         else
535                 firstPlace = RandomSelection_chosen_float;
536
537         //dprint("First place: ", ftos(firstPlace), "\n");
538         //dprint("First place votes: ", ftos(firstPlaceVotes), "\n");
539
540         RandomSelection_Init();
541         for(i = 0; i < mapvote_count_real; ++i)
542                 if(i != firstPlace)
543                 if ( mapvote_maps_flags[i] & GTV_AVAILABLE )
544                         RandomSelection_AddFloat(i, 1, mapvote_selections[i]);
545         secondPlace = RandomSelection_chosen_float;
546         secondPlaceVotes = RandomSelection_best_priority;
547         //dprint("Second place: ", ftos(secondPlace), "\n");
548         //dprint("Second place votes: ", ftos(secondPlaceVotes), "\n");
549
550         if(firstPlace == -1)
551                 error("No first place in map vote... WTF?");
552
553         if(secondPlace == -1 || time > mapvote_timeout || (mapvote_voters_real - firstPlaceVotes) < firstPlaceVotes)
554                 return MapVote_Finished(firstPlace);
555
556         if(mapvote_keeptwotime)
557                 if(time > mapvote_keeptwotime || (mapvote_voters_real - firstPlaceVotes - secondPlaceVotes) < secondPlaceVotes)
558                 {
559                         MapVote_TouchMask();
560                         mapvote_keeptwotime = 0;
561                         result = strcat(":vote:keeptwo:", mapvote_maps[firstPlace]);
562                         result = strcat(result, ":", ftos(firstPlaceVotes));
563                         result = strcat(result, ":", mapvote_maps[secondPlace]);
564                         result = strcat(result, ":", ftos(secondPlaceVotes), "::");
565                         int didntvote = mapvote_voters;
566                         for(i = 0; i < mapvote_count; ++i)
567                         {
568                                 didntvote -= mapvote_selections[i];
569                                 if(i != firstPlace)
570                                         if(i != secondPlace)
571                                         {
572                                                 result = strcat(result, ":", mapvote_maps[i]);
573                                                 result = strcat(result, ":", ftos(mapvote_selections[i]));
574                                                 if(i < mapvote_count_real)
575                                                 {
576                                                         mapvote_maps_flags[i] &= ~GTV_AVAILABLE;
577                                                 }
578                                         }
579                         }
580                         result = strcat(result, ":didn't vote:", ftos(didntvote));
581                         if(autocvar_sv_eventlog)
582                                 GameLogEcho(result);
583                 }
584
585         return false;
586 }
587
588 void MapVote_Tick()
589 {
590
591         MapVote_CheckRules_1(); // count
592         if(MapVote_CheckRules_2()) // decide
593                 return;
594
595         int totalvotes = 0;
596         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
597                 // hide scoreboard again
598                 if(GetResource(it, RES_HEALTH) != 2342)
599                 {
600                         SetResourceExplicit(it, RES_HEALTH, 2342);
601                         CS(it).impulse = 0;
602
603                         msg_entity = it;
604                         WriteByte(MSG_ONE, SVC_FINALE);
605                         WriteString(MSG_ONE, "");
606                 }
607
608                 // clear possibly invalid votes
609                 if ( !(mapvote_maps_flags[it.mapvote-1] & GTV_AVAILABLE) )
610                         it.mapvote = 0;
611                 // use impulses as new vote
612                 if(CS(it).impulse >= 1 && CS(it).impulse <= mapvote_count)
613                         if( mapvote_maps_flags[CS(it).impulse - 1] & GTV_AVAILABLE )
614                         {
615                                 it.mapvote = CS(it).impulse;
616                                 MapVote_TouchVotes(it);
617                         }
618                 CS(it).impulse = 0;
619
620                 if(it.mapvote)
621                         ++totalvotes;
622         });
623
624         MapVote_CheckRules_1(); // just count
625 }
626
627 void MapVote_Start()
628 {
629         // if mapvote is already running, don't do this initialization again
630         if(mapvote_run) { return; }
631
632         // don't start mapvote until after playerstats gamereport is sent
633         if(PlayerStats_GameReport_DelayMapVote) { return; }
634
635         MapInfo_Enumerate();
636         if(MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1))
637                 mapvote_run = true;
638 }
639
640 void MapVote_Think()
641 {
642         if(!mapvote_run)
643                 return;
644
645         if(alreadychangedlevel)
646                 return;
647
648         if(time < mapvote_nextthink)
649                 return;
650         //dprint("tick\n");
651
652         mapvote_nextthink = time + 0.5;
653
654         if(!mapvote_initialized)
655         {
656                 if(autocvar_rescan_pending == 1)
657                 {
658                         cvar_set("rescan_pending", "2");
659                         localcmd("fs_rescan\nrescan_pending 3\n");
660                         return;
661                 }
662                 else if(autocvar_rescan_pending == 2)
663                 {
664                         return;
665                 }
666                 else if(autocvar_rescan_pending == 3)
667                 {
668                         // now build missing mapinfo files
669                         if(!MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1))
670                                 return;
671
672                         // we're done, start the timer
673                         cvar_set("rescan_pending", "0");
674                 }
675
676                 mapvote_initialized = true;
677                 if(DoNextMapOverride(0))
678                         return;
679                 if(!autocvar_g_maplist_votable || player_count <= 0)
680                 {
681                         GotoNextMap(0);
682                         return;
683                 }
684
685                 if(autocvar_sv_vote_gametype) { GameTypeVote_Start(); }
686                 else if(autocvar_nextmap == "") { MapVote_Init(); }
687         }
688
689         MapVote_Tick();
690 }
691
692 bool GameTypeVote_SetGametype(Gametype type)
693 {
694         if (MapInfo_CurrentGametype() == type)
695                 return true;
696
697         Gametype tsave = MapInfo_CurrentGametype();
698
699         MapInfo_SwitchGameType(type);
700
701         MapInfo_Enumerate();
702         MapInfo_FilterGametype(type, MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
703         if(MapInfo_count > 0)
704         {
705                 // update lsmaps in case the gametype changed, this way people can easily list maps for it
706                 if(lsmaps_reply != "") { strunzone(lsmaps_reply); }
707                 lsmaps_reply = strzone(getlsmaps());
708                 bprint("Game type successfully switched to ", MapInfo_Type_ToString(type), "\n");
709         }
710         else
711         {
712                 bprint("Cannot use this game type: no map for it found\n");
713                 MapInfo_SwitchGameType(tsave);
714                 MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
715                 return false;
716         }
717
718         //localcmd("gametype ", MapInfo_Type_ToString(type), "\n");
719
720         cvar_set("g_maplist", MapInfo_ListAllowedMaps(type, MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags()) );
721         if(autocvar_g_maplist_shuffle)
722                 ShuffleMaplist();
723
724         return true;
725 }
726
727 bool gametypevote_finished;
728 bool GameTypeVote_Finished(int pos)
729 {
730         if(!gametypevote || gametypevote_finished)
731                 return false;
732
733         localcmd("sv_vote_gametype_hook_all\n");
734         localcmd("sv_vote_gametype_hook_", mapvote_maps[pos], "\n");
735
736         if ( !GameTypeVote_SetGametype(GameTypeVote_Type_FromString(mapvote_maps[pos])) )
737         {
738                 LOG_TRACE("Selected gametype is not supported by any map");
739         }
740
741         gametypevote_finished = true;
742
743         return true;
744 }
745
746 bool GameTypeVote_AddVotable(string nextMode)
747 {
748         if ( nextMode == "" || GameTypeVote_Type_FromString(nextMode) == NULL )
749                 return false;
750
751         for(int j = 0; j < mapvote_count; ++j)
752                 if(mapvote_maps[j] == nextMode)
753                         return false;
754
755         mapvote_maps[mapvote_count] = strzone(nextMode);
756         mapvote_maps_suggested[mapvote_count] = false;
757
758         mapvote_maps_screenshot_dir[mapvote_count] = 0;
759         mapvote_maps_pakfile[mapvote_count] = strzone("");
760         mapvote_maps_flags[mapvote_count] = GameTypeVote_AvailabilityStatus(nextMode);
761
762         mapvote_count += 1;
763
764         return true;
765
766 }
767
768 bool GameTypeVote_Start()
769 {
770         MapVote_ClearAllVotes();
771         MapVote_UnzoneStrings();
772
773         mapvote_count = 0;
774         mapvote_timeout = time + autocvar_sv_vote_gametype_timeout;
775         mapvote_abstain = false;
776         mapvote_detail = !autocvar_g_maplist_votable_nodetail;
777
778         int n = tokenizebyseparator(autocvar_sv_vote_gametype_options, " ");
779         n = min(MAPVOTE_COUNT, n);
780
781         int really_available, which_available;
782         really_available = 0;
783         which_available = -1;
784         for(int j = 0; j < n; ++j)
785         {
786                 if ( GameTypeVote_AddVotable(argv(j)) )
787                 if ( mapvote_maps_flags[j] & GTV_AVAILABLE )
788                 {
789                         really_available++;
790                         which_available = j;
791                 }
792         }
793
794         mapvote_count_real = mapvote_count;
795
796         gametypevote = 1;
797
798         if ( really_available == 0 )
799         {
800                 if ( mapvote_count > 0 )
801                         strunzone(mapvote_maps[0]);
802                 mapvote_maps[0] = strzone(MapInfo_Type_ToString(MapInfo_CurrentGametype()));
803                 //GameTypeVote_Finished(0);
804                 MapVote_Finished(0);
805                 return false;
806         }
807         if ( really_available == 1 )
808         {
809                 //GameTypeVote_Finished(which_available);
810                 MapVote_Finished(which_available);
811                 return false;
812         }
813
814         mapvote_count_real = mapvote_count;
815
816         mapvote_keeptwotime = time + autocvar_sv_vote_gametype_keeptwotime;
817         if(mapvote_count_real < 3 || mapvote_keeptwotime <= time)
818                 mapvote_keeptwotime = 0;
819
820         MapVote_Spawn();
821
822         return true;
823 }