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