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