]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/mapvoting.qc
Merge branch 'bones_was_here/csprogs' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / mapvoting.qc
1 #include "mapvoting.qh"
2
3 #include <client/autocvars.qh>
4 #include <client/draw.qh>
5 #include <client/hud/_mod.qh>
6 #include <client/hud/panel/scoreboard.qh>
7 #include <common/mapinfo.qh>
8 #include <common/util.qh>
9
10 // MapVote (#21)
11
12 void MapVote_Draw_Export(int fh)
13 {
14         // allow saving cvars that aesthetically change the panel into hud skin files
15         HUD_Write_Cvar("hud_panel_mapvote_highlight_border");
16 }
17
18 int mv_num_maps;
19
20 string mv_maps[MAPVOTE_COUNT];
21 string mv_pics[MAPVOTE_COUNT];
22 string mv_pk3[MAPVOTE_COUNT]; // map pk3 name or gametype human readable name
23 string mv_desc[MAPVOTE_COUNT];
24 float mv_preview[MAPVOTE_COUNT];
25 float mv_votes[MAPVOTE_COUNT];
26 float mv_flags[MAPVOTE_COUNT];
27 float mv_flags_start[MAPVOTE_COUNT];
28 entity mv_pk3list;
29 float mv_abstain;
30 float mv_ownvote;
31 float mv_detail;
32 float mv_timeout;
33 float mv_top2_time;
34 float mv_top2_alpha;
35
36 int mv_selection;
37 int mv_columns;
38 int mv_mouse_selection;
39 int mv_selection_keyboard;
40
41 float gametypevote;
42 string mapvote_chosenmap;
43 vector gtv_text_size;
44 vector gtv_text_size_small;
45
46 const int NUM_SSDIRS = 4;
47 string ssdirs[NUM_SSDIRS];
48 int n_ssdirs;
49
50 bool PreviewExists(string name)
51 {
52         if(autocvar_cl_readpicture_force)
53                 return false;
54
55         if (fexists(strcat(name, ".tga"))) return true;
56         if (fexists(strcat(name, ".png"))) return true;
57         if (fexists(strcat(name, ".jpg"))) return true;
58         if (fexists(strcat(name, ".pcx"))) return true;
59
60         return false;
61 }
62
63 string MapVote_FormatMapItem(int id, string map, float _count, float maxwidth, vector fontsize)
64 {
65         TC(int, id);
66         string pre, post;
67         pre = sprintf("%d. ", id+1);
68         if(mv_detail)
69         {
70                 if(_count == 1)
71                         post = _(" (1 vote)");
72                 else if(_count >= 0 && (mv_flags[id] & GTV_AVAILABLE))
73                         post = sprintf(_(" (%d votes)"), _count);
74                 else
75                         post = "";
76         }
77         else
78                 post = "";
79         maxwidth -= stringwidth(pre, false, fontsize) + stringwidth(post, false, fontsize);
80         map = textShortenToWidth(map, maxwidth, fontsize, stringwidth_nocolors);
81         return strcat(pre, map, post);
82 }
83
84 vector MapVote_RGB(int id)
85 {
86         TC(int, id);
87         if(!(mv_flags[id] & GTV_AVAILABLE))
88                 return '1 1 1';
89         if(id == mv_ownvote)
90                 return '0 1 0';
91         else if (id == mv_selection)
92                 return '1 1 0';
93         else
94                 return '1 1 1';
95 }
96
97 void GameTypeVote_DrawGameTypeItem(vector pos, float maxh, float tsize, string gtype, string pic, float _count, int id)
98 {
99         TC(int, id);
100         // Find the correct alpha
101         float alpha;
102         if(!(mv_flags_start[id] & GTV_AVAILABLE))
103                 alpha = 0.2; // The gametype isn't supported by the map
104         else if ( !(mv_flags[id] & GTV_AVAILABLE) && mv_top2_alpha)
105                 alpha = mv_top2_alpha; // Fade away if not one of the top 2 choice
106         else
107                 alpha = 1; // Normal, full alpha
108         alpha *= panel_fg_alpha;
109
110         // Bounding box details
111         float rect_margin = hud_fontsize.y / 2;
112
113         pos.x += rect_margin + autocvar_hud_panel_mapvote_highlight_border;
114         pos.y += rect_margin + autocvar_hud_panel_mapvote_highlight_border;
115         maxh -= 2 * (rect_margin + autocvar_hud_panel_mapvote_highlight_border);
116         tsize -= 2 * (rect_margin + autocvar_hud_panel_mapvote_highlight_border);
117
118         vector rect_pos = pos - '0.5 0.5 0' * rect_margin;
119         vector rect_size = '1 1 0';
120         rect_size.x = tsize + rect_margin;
121         rect_size.y = maxh + rect_margin;
122
123         // Highlight selected item
124         if(id == mv_selection && (mv_flags[id] & GTV_AVAILABLE))
125         {
126                 drawfill(rect_pos, rect_size, '1 1 1', 0.1 * panel_fg_alpha, DRAWFLAG_NORMAL);
127         }
128
129         // Highlight current vote
130         vector rgb = MapVote_RGB(id);
131         if(id == mv_ownvote)
132         {
133                 drawfill(rect_pos, rect_size, rgb, 0.1 * alpha, DRAWFLAG_NORMAL);
134                 drawborderlines(autocvar_hud_panel_mapvote_highlight_border, rect_pos, rect_size, rgb, alpha, DRAWFLAG_NORMAL);
135         }
136
137         vector offset = pos;
138
139         float title_gap = gtv_text_size.y * 1.4; // distance between the title and the description
140         pos.y += title_gap;
141         maxh -= title_gap;
142
143         // Evaluate the image size
144         vector image_size = '1 1 0' * gtv_text_size.x * 3;
145         if ( maxh < image_size.y )
146                 image_size = '1 1 0' * maxh;
147         image_size *= 0.8;
148         float desc_padding = gtv_text_size.x * 0.6;
149         pos.x += image_size.x + desc_padding;
150         tsize -= image_size.x + desc_padding;
151
152         // Split the description into lines
153         entity title;
154         title = spawn();
155         title.message = MapVote_FormatMapItem(id, mv_pk3[id], _count, tsize, gtv_text_size);
156
157         string thelabel = mv_desc[id], ts;
158         entity last = title;
159         entity next = NULL;
160         float nlines = 0;
161         if( thelabel != "")
162         {
163                 float i,n = tokenizebyseparator(thelabel, "\n");
164                 for(i = 0; i < n && maxh > (nlines+1)*gtv_text_size_small.y; ++i)
165                 {
166                         getWrappedLine_remaining = argv(i);
167                         while(getWrappedLine_remaining && maxh > (nlines+1)*gtv_text_size_small.y)
168                         {
169                                 ts = getWrappedLine(tsize, gtv_text_size_small, stringwidth_colors);
170                                 if (ts != "")
171                                 {
172                                         next = spawn();
173                                         next.message = ts;
174                                         next.origin = pos-offset;
175                                         last.chain = next;
176                                         last = next;
177                                         pos.y += gtv_text_size_small.y;
178                                         nlines++;
179                                 }
180                         }
181                 }
182         }
183
184         // Center the contents in the bounding box
185         maxh -= max(nlines*gtv_text_size_small.y,image_size.y);
186         if ( maxh > 0 )
187                 offset.y += maxh/2;
188
189         // Draw the title
190         drawstring(offset, title.message, gtv_text_size, rgb, alpha, DRAWFLAG_NORMAL);
191
192         // Draw the icon
193         if(pic != "")
194                 drawpic('0 1 0'*title_gap+'0.5 0 0'*desc_padding+offset, pic, image_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
195
196         // Draw the description
197         for ( last = title.chain; last ; )
198         {
199                 drawstring(last.origin+offset, last.message, gtv_text_size_small, '1 1 1', alpha, DRAWFLAG_NORMAL);
200                 next = last;
201                 last = last.chain;
202                 delete(next);
203         }
204
205         // Cleanup
206         delete(title);
207 }
208
209 void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, string pic, float _count, int id)
210 {
211         TC(int, id);
212         vector img_size = '0 0 0';
213         string label;
214         float text_size;
215
216         float rect_margin = hud_fontsize.y / 2;
217
218         pos.x += rect_margin + autocvar_hud_panel_mapvote_highlight_border;
219         pos.y += rect_margin + autocvar_hud_panel_mapvote_highlight_border;
220         isize -= 2 * (rect_margin + autocvar_hud_panel_mapvote_highlight_border);
221         tsize -= 2 * (rect_margin + autocvar_hud_panel_mapvote_highlight_border);
222
223         vector rect_pos = pos - '0.5 0.5 0' * rect_margin;
224         vector rect_size = '1 1 0';
225         rect_size.x = tsize + rect_margin;
226         rect_size.y = isize + rect_margin;
227
228         float img_ar = 4/3;
229         img_size.x = min(tsize, isize * img_ar);
230         img_size.y = img_size.x / img_ar;
231         img_size.y -= hud_fontsize.y;
232         img_size.x = img_size.y * img_ar;
233
234         pos.y += (isize - img_size.y - hud_fontsize.y) / 2;
235
236         label = MapVote_FormatMapItem(id, map, _count, tsize, hud_fontsize);
237
238         text_size = stringwidth(label, false, hud_fontsize);
239
240         float save_rect_sizex = rect_size.x;
241         rect_size.x = max(img_size.x, text_size) + rect_margin;
242         rect_pos.x += (save_rect_sizex - rect_size.x) / 2;
243
244         vector text_pos = '0 0 0';
245         text_pos.x = pos.x + (tsize - text_size) / 2;
246         text_pos.y = pos.y + img_size.y;
247
248         pos.x += (tsize - img_size.x) / 2;
249
250         float theAlpha;
251         if (!(mv_flags[id] & GTV_AVAILABLE) && mv_top2_alpha)
252                 theAlpha = mv_top2_alpha;
253         else
254                 theAlpha = 1;
255         theAlpha *= panel_fg_alpha;
256
257         // Highlight selected item
258         if(id == mv_selection && (mv_flags[id] & GTV_AVAILABLE))
259                 drawfill(rect_pos, rect_size, '1 1 1', 0.1 * panel_fg_alpha, DRAWFLAG_NORMAL);
260
261         // Highlight current vote
262         vector rgb = MapVote_RGB(id);
263         if(id == mv_ownvote)
264         {
265                 drawfill(rect_pos, rect_size, rgb, 0.1 * theAlpha, DRAWFLAG_NORMAL);
266                 drawborderlines(autocvar_hud_panel_mapvote_highlight_border, rect_pos, rect_size, rgb, theAlpha, DRAWFLAG_NORMAL);
267         }
268
269         drawstring(text_pos, label, hud_fontsize, rgb, theAlpha, DRAWFLAG_NORMAL);
270
271         if(pic == "")
272         {
273                 drawfill(pos, img_size, '.5 .5 .5', .7 * theAlpha, DRAWFLAG_NORMAL);
274         }
275         else
276         {
277                 if(drawgetimagesize(pic) == '0 0 0')
278                         drawpic(pos, draw_UseSkinFor("nopreview_map"), img_size, '1 1 1', theAlpha, DRAWFLAG_NORMAL);
279                 else
280                         drawpic(pos, pic, img_size, '1 1 1', theAlpha, DRAWFLAG_NORMAL);
281         }
282 }
283
284 void MapVote_DrawAbstain(vector pos, float isize, float tsize, float _count, int id)
285 {
286         TC(int, id);
287         vector rgb;
288         float text_size;
289         string label;
290
291         rgb = MapVote_RGB(id);
292
293         label = MapVote_FormatMapItem(id, _("Don't care"), _count, tsize, hud_fontsize);
294
295         text_size = stringwidth(label, false, hud_fontsize);
296
297         pos.x -= text_size*0.5;
298         drawstring(pos, label, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
299 }
300
301 vector MapVote_GridVec(vector gridspec, int i, int m)
302 {
303         TC(int, i); TC(int, m);
304         int r = i % m;
305         return
306                 '1 0 0' * (gridspec.x * r)
307                 +
308                 '0 1 0' * (gridspec.y * (i - r) / m);
309 }
310
311 float MapVote_Selection(vector topleft, vector cellsize, float rows, float columns)
312 {
313
314         float c, r;
315
316         mv_mouse_selection = -1;
317
318         for (r = 0; r < rows; ++r)
319                 for (c = 0; c < columns; ++c)
320                 {
321                         if (mousepos.x >= topleft.x + cellsize.x *  c &&
322                                 mousepos.x <= topleft.x + cellsize.x * (c + 1) &&
323                                 mousepos.y >= topleft.y + cellsize.y *  r &&
324                                 mousepos.y <= topleft.y + cellsize.y * (r + 1))
325                         {
326                                 mv_mouse_selection = r * columns + c;
327                                 break;
328                         }
329                 }
330
331         if (mv_mouse_selection >= mv_num_maps)
332                 mv_mouse_selection = -1;
333
334         if (mv_abstain && mv_mouse_selection < 0)
335                 mv_mouse_selection = mv_num_maps;
336
337         if ( mv_selection_keyboard )
338                 return mv_selection;
339
340         return mv_mouse_selection;
341 }
342
343 vector prev_mousepos;
344 // draws map vote or gametype vote
345 void MapVote_Draw()
346 {
347         string map;
348         int i;
349         float tmp;
350         vector pos;
351         float center;
352         float rows;
353         vector dist = '0 0 0';
354
355         //if(intermission != 2) return;
356         if(!mv_active)
357                 return;
358
359         HUD_Panel_LoadCvars();
360
361         if (!autocvar_hud_cursormode)
362         {
363                 if (mousepos.x != prev_mousepos.x || mousepos.y != prev_mousepos.y)
364                 {
365                         mv_selection_keyboard = 0;
366                         prev_mousepos = mousepos;
367                 }
368         }
369
370         center = (vid_conwidth - 1)/2;
371         xmin = vid_conwidth * 0.08;
372         xmax = vid_conwidth - xmin;
373         ymin = 20;
374         ymax = vid_conheight - ymin;
375
376         if(chat_posy + chat_sizey / 2 < vid_conheight / 2)
377                 ymin += chat_sizey;
378         else
379                 ymax -= chat_sizey;
380
381         hud_fontsize = HUD_GetFontsize("hud_fontsize");
382         if (gametypevote)
383         {
384                 gtv_text_size = hud_fontsize * 1.4;
385                 gtv_text_size_small = hud_fontsize * 1.1;
386         }
387
388         pos.y = ymin;
389         pos.z = 0;
390
391         HUD_Scale_Disable();
392         draw_beginBoldFont();
393
394         map = ((gametypevote) ? _("Decide the gametype") : _("Vote for a map"));
395         pos.x = center - stringwidth(map, false, hud_fontsize * 2) * 0.5;
396         drawstring(pos, map, hud_fontsize * 2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
397         pos.y += hud_fontsize.y * 2;
398
399         if( mapvote_chosenmap != "" )
400         {
401                 pos.y += hud_fontsize.y * 0.25;
402                 pos.x = center - stringwidth(mapvote_chosenmap, false, hud_fontsize * 1.5) * 0.5;
403                 drawstring(pos, mapvote_chosenmap, hud_fontsize * 1.5, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
404                 pos.y += hud_fontsize.y * 1.5;
405         }
406         pos.y += hud_fontsize.y * 0.5;
407
408         draw_endBoldFont();
409
410         i = ceil(max(0, mv_timeout - time));
411         map = sprintf(_("%d seconds left"), i);
412         pos.x = center - stringwidth(map, false, hud_fontsize * 1.5) * 0.5;
413         drawstring(pos, map, hud_fontsize * 1.5, '0 1 0', panel_fg_alpha, DRAWFLAG_NORMAL);
414         pos.y += hud_fontsize.y * 1.5;
415         pos.y += hud_fontsize.y * 0.5;
416
417         // base for multi-column stuff...
418         pos.y += hud_fontsize.y;
419         pos.x = xmin;
420         ymin = pos.y;
421         float abstain_spacing = panel_bg_border + hud_fontsize.y;
422         if(mv_abstain)
423         {
424                 mv_num_maps -= 1;
425                 ymax -= abstain_spacing;
426         }
427
428         // higher than the image itself ratio for mapvote items to reserve space for long map names
429         int item_aspect = (gametypevote) ? 3/1 : 5/3;
430         vector table_size = HUD_GetTableSize_BestItemAR(mv_num_maps, vec2(xmax - xmin, ymax - ymin), item_aspect);
431         mv_columns = table_size.x;
432         rows = table_size.y;
433
434         dist.x = (xmax - xmin) / mv_columns;
435         dist.y = (ymax - pos.y) / rows;
436
437         // reduce size of too wide items
438         tmp = vid_conwidth / 3; // max width
439         if(dist.x > tmp)
440         {
441                 dist.x = tmp;
442                 dist.y = min(dist.y, dist.x / item_aspect);
443         }
444         tmp = vid_conheight / 3; // max height
445         if(dist.y > tmp)
446         {
447                 dist.y = tmp;
448                 dist.x = min(dist.x, dist.y * item_aspect);
449         }
450
451         // reduce size to fix aspect ratio
452         if(dist.x / dist.y > item_aspect)
453                 dist.x = dist.y * item_aspect;
454         else
455                 dist.y = dist.x / item_aspect;
456
457         // adjust table pos and size according to the new size
458         float offset;
459         offset = ((xmax - pos.x) - dist.x * mv_columns) / 2;
460         xmin = pos.x += offset;
461         xmax -= offset;
462         offset = ((ymax - pos.y) - dist.y * rows) / 2;
463         ymax -= 2 * offset;
464
465         // override panel_pos and panel_size
466         panel_pos.x = pos.x;
467         panel_pos.y = pos.y;
468         panel_size.x = xmax - xmin;
469         panel_size.y = ymax - ymin;
470         HUD_Panel_DrawBg();
471
472         if(panel_bg_padding)
473         {
474                 // FIXME item AR gets slightly changed here...
475                 // it's rather hard to avoid it at this point
476                 dist.x -= 2 * panel_bg_padding / mv_columns;
477                 dist.y -= 2 * panel_bg_padding / rows;
478                 xmin = pos.x += panel_bg_padding;
479                 ymin = pos.y += panel_bg_padding;
480                 xmax -= 2 * panel_bg_padding;
481                 ymax -= 2 * panel_bg_padding;
482         }
483
484         mv_selection = MapVote_Selection(pos, dist, rows, mv_columns);
485
486         if (mv_top2_time)
487                 mv_top2_alpha = max(0.2, 1 - (time - mv_top2_time) ** 2);
488
489         void (vector, float, float, string, string, float, float) DrawItem;
490
491         if(gametypevote)
492                 DrawItem = GameTypeVote_DrawGameTypeItem;
493         else
494                 DrawItem = MapVote_DrawMapItem;
495
496         for(i = 0; i < mv_num_maps; ++i)
497         {
498                 tmp = mv_votes[i]; // FTEQCC bug: too many array accesses in the function call screw it up
499                 map = mv_maps[i];
500                 if(mv_preview[i])
501                         DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), dist.y, dist.x, map, mv_pics[i], tmp, i);
502                 else
503                         DrawItem(pos + MapVote_GridVec(dist, i, mv_columns), dist.y, dist.x, map, "", tmp, i);
504         }
505
506         if(mv_abstain)
507                 ++mv_num_maps;
508
509         if(mv_abstain && i < mv_num_maps) {
510                 tmp = mv_votes[i];
511                 pos.y = ymax + abstain_spacing;
512                 pos.x = (xmax+xmin)*0.5;
513                 MapVote_DrawAbstain(pos, dist.x, xmax - xmin, tmp, i);
514         }
515 }
516
517 void Cmd_MapVote_MapDownload(int argc)
518 {
519         TC(int, argc);
520         entity pak;
521
522         if(argc != 2 || !mv_pk3list)
523         {
524                 LOG_INFO(_("mv_mapdownload: ^3You're not supposed to use this command on your own!"));
525                 return;
526         }
527
528         int id = stof(argv(1));
529         for(pak = mv_pk3list; pak; pak = pak.chain)
530                 if(pak.sv_entnum == id)
531                         break;
532
533         if(!pak || pak.sv_entnum != id) {
534                 LOG_INFO(_("^1Error:^7 Couldn't find pak index."));
535                 return;
536         }
537
538         if(PreviewExists(pak.message))
539         {
540                 mv_preview[id] = true;
541                 return;
542         } else {
543                 LOG_INFO(_("Requesting preview..."));
544                 localcmd(strcat("\ncmd mv_getpicture ", ftos(id), "\n"));
545         }
546 }
547
548 void MapVote_CheckPK3(string pic, string pk3, int id)
549 {
550         TC(int, id);
551         entity pak;
552         pak = spawn();
553         pak.netname = pk3;
554         pak.message = pic;
555         pak.sv_entnum = id;
556
557         pak.chain = mv_pk3list;
558         mv_pk3list = pak;
559
560         if(pk3 != "")
561         {
562                 localcmd(strcat("\ncurl --pak ", pk3, "; wait; cl_cmd mv_download ", ftos(id), "\n"));
563         }
564         else
565         {
566                 Cmd_MapVote_MapDownload(tokenize_console(strcat("mv_download ", ftos(id))));
567         }
568 }
569
570 void MapVote_CheckPic(string pic, string pk3, int id)
571 {
572         TC(int, id);
573         // never try to retrieve a pic for the "don't care" 'map'
574         if(mv_abstain && id == mv_num_maps - 1)
575                 return;
576
577         if(PreviewExists(pic))
578         {
579                 mv_preview[id] = true;
580                 return;
581         }
582         MapVote_CheckPK3(pic, pk3, id);
583 }
584
585 void MapVote_ReadMask()
586 {
587         int i;
588         if ( mv_num_maps < 24 )
589         {
590                 int mask;
591                 if(mv_num_maps < 8)
592                         mask = ReadByte();
593                 else if(mv_num_maps < 16)
594                         mask = ReadShort();
595                 else
596                         mask = ReadLong();
597
598                 for(i = 0; i < mv_num_maps; ++i)
599                 {
600                         if (mask & BIT(i))
601                                 mv_flags[i] |= GTV_AVAILABLE;
602                         else
603                                 mv_flags[i] &= ~GTV_AVAILABLE;
604                 }
605         }
606         else
607         {
608                 for(i = 0; i < mv_num_maps; ++i )
609                         mv_flags[i] = ReadByte();
610         }
611 }
612
613 void MapVote_ReadOption(int i)
614 {
615         TC(int, i);
616         string map = strzone(ReadString());
617         string pk3 = strzone(ReadString());
618         int j = bound(0, ReadByte(), n_ssdirs - 1);
619
620         mv_maps[i] = map;
621         mv_pk3[i] = pk3;
622         mv_flags[i] = GTV_AVAILABLE;
623
624         string pic = strzone(strcat(ssdirs[j], "/", map));
625         mv_pics[i] = pic;
626         mv_preview[i] = false;
627         MapVote_CheckPic(pic, pk3, i);
628 }
629
630 void GameTypeVote_ReadOption(int i)
631 {
632         TC(int, i);
633         string gt = strzone(ReadString());
634
635         mv_maps[i] = gt;
636         mv_flags[i] = ReadByte();
637
638         string basetype = "";
639
640         if ( mv_flags[i] & GTV_CUSTOM )
641         {
642                 string name = ReadString();
643                 if ( strlen(name) < 1 )
644                         name = gt;
645                 mv_pk3[i] = strzone(name);
646                 mv_desc[i] = strzone(ReadString());
647                 basetype = strzone(ReadString());
648         }
649         else
650         {
651                 Gametype type = MapInfo_Type_FromString(gt, false);
652                 mv_pk3[i] = strzone(MapInfo_Type_ToText(type));
653                 mv_desc[i] = MapInfo_Type_Description(type);
654         }
655
656         string mv_picpath = sprintf("gfx/menu/%s/gametype_%s", autocvar_menu_skin, gt);
657         if(precache_pic(mv_picpath) == "")
658         {
659                 mv_picpath = strcat("gfx/menu/default/gametype_", gt);
660                 if(precache_pic(mv_picpath) == "")
661                 {
662                         mv_picpath = sprintf("gfx/menu/%s/gametype_%s", autocvar_menu_skin, basetype);
663                         if(precache_pic(mv_picpath) == "")
664                         {
665                                 mv_picpath = strcat("gfx/menu/default/gametype_", basetype);
666                         }
667                 }
668         }
669         string pic = strzone(mv_picpath);
670         mv_pics[i] = pic;
671         mv_preview[i] = PreviewExists(pic);
672 }
673
674 void MapVote_Init()
675 {
676         mv_active = 1;
677         if(!autocvar_hud_cursormode) mousepos = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
678         mv_selection = -1;
679         mv_selection_keyboard = 0;
680
681         string s;
682         for(n_ssdirs = 0; ; ++n_ssdirs)
683         {
684                 s = ReadString();
685                 if(s == "")
686                         break;
687                 if(n_ssdirs < NUM_SSDIRS)
688                         ssdirs[n_ssdirs] = s;
689         }
690         n_ssdirs = min(n_ssdirs, NUM_SSDIRS);
691
692         mv_num_maps = min(MAPVOTE_COUNT, ReadByte());
693         mv_abstain = ReadByte();
694         if(mv_abstain)
695                 mv_abstain = 1; // must be 1 for bool-true, makes stuff easier
696         mv_detail = ReadByte();
697
698         mv_ownvote = -1;
699         mv_timeout = ReadCoord();
700
701         gametypevote = ReadByte();
702
703         if(gametypevote)
704         {
705                 mapvote_chosenmap = strzone(ReadString());
706                 if ( gametypevote == 2 )
707                         gametypevote = 0;
708         }
709
710         MapVote_ReadMask();
711         int i;
712         for(i = 0; i < mv_num_maps; ++i )
713                 mv_flags_start[i] = mv_flags[i];
714
715         // Assume mv_pk3list is NULL, there should only be 1 mapvote per round
716         mv_pk3list = NULL; // I'm still paranoid!
717
718         for(i = 0; i < mv_num_maps; ++i)
719         {
720                 mv_votes[i] = 0;
721
722                 if ( gametypevote )
723                         GameTypeVote_ReadOption(i);
724                 else
725                         MapVote_ReadOption(i);
726         }
727
728         for(i = 0; i < n_ssdirs; ++i)
729                 ssdirs[n_ssdirs] = string_null;
730         n_ssdirs = 0;
731 }
732
733 void MapVote_SendChoice(int index)
734 {
735         TC(int, index);
736         localcmd(strcat("\nimpulse ", ftos(index+1), "\n"));
737 }
738
739 int MapVote_MoveLeft(int pos)
740 {
741         TC(int, pos);
742         int imp;
743         if ( pos < 0 )
744                 imp = mv_num_maps - 1;
745         else
746                 imp = pos < 1 ? mv_num_maps - 1 : pos - 1;
747         if ( !(mv_flags[imp] & GTV_AVAILABLE) && imp != mv_ownvote )
748                 imp = MapVote_MoveLeft(imp);
749         return imp;
750 }
751 int MapVote_MoveRight(int pos)
752 {
753         TC(int, pos);
754         int imp;
755         if ( pos < 0 )
756                 imp = 0;
757         else
758                 imp = pos >= mv_num_maps - 1 ? 0 : pos + 1;
759         if ( !(mv_flags[imp] & GTV_AVAILABLE) && imp != mv_ownvote )
760                 imp = MapVote_MoveRight(imp);
761         return imp;
762 }
763 int MapVote_MoveUp(int pos)
764 {
765         TC(int, pos);
766         int imp;
767         if ( pos < 0 )
768                 imp = mv_num_maps - 1;
769         else
770         {
771                 imp = pos - mv_columns;
772                 if ( imp < 0 )
773                 {
774                         imp = floor(mv_num_maps/mv_columns)*mv_columns + pos % mv_columns;
775                         if ( imp >= mv_num_maps )
776                                 imp -= mv_columns;
777                 }
778         }
779         if ( !(mv_flags[imp] & GTV_AVAILABLE) && imp != mv_ownvote )
780                 imp = MapVote_MoveUp(imp);
781         return imp;
782 }
783 int MapVote_MoveDown(int pos)
784 {
785         TC(int, pos);
786         int imp;
787         if ( pos < 0 )
788                 imp = 0;
789         else
790         {
791                 imp = pos + mv_columns;
792                 if ( imp >= mv_num_maps )
793                         imp = imp % mv_columns;
794         }
795         if ( !(mv_flags[imp] & GTV_AVAILABLE) && imp != mv_ownvote )
796                 imp = MapVote_MoveDown(imp);
797         return imp;
798 }
799
800 float MapVote_InputEvent(int bInputType, float nPrimary, float nSecondary)
801 {
802         TC(int, bInputType);
803
804         static int first_digit = 0;
805         if (!mv_active)
806                 return false;
807
808         if(bInputType == 3)
809         {
810                 mousepos.x = nPrimary;
811                 mousepos.y = nSecondary;
812                 mv_selection_keyboard = 0;
813                 return true;
814         }
815
816         if (bInputType == 0)
817         {
818                 if (nPrimary == K_ALT) hudShiftState |= S_ALT;
819                 if (nPrimary == K_CTRL) hudShiftState |= S_CTRL;
820                 if (nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
821         }
822         else if (bInputType == 1)
823         {
824                 if (nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
825                 if (nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
826                 if (nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
827
828                 if (nPrimary == K_CTRL)
829                         first_digit = 0;
830         }
831
832         if (bInputType != 0)
833                 return false;
834
835         int imp = 0;
836         switch(nPrimary)
837         {
838                 case K_RIGHTARROW:
839                         mv_selection_keyboard = 1;
840                         mv_selection = MapVote_MoveRight(mv_selection);
841                         return true;
842                 case K_LEFTARROW:
843                         mv_selection_keyboard = 1;
844                         mv_selection = MapVote_MoveLeft(mv_selection);
845                         return true;
846                 case K_DOWNARROW:
847                         mv_selection_keyboard = 1;
848                         mv_selection = MapVote_MoveDown(mv_selection);
849                         return true;
850                 case K_UPARROW:
851                         mv_selection_keyboard = 1;
852                         mv_selection = MapVote_MoveUp(mv_selection);
853                         return true;
854                 case K_KP_ENTER:
855                 case K_ENTER:
856                 case K_SPACE:
857                         if ( mv_selection_keyboard )
858                                 MapVote_SendChoice(mv_selection);
859                         return true;
860                 case '1': case K_KP_1: imp = 1; break;
861                 case '2': case K_KP_2: imp = 2; break;
862                 case '3': case K_KP_3: imp = 3; break;
863                 case '4': case K_KP_4: imp = 4; break;
864                 case '5': case K_KP_5: imp = 5; break;
865                 case '6': case K_KP_6: imp = 6; break;
866                 case '7': case K_KP_7: imp = 7; break;
867                 case '8': case K_KP_8: imp = 8; break;
868                 case '9': case K_KP_9: imp = 9; break;
869                 case '0': case K_KP_0: imp = 10; break;
870         }
871
872         if (imp && hudShiftState & S_CTRL)
873         {
874                 if (!first_digit)
875                 {
876                         first_digit = imp % 10;
877                         return true;
878                 }
879                 else
880                         imp = first_digit * 10 + (imp % 10);
881         }
882
883         if (nPrimary == K_MOUSE1)
884         {
885                 mv_selection_keyboard = 0;
886                 mv_selection = mv_mouse_selection;
887                 if (mv_selection >= 0)
888                         imp = min(mv_selection + 1, mv_num_maps);
889         }
890
891         if (imp)
892         {
893                 if (imp <= mv_num_maps)
894                         localcmd(strcat("\nimpulse ", ftos(imp), "\n"));
895                 return true;
896         }
897
898         return false;
899 }
900
901 void MapVote_UpdateMask()
902 {
903         MapVote_ReadMask();
904         mv_top2_time = time;
905 }
906
907 void MapVote_UpdateVotes()
908 {
909         int i;
910         for(i = 0; i < mv_num_maps; ++i)
911         {
912                 if(mv_flags[i] & GTV_AVAILABLE)
913                 {
914                         if(mv_detail)
915                                 mv_votes[i] = ReadByte();
916                         else
917                                 mv_votes[i] = 0;
918                 }
919                 else
920                         mv_votes[i] = -1;
921         }
922
923         mv_ownvote = ReadByte()-1;
924 }
925
926 NET_HANDLE(ENT_CLIENT_MAPVOTE, bool isnew)
927 {
928         make_pure(this);
929         int sf = ReadByte();
930         return = true;
931
932         if(sf & 1)
933                 MapVote_Init();
934
935         if(sf & 2)
936                 MapVote_UpdateMask();
937
938         if(sf & 4)
939                 MapVote_UpdateVotes();
940 }
941
942 NET_HANDLE(TE_CSQC_PICTURE, bool isNew)
943 {
944         Net_MapVote_Picture();
945         return true;
946 }
947
948 void Net_MapVote_Picture()
949 {
950         int type = ReadByte();
951         mv_preview[type] = true;
952         mv_pics[type] = strzone(ReadPicture());
953 }