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