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