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