Merge remote branch 'refs/remotes/origin/fruitiex/racefixes'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / mapvoting.qc
1 float mv_num_maps;
2
3 float mv_active;
4 string mv_maps[MAPVOTE_COUNT];
5 string mv_pics[MAPVOTE_COUNT];
6 string mv_pk3[MAPVOTE_COUNT];
7 float mv_preview[MAPVOTE_COUNT];
8 float mv_votes[MAPVOTE_COUNT];
9 entity mv_pk3list;
10 float mv_abstain;
11 float mv_ownvote;
12 float mv_detail;
13 float mv_timeout;
14 float mv_maps_mask;
15
16 vector mv_mousepos;
17 float mv_selection;
18
19 string MapVote_FormatMapItem(float id, string map, float count, float maxwidth, vector fontsize)
20 {
21         string pre, post;
22         pre = strcat(ftos(id+1), ". ");
23         if(mv_detail)
24         {
25                 if(count == 1)
26                         post = strcat(" (1 vote)");
27                 else
28                         post = strcat(" (", ftos(count), " votes)");
29         }
30         else
31                 post = "";
32         maxwidth -= stringwidth(pre, FALSE, fontsize) + stringwidth(post, FALSE, fontsize);
33         map = textShortenToWidth(map, maxwidth, fontsize, stringwidth_nocolors);
34         return strcat(pre, map, post);
35 }
36
37 vector MapVote_RGB(float id)
38 {
39         if(id == mv_ownvote)
40                 return '0 1 0';
41         else if (id == mv_selection)
42                 return '1 1 0';
43         else
44                 return '1 1 1';
45 }
46
47 void MapVote_DrawMapItem(vector pos, float isize, float tsize, string map, string pic, float count, float id)
48 {
49         vector img_size;
50         vector rgb;
51         string label;
52         float text_size;
53         
54         isize -= hud_fontsize_y; // respect the text when calculating the image size
55
56         rgb = MapVote_RGB(id);
57         
58         img_size_y = isize;
59         img_size_x = isize / 0.75; // 4:3 x can be stretched easily, height is defined in isize
60
61         drawfont = hud_font;
62         pos_y = pos_y + img_size_y;
63         
64         label = MapVote_FormatMapItem(id, map, count, tsize, hud_fontsize);
65
66         text_size = stringwidth(label, false, hud_fontsize);
67         
68         pos_x -= text_size*0.5;
69         drawstring(pos, label, hud_fontsize, rgb, 1, DRAWFLAG_NORMAL);
70         
71         pos_x = pos_x + text_size*0.5 - img_size_x*0.5;
72         pos_y = pos_y - img_size_y;
73
74         pos += autocvar_scoreboard_border_thickness * '1 1 0';
75         img_size -= (autocvar_scoreboard_border_thickness * 2) * '1 1 0';
76         if(pic == "")
77         {
78                 drawfill(pos, img_size, '.5 .5 .5', .7, DRAWFLAG_NORMAL);
79         }
80         else
81         {
82                 drawpic(pos, pic, img_size, '1 1 1', 1, DRAWFLAG_NORMAL);
83         }
84
85         if(id == mv_ownvote)
86                 drawborderlines(autocvar_scoreboard_border_thickness, pos, img_size, rgb, 1, DRAWFLAG_NORMAL);
87         else
88                 drawborderlines(autocvar_scoreboard_border_thickness, pos, img_size, '0 0 0', 1, DRAWFLAG_NORMAL);
89
90         if(id == mv_selection)
91                 drawfill(pos, img_size, '1 1 1', 0.1, DRAWFLAG_NORMAL);
92 }
93
94 void MapVote_DrawAbstain(vector pos, float isize, float tsize, float count, float id)
95 {
96         vector rgb;
97         float text_size;
98         string label;
99         
100         rgb = MapVote_RGB(id);
101
102         drawfont = hud_font;
103         pos_y = pos_y + hud_fontsize_y;
104         
105         label = MapVote_FormatMapItem(id, "Don't care", count, tsize, hud_fontsize);
106
107         text_size = stringwidth(label, false, hud_fontsize);
108         
109         pos_x -= text_size*0.5;
110         drawstring(pos, label, hud_fontsize, rgb, 1, DRAWFLAG_NORMAL);
111 }
112
113 vector MapVote_GridVec(vector gridspec, float i, float m)
114 {
115         float r;
116         r = mod(i, m);
117         return
118                 '1 0 0' * (gridspec_x * r)
119                 +
120                 '0 1 0' * (gridspec_y * (i - r) / m);
121 }
122
123 float MapVote_Selection(vector topleft, vector cellsize, float rows, float columns)
124 {
125         float cell;
126         float c, r;
127
128         cell = -1;
129
130         for (r = 0; r < rows; ++r)
131                 for (c = 0; c < columns; ++c)
132                 {
133                         if (mv_mousepos_x >= topleft_x + cellsize_x *  c &&
134                                 mv_mousepos_x <= topleft_x + cellsize_x * (c + 1) &&
135                                 mv_mousepos_y >= topleft_y + cellsize_y *  r &&
136                                 mv_mousepos_y <= topleft_y + cellsize_y * (r + 1))
137                         {
138                                 cell = r * columns + c;
139                                 break;
140                         }
141                 }
142
143         if (cell >= mv_num_maps)
144                 cell = -1;
145
146         if (mv_abstain && cell < 0)
147                 return mv_num_maps;
148
149         return cell;
150 }
151
152 void MapVote_Draw()
153 {
154         string map;
155         float i, tmp;
156         vector pos;
157         float isize;
158         float center;
159         float columns, rows;
160         float tsize;
161         vector dist;
162
163         if(!mv_active)
164                 return;
165         
166         mv_mousepos = mv_mousepos + getmousepos();
167
168         mv_mousepos_x = bound(0, mv_mousepos_x, vid_conwidth);
169         mv_mousepos_y = bound(0, mv_mousepos_y, vid_conheight);
170
171         center = (vid_conwidth - 1)/2;
172         xmin = vid_conwidth*0.05; // 5% border must suffice
173         xmax = vid_conwidth - xmin;
174         ymin = 20;
175         i = cvar("con_chatpos"); //*cvar("con_chatsize");
176         if(i < 0)
177                 ymax = vid_conheight + (i - cvar("con_chat")) * cvar("con_chatsize");
178         if(i >= 0 || ymax < (vid_conheight*0.5))
179                 ymax = vid_conheight - ymin;
180
181         drawfont = hud_bigfont;
182         hud_fontsize = HUD_GetFontsize("hud_fontsize");
183
184         pos_y = ymin;
185         pos_z = 0;
186         //pos_x = center - stringwidth("Vote for a map", false) * 0.5 * 24;
187         pos_x = center - stringwidth("Vote for a map", false, '12 0 0');
188         drawstring(pos, "Vote for a map", '24 24 0', '1 1 1', 1, DRAWFLAG_NORMAL);
189         pos_y += 26;
190
191         i = ceil(max(0, mv_timeout - time));
192         map = strcat(ftos(i), " seconds left");
193         //pos_x = center - stringwidth(map, false) * 0.5 * 16;
194         pos_x = center - stringwidth(map, false, '8 0 0');
195         drawstring(pos, map, '16 16 0', '0 1 0', 1, DRAWFLAG_NORMAL);
196         pos_y += 22;
197         pos_x = xmin;
198
199         drawfont = hud_font;
200         
201         // base for multi-column stuff...
202         ymin = pos_y;
203         if(mv_abstain)
204                 mv_num_maps -= 1;
205         
206         if(mv_num_maps > 3)
207         {
208                 columns = 3;
209         } else {
210                 columns = mv_num_maps;
211         }
212         rows = ceil(mv_num_maps / columns);
213
214         dist_x = (xmax - xmin) / columns;
215         dist_y = (ymax - pos_y) / rows;
216         tsize = dist_x - 10;
217         isize = min(dist_y - 10, 0.75 * tsize);
218
219         mv_selection = MapVote_Selection(pos, dist, rows, columns);
220
221         pos_x += (xmax - xmin) / (2 * columns);
222         pos_y += (dist_y - isize) / 2;
223         ymax -= isize;
224
225         for(i = 0; i < mv_num_maps; ++i)
226         {
227                 tmp = mv_votes[i]; // FTEQCC bug: too many array accesses in the function call screw it up
228                 if(tmp < 0)
229                         continue;
230                 map = mv_maps[i];
231                 if(mv_preview[i])
232                         MapVote_DrawMapItem(pos + MapVote_GridVec(dist, i, columns), isize, tsize, map, mv_pics[i], tmp, i);
233                 else
234                         MapVote_DrawMapItem(pos + MapVote_GridVec(dist, i, columns), isize, tsize, map, "", tmp, i);
235         }
236
237         if(mv_abstain)
238                 ++mv_num_maps;
239         
240         if(mv_abstain && i < mv_num_maps) {
241                 tmp = mv_votes[i];
242                 pos_y = ymax + isize - hud_fontsize_y;
243                 pos_x = (xmax+xmin)*0.5;
244                 MapVote_DrawAbstain(pos, isize, xmax - xmin, tmp, i);
245         }
246
247         drawpic(mv_mousepos, strcat("gfx/menu/", cvar_string("menu_skin"), "/cursor.tga"), '32 32 0', '1 1 1', autocvar_hud_panel_fg_alpha, DRAWFLAG_NORMAL);
248 }
249
250 void Cmd_MapVote_MapDownload(float argc)
251 {
252         float id;
253         entity pak;
254
255         if(argc != 2 || !mv_pk3list)
256         {
257                 print("mv_mapdownload: ^3You're not supposed to use this command on your own!\n");
258                 return;
259         }
260         
261         id = stof(argv(1));
262         for(pak = mv_pk3list; pak; pak = pak.chain)
263                 if(pak.sv_entnum == id)
264                         break;
265         
266         if(!pak || pak.sv_entnum != id) {
267                 print("^1Error:^7 Couldn't find pak index.\n");
268                 return;
269         }
270
271         //print(strcat("^3Adding: ", ftos(id), " - ", pak.message, " - "));
272         
273         if(PreviewExists(pak.message))
274         {
275                 mv_preview[id] = true;
276                 //print("^2Found...\n");
277                 return;
278         } else {
279                 print("Requesting preview...\n");
280                 localcmd(strcat("\ncmd mv_getpic ", ftos(id), "\n"));
281         }
282 }
283
284 void MapVote_CheckPK3(string pic, string pk3, float id)
285 {
286         entity pak;
287         pak = spawn();
288         pak.netname = pk3;
289         pak.message = pic;
290         pak.sv_entnum = id;
291         
292         pak.chain = mv_pk3list;
293         mv_pk3list = pak;
294         
295         if(pk3 != "")
296         {
297                 localcmd(strcat("\ncurl --pak ", pk3, "; wait; cl_cmd mv_download ", ftos(id), "\n"));
298         }
299         else
300         {
301                 Cmd_MapVote_MapDownload(tokenize_console(strcat("mv_download ", ftos(id))));
302         }
303 }
304
305 void MapVote_CheckPic(string pic, string pk3, float id)
306 {
307         // never try to retrieve a pic for the "don't care" 'map'
308         if(mv_abstain && id == mv_num_maps - 1)
309                 return;
310
311         if(PreviewExists(pic))
312         {
313                 mv_preview[id] = true;
314                 return;
315         }
316         MapVote_CheckPK3(pic, pk3, id);
317 }
318
319 #define NUM_SSDIRS 4
320 string ssdirs[NUM_SSDIRS];
321 float n_ssdirs;
322 void MapVote_Init()
323 {
324         float i, j, power;
325         string map, pk3, s;
326
327         precache_sound ("misc/invshot.wav");
328
329         mv_active = 1;
330
331         mv_mousepos = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
332         mv_selection = -1;
333
334         for(n_ssdirs = 0; ; ++n_ssdirs)
335         {
336                 s = ReadString();
337                 if(s == "")
338                         break;
339                 if(n_ssdirs < NUM_SSDIRS)
340                         ssdirs[n_ssdirs] = s;
341         }
342         n_ssdirs = min(n_ssdirs, NUM_SSDIRS);
343
344         mv_num_maps = min(MAPVOTE_COUNT, ReadByte());
345         mv_abstain = ReadByte();
346         if(mv_abstain)
347                 mv_abstain = 1; // must be 1 for bool-true, makes stuff easier
348         mv_detail = ReadByte();
349
350         mv_ownvote = -1;
351         mv_timeout = ReadCoord();
352
353         if(mv_num_maps <= 8)
354                 mv_maps_mask = ReadByte();
355         else
356                 mv_maps_mask = ReadShort();
357         
358         // Assume mv_pk3list is NULL, there should only be 1 mapvote per round
359         mv_pk3list = NULL; // I'm still paranoid!
360         
361         for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2)
362         {
363                 mv_votes[i] = 0;
364
365                 if(mv_maps_mask & power)
366                 {
367                         map = strzone(ReadString());
368                         pk3 = strzone(ReadString());
369                         j = bound(0, ReadByte(), n_ssdirs - 1);
370         
371                         mv_maps[i] = map;
372                         mv_pk3[i] = pk3;
373                         map = strzone(strcat(ssdirs[j], "/", map));
374                         mv_pics[i] = map;
375
376                         mv_preview[i] = false;
377
378                         //print(strcat("RECV: ", map, " in ", pk3, "\n"));
379                         MapVote_CheckPic(map, pk3, i);
380                 }
381                 else
382                 {
383                         mv_maps[i] = strzone("if-you-see-this-the-code-is-broken");
384                         mv_pk3[i] = strzone("if-you-see-this-the-code-is-broken");
385                         mv_pics[i] = strzone("if-you-see-this-the-code-is-broken");
386                         mv_preview[i] = false;
387                 }
388         }
389
390         for(i = 0; i < n_ssdirs; ++i)
391                 ssdirs[n_ssdirs] = string_null;
392         n_ssdirs = 0;
393 }
394
395 float MapVote_InputEvent(float bInputType, float nPrimary, float nSecondary)
396 {
397         float imp;
398
399         if (!mv_active)
400                 return false;
401
402         if (bInputType != 0)
403                 return false;
404
405         if ('0' <= nPrimary && nPrimary <= '9')
406         {
407                 imp = nPrimary - '0';
408                 if (imp == 0) imp = 10;
409                 localcmd(strcat("\nimpulse ", ftos(imp), "\n"));
410                 return true;
411         }
412
413         if (nPrimary == K_MOUSE1)
414                 if (mv_selection >= 0)
415                 {
416                         imp = min(mv_selection + 1, mv_num_maps);
417                         localcmd(strcat("\nimpulse ", ftos(imp), "\n"));
418                         return true;
419                 }
420
421         return false;
422 }
423
424 void MapVote_UpdateMask()
425 {
426         float i, power;
427         float oldmask;
428
429         oldmask = mv_maps_mask;
430         if(mv_num_maps <= 8)
431                 mv_maps_mask = ReadByte();
432         else
433                 mv_maps_mask = ReadShort();
434
435         if(oldmask & mv_maps_mask != oldmask)
436                 if(oldmask & mv_maps_mask == mv_maps_mask)
437                          sound(world, CHAN_AUTO, "misc_invshot.wav", VOL_BASE, ATTN_NONE);
438
439         // remove votes that no longer apply
440         for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2)
441                 if not(mv_maps_mask & power)
442                         mv_votes[i] = -1;
443 }
444
445 void MapVote_UpdateVotes()
446 {
447         float i, power;
448         for(i = 0, power = 1; i < mv_num_maps; ++i, power *= 2)
449         {
450                 if(mv_maps_mask & power)
451                 {
452                         if(mv_detail)
453                                 mv_votes[i] = ReadByte();
454                         else
455                                 mv_votes[i] = 0;
456                 }
457                 else
458                         mv_votes[i] = -1;
459         }
460
461         mv_ownvote = ReadByte()-1;
462 }
463
464 void Ent_MapVote()
465 {
466         float sf;
467
468         sf = ReadByte();
469
470         if(sf & 1)
471                 MapVote_Init();
472
473         if(sf & 2)
474                 MapVote_UpdateMask();
475
476         if(sf & 4)
477                 MapVote_UpdateVotes();
478 }
479
480 void Net_MapVote_Picture()
481 {
482         float type;
483         type = ReadByte();
484         mv_preview[type] = true;
485         mv_pics[type] = strzone(ReadPicture());
486 }