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