]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/waypointsprites.qc
an all-new waypointsprite drawing system that allows sprites to be translated
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / waypointsprites.qc
1 float waypointsprite_initialized;
2 float waypointsprite_fadedistance;
3 float waypointsprite_normdistance;
4 float waypointsprite_minscale;
5 float waypointsprite_minalpha;
6 float waypointsprite_distancealphaexponent;
7 float waypointsprite_timealphaexponent;
8 float waypointsprite_scale;
9 float waypointsprite_fontsize;
10 float waypointsprite_edgefadealpha;
11 float waypointsprite_edgefadescale;
12 float waypointsprite_edgefadedistance;
13 float waypointsprite_crosshairfadealpha;
14 float waypointsprite_crosshairfadescale;
15 float waypointsprite_crosshairfadedistance;
16 float waypointsprite_distancefadealpha;
17 float waypointsprite_distancefadescale;
18 float waypointsprite_distancefadedistance;
19 float waypointsprite_alpha;
20
21 .float rule;
22 .string netname; // primary picture
23 .string netname2; // secondary picture
24 .string netname3; // tertiary picture
25 .float team; // team that gets netname2
26 .float lifetime;
27 .float fadetime;
28 .float maxdistance;
29 .float hideflags;
30 .float spawntime;
31 .float health;
32 .float build_started;
33 .float build_starthealth;
34 .float build_finished;
35
36 float SPRITE_HEALTHBAR_WIDTH = 144;
37 float SPRITE_HEALTHBAR_HEIGHT = 9;
38 float SPRITE_HEALTHBAR_MARGIN = 6;
39 float SPRITE_HEALTHBAR_BORDER = 2;
40 float SPRITE_HEALTHBAR_BORDERALPHA = 1;
41 float SPRITE_HEALTHBAR_HEALTHALPHA = 0.5;
42 float SPRITE_ARROW_SCALE = 1.0;
43
44 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
45 {
46         vector v1, v2, v3, v4;
47
48         hotspot = -1 * hotspot;
49
50         // hotspot-relative coordinates of the corners
51         v1 = hotspot;
52         v2 = hotspot + '1 0 0' * sz_x;
53         v3 = hotspot + '1 0 0' * sz_x + '0 1 0' * sz_y;
54         v4 = hotspot                  + '0 1 0' * sz_y;
55
56         // rotate them, and make them absolute
57         rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
58         v1 = rotate(v1, rot) + org;
59         v2 = rotate(v2, rot) + org;
60         v3 = rotate(v3, rot) + org;
61         v4 = rotate(v4, rot) + org;
62
63         // draw them
64         R_BeginPolygon(pic, f);
65         R_PolygonVertex(v1, '0 0 0', rgb, a);
66         R_PolygonVertex(v2, '1 0 0', rgb, a);
67         R_PolygonVertex(v3, '1 1 0', rgb, a);
68         R_PolygonVertex(v4, '0 1 0', rgb, a);
69         R_EndPolygon();
70 }
71
72 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
73 {
74         R_BeginPolygon(pic, f);
75         R_PolygonVertex(o, '0 0 0', rgb, a);
76         R_PolygonVertex(o + ri, '1 0 0', rgb, a);
77         R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
78         R_PolygonVertex(o + up, '0 1 0', rgb, a);
79         R_EndPolygon();
80 }
81
82 void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float height, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
83 {
84         vector o, ri, up;
85         float owidth; // outer width
86
87         hotspot = -1 * hotspot;
88
89         // hotspot-relative coordinates of the healthbar corners
90         o = hotspot;
91         ri = '1 0 0';
92         up = '0 1 0';
93         
94         rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
95         o = rotate(o, rot) + org;
96         ri = rotate(ri, rot);
97         up = rotate(up, rot);
98
99         owidth = width + 2 * border;
100         o = o - up * (margin + border + height) + ri * (sz_x - owidth) * 0.5;
101
102         drawquad(o - up * border,                               ri * owidth,    up * border, "", rgb,  a,  f);
103         drawquad(o + up * height,                               ri * owidth,    up * border, "", rgb,  a,  f);
104         drawquad(o,                                             ri * border,    up * height, "", rgb,  a,  f);
105         drawquad(o + ri * (owidth - border),                    ri * border,    up * height, "", rgb,  a,  f);
106         drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * height, "", hrgb, ha, f);
107 }
108
109 // returns location of sprite text
110 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
111 {
112         R_BeginPolygon("", DRAWFLAG_NORMAL);
113         R_PolygonVertex(o + rotate('-11.5 9.5 0'*t, ang), '0 0 0', '0 0 0', a);
114         R_PolygonVertex(o + rotate('11.5 9.5 0'*t, ang), '0 0 0', '0 0 0', a);
115         R_PolygonVertex(o + rotate('0 -2 0'*t, ang), '0 0 0', '0 0 0', a);
116         R_EndPolygon();
117         R_BeginPolygon("", DRAWFLAG_NORMAL);
118         R_PolygonVertex(o + rotate('-5.5 9.5 0'*t, ang), '0 0 0', '0 0 0', a);
119         R_PolygonVertex(o + rotate('-5.5 17.5 0'*t, ang), '0 0 0', '0 0 0', a);
120         R_PolygonVertex(o + rotate('5.5 17.5 0'*t, ang), '0 0 0', '0 0 0', a);
121         R_PolygonVertex(o + rotate('5.5 9.5 0'*t, ang), '0 0 0', '0 0 0', a);
122         R_EndPolygon();
123
124         R_BeginPolygon("", DRAWFLAG_NORMAL);
125         R_PolygonVertex(o + rotate('-8 8 0'*t, ang), '0 0 0', rgb, a);
126         R_PolygonVertex(o + rotate('8 8 0'*t, ang), '0 0 0', rgb, a);
127         R_PolygonVertex(o + rotate('0 0 0'*t, ang), '0 0 0', rgb, a);
128         R_EndPolygon();
129         R_BeginPolygon("", DRAWFLAG_NORMAL);
130         R_PolygonVertex(o + rotate('-4 8 0'*t, ang), '0 0 0', rgb, a);
131         R_PolygonVertex(o + rotate('-4 16 0'*t, ang), '0 0 0', rgb, a);
132         R_PolygonVertex(o + rotate('4 16 0'*t, ang), '0 0 0', rgb, a);
133         R_PolygonVertex(o + rotate('4 8 0'*t, ang), '0 0 0', rgb, a);
134         R_EndPolygon();
135
136         return
137                 o + rotate('0 20 0'*t, ang);
138 }
139
140 // returns location of sprite healthbar
141 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
142 {
143         float algnx, algny;
144         float sw, w, h;
145         float aspect, sa, ca;
146
147         sw = stringwidth(s, FALSE, fontsize);
148         if(sw > minwidth)
149                 w = sw;
150         else
151                 w = minwidth;
152         h = fontsize_y;
153
154         // how do corners work?
155         aspect = vid_conwidth / vid_conheight;
156         sa = sin(ang);
157         ca = cos(ang) * aspect;
158         if(fabs(sa) > fabs(ca))
159         {
160                 algnx = (sa < 0);
161                 algny = 0.5 - 0.5 * ca / fabs(sa);
162         }
163         else
164         {
165                 algnx = 0.5 - 0.5 * sa / fabs(ca);
166                 algny = (ca < 0);
167         }
168
169         // align
170         o_x -= w * algnx;
171         o_y -= h * algny;
172
173         // we want to be onscreen
174         if(o_x < 0)
175                 o_x = 0;
176         if(o_y < 0)
177                 o_y = 0;
178         if(o_x > vid_conwidth - w)
179                 o_x = vid_conwidth - w;
180         if(o_y > vid_conheight - h)
181                 o_x = vid_conheight - h;
182
183         o_x += 0.5 * (w - sw);
184
185         drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
186
187         o_x += 0.5 * sw;
188         o_y += 0.5 * h;
189
190         return o;
191 }
192
193 float spritelookupblinkvalue(string s)
194 {
195         switch(s)
196         {
197                 case "ons-cp-atck-neut": return 2;
198                 case "ons-cp-atck-red":  return 2;
199                 case "ons-cp-atck-blue": return 2;
200                 case "ons-cp-dfnd-red":  return 0.5;
201                 case "ons-cp-dfnd-blue": return 0.5;
202                 case "item-invis":       return 2;
203                 case "item-extralife":   return 2;
204                 case "item-speed":       return 2;
205                 case "item-strength":    return 2;
206                 case "item-shueld":      return 2;
207                 case "item-fuelregen":   return 2;
208                 case "item-jetpack":     return 2;
209                 default:                 return 1;
210         }
211 }
212 string spritelookuptext(string s)
213 {
214         switch(s)
215         {
216                 case "as-push": return _("Push");
217                 case "as-destroy": return _("Destroy");
218                 case "as-defend": return _("Defend");
219                 case "bluebase": return _("Blue base");
220                 case "danger": return _("DANGER");
221                 case "flagcarrier": return _("Flag carrier");
222                 case "flagdropped": return _("Dropped flag");
223                 case "helpme": return _("Help me!");
224                 case "here": return _("Here");
225                 case "key-dropped": return _("Dropped key");
226                 case "keycarrier-blue": return _("Key carrier");
227                 case "keycarrier-finish": return _("Run here");
228                 case "keycarrier-friend": return _("Key carrier");
229                 case "keycarrier-pink": return _("Key carrier");
230                 case "keycarrier-red": return _("Key carrier");
231                 case "keycarrier-yellow": return _("Key carrier");
232                 case "redbase": return _("Red base");
233                 case "waypoint": return _("Waypoint");
234                 case "ons-gen-red": return _("Generator");
235                 case "ons-gen-blue": return _("Generator");
236                 case "ons-gen-shielded": return _("Generator");
237                 case "ons-cp-neut": return _("Control point");
238                 case "ons-cp-red": return _("Control point");
239                 case "ons-cp-blue": return _("Control point");
240                 case "ons-cp-atck-neut": return _("Control point");
241                 case "ons-cp-atck-red": return _("Control point");
242                 case "ons-cp-atck-blue": return _("Control point");
243                 case "ons-cp-dfnd-red": return _("Control point");
244                 case "ons-cp-dfnd-blue": return _("Control point");
245                 case "race-checkpoint": return _("Checkpoint");
246                 case "race-finish": return _("Finish");
247                 case "race-start": return _("Start");
248                 case "nb-ball": return _("Ball");
249                 case "ka-ball": return _("Ball");
250                 case "ka-ballcarrier": return _("Ball carrier");
251                 case "wpn-laser": return _("Laser");
252                 case "wpn-shotgun": return _("Shotgun");
253                 case "wpn-uzi": return _("Machine Gun");
254                 case "wpn-gl": return _("Mortar");
255                 case "wpn-electro": return _("Electro");
256                 case "wpn-crylink": return _("Crylink");
257                 case "wpn-nex": return _("Nex");
258                 case "wpn-hagar": return _("Hagar");
259                 case "wpn-rl": return _("Rocket Launcher");
260                 case "wpn-porto": return _("Port-O-Launch");
261                 case "wpn-minstanex": return _("Minstanex");
262                 case "wpn-hookgun": return _("Hook");
263                 case "wpn-fireball": return _("Fireball");
264                 case "wpn-hlac": return _("HLAC");
265                 case "wpn-campingrifle": return _("Rifle");
266                 case "wpn-minelayer": return _("Mine Layer");
267                 case "dom-neut": return _("Control point");
268                 case "dom-red": return _("Control point");
269                 case "dom-blue": return _("Control point");
270                 case "dom-yellow": return _("Control point");
271                 case "dom-pink": return _("Control point");
272                 case "item-invis": return _("Invisibility");
273                 case "item-extralife": return _("Extra life");
274                 case "item-speed": return _("Speed");
275                 case "item-strength": return _("Strength");
276                 case "item-shield": return _("Shield");
277                 case "item-fuelregen": return _("Fuel regen");
278                 case "item-jetpack": return _("Jet Pack");
279                 case "freezetag_frozen": return _("Frozen!");
280                 case "tagged-target": return _("Tagged");
281                 default: return s;
282         }
283 }
284
285 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
286 {
287         vector yvec = '0.299 0.587 0.114';
288         return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
289 }
290 vector fixrgbexcess(vector rgb)
291 {
292         if(rgb_x > 1)
293         {
294                 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
295                 if(rgb_y > 1)
296                 {
297                         rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
298                         if(rgb_z > 1)
299                                 rgb_z = 1;
300                 }
301                 else if(rgb_z > 1)
302                 {
303                         rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
304                         if(rgb_y > 1)
305                                 rgb_y = 1;
306                 }
307         }
308         else if(rgb_y > 1)
309         {
310                 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
311                 if(rgb_x > 1)
312                 {
313                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
314                         if(rgb_z > 1)
315                                 rgb_z = 1;
316                 }
317                 else if(rgb_z > 1)
318                 {
319                         rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
320                         if(rgb_x > 1)
321                                 rgb_x = 1;
322                 }
323         }
324         else if(rgb_z > 1)
325         {
326                 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
327                 if(rgb_x > 1)
328                 {
329                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
330                         if(rgb_y > 1)
331                                 rgb_y = 1;
332                 }
333                 else if(rgb_y > 1)
334                 {
335                         rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
336                         if(rgb_x > 1)
337                                 rgb_x = 1;
338                 }
339         }
340         return rgb;
341 }
342
343 void Draw_WaypointSprite()
344 {
345         string spriteimage;
346         float t;
347
348         if(self.lifetime)
349                 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
350         else
351                 self.alpha = 1;
352
353         if(self.hideflags & 2)
354                 return; // radar only
355
356         if(autocvar_cl_hidewaypoints >= 2)
357                 return;
358
359         if(self.hideflags & 1)
360                 if(autocvar_cl_hidewaypoints)
361                         return; // fixed waypoint
362
363         InterpolateOrigin_Do();
364
365         t = GetPlayerColor(player_localentnum - 1) + 1;
366
367         spriteimage = "";
368
369         // choose the sprite
370         switch(self.rule)
371         {
372                 case SPRITERULE_DEFAULT:
373                         if(self.team)
374                         {
375                                 if(self.team == t)
376                                         spriteimage = self.netname;
377                                 else
378                                         spriteimage = "";
379                         }
380                         else
381                                 spriteimage = self.netname;
382                         break;
383                 case SPRITERULE_TEAMPLAY:
384                         if(t == COLOR_SPECTATOR + 1)
385                                 spriteimage = self.netname3;
386                         else if(self.team == t)
387                                 spriteimage = self.netname2;
388                         else
389                                 spriteimage = self.netname;
390                         break;
391                 default:
392                         error("Invalid waypointsprite rule!");
393                         break;
394         }
395
396         if(spriteimage == "")
397                 return;
398         
399         float dist;
400         dist = vlen(self.origin - view_origin);
401         
402         float a;
403         a = self.alpha * autocvar_hud_panel_fg_alpha;
404
405         if(self.maxdistance > waypointsprite_normdistance)
406                 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
407         else if(self.maxdistance > 0)
408                 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
409
410         vector rgb;
411         rgb = self.teamradar_color;
412
413         if(time - floor(time) > 0.5)
414                 a *= spritelookupblinkvalue(spriteimage);
415
416         if(a > 1)
417         {
418                 rgb *= a;
419                 a = 1;
420         }
421
422         if(a <= 0)
423                 return;
424
425         rgb = fixrgbexcess(rgb);
426
427         vector o;
428         float ang;
429
430         o = project_3d_to_2d(self.origin);
431         if(o_z < 0 || o_x < 0 || o_y < 0 || o_x > vid_conwidth || o_y > vid_conheight)
432         {
433                 // scale it to be just in view
434                 vector d;
435                 float f1, f2;
436
437                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
438                 ang = atan2(-d_x, -d_y);
439                 if(o_z < 0)
440                         ang += M_PI;
441
442                 f1 = d_x / vid_conwidth;
443                 f2 = d_y / vid_conheight;
444
445                 if(max(f1, -f1) > max(f2, -f2))
446                 {
447                         if(d_z * f1 > 0)
448                         {
449                                 // RIGHT edge
450                                 d = d * (0.5 / f1);
451                         }
452                         else
453                         {
454                                 // LEFT edge
455                                 d = d * (-0.5 / f1);
456                         }
457                 }
458                 else
459                 {
460                         if(d_z * f2 > 0)
461                         {
462                                 // BOTTOM edge
463                                 d = d * (0.5 / f2);
464                         }
465                         else
466                         {
467                                 // TOP edge
468                                 d = d * (-0.5 / f2);
469                         }
470                 }
471
472                 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
473         }
474         else
475         {
476                 ang = M_PI;
477         }
478         o_z = 0;
479
480         float edgedistance_min, crosshairdistance;
481         edgedistance_min = min4(o_y, o_x,vid_conwidth - o_x, vid_conheight - o_y);
482
483         float vidscale;
484         vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
485
486         crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
487
488         t = waypointsprite_scale * vidscale;
489         a *= waypointsprite_alpha;
490
491         {
492                 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
493                 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
494         }
495         if (edgedistance_min < waypointsprite_edgefadedistance) {
496                 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
497                 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
498         }
499         if(crosshairdistance < waypointsprite_crosshairfadedistance) {
500                 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
501                 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
502         }
503
504         if(self.build_finished)
505         {
506                 if(time < self.build_finished + 0.25)
507                 {
508                         if(time < self.build_started)
509                                 self.health = self.build_starthealth;
510                         else if(time < self.build_finished)
511                                 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
512                         else
513                                 self.health = 1;
514                 }
515                 else
516                         self.health = -1;
517         }
518
519         o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
520         
521         string txt;
522         txt = spritelookuptext(spriteimage);
523         txt = strtoupper(txt);
524
525         if(self.health >= 0)
526         {
527                 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
528
529                 float align, marg;
530                 if(self.build_finished)
531                         align = 0.5;
532                 else
533                         align = 0;
534                 if(cos(ang) > 0)
535                         marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
536                 else
537                         marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
538                 drawhealthbar(
539                                 o,
540                                 0,
541                                 self.health,
542                                 '0 0 0',
543                                 '0 0 0',
544                                 SPRITE_HEALTHBAR_WIDTH * t,
545                                 SPRITE_HEALTHBAR_HEIGHT * t,
546                                 marg,
547                                 SPRITE_HEALTHBAR_BORDER * t,
548                                 align,
549                                 rgb,
550                                 a * SPRITE_HEALTHBAR_BORDERALPHA,
551                                 rgb,
552                                 a * SPRITE_HEALTHBAR_HEALTHALPHA,
553                                 DRAWFLAG_NORMAL
554                              );
555         }
556         else
557         {
558                 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
559         }
560 }
561
562 void Ent_RemoveWaypointSprite()
563 {
564         if(self.netname)
565                 strunzone(self.netname);
566         if(self.netname2)
567                 strunzone(self.netname2);
568         if(self.netname3)
569                 strunzone(self.netname3);
570 }
571
572 void Ent_WaypointSprite()
573 {
574         float sendflags, f, t;
575         sendflags = ReadByte();
576
577         if(!self.spawntime)
578                 self.spawntime = time;
579
580         self.draw2d = Draw_WaypointSprite;
581
582         InterpolateOrigin_Undo();
583
584         if(sendflags & 0x80)
585         {
586                 t = ReadByte();
587                 if(t < 192)
588                 {
589                         self.health = t / 191.0;
590                         self.build_finished = 0;
591                 }
592                 else
593                 {
594                         t = (t - 192) * 256 + ReadByte();
595                         self.build_started = servertime;
596                         if(self.build_finished)
597                                 self.build_starthealth = bound(0, self.health, 1);
598                         else
599                                 self.build_starthealth = 0;
600                         self.build_finished = servertime + t / 32;
601                 }
602         }
603         else
604         {
605                 self.health = -1;
606                 self.build_finished = 0;
607         }
608
609         if(sendflags & 64)
610         {
611                 // unfortunately, this needs to be exact (for the 3D display)
612                 self.origin_x = ReadCoord();
613                 self.origin_y = ReadCoord();
614                 self.origin_z = ReadCoord();
615         }
616
617         if(sendflags & 1)
618         {
619                 self.team = ReadByte();
620                 self.rule = ReadByte();
621         }
622
623         if(sendflags & 2)
624         {
625                 if(self.netname)
626                         strunzone(self.netname);
627                 self.netname = strzone(ReadString());
628         }
629
630         if(sendflags & 4)
631         {
632                 if(self.netname2)
633                         strunzone(self.netname2);
634                 self.netname2 = strzone(ReadString());
635         }
636
637         if(sendflags & 8)
638         {
639                 if(self.netname3)
640                         strunzone(self.netname3);
641                 self.netname3 = strzone(ReadString());
642         }
643
644         if(sendflags & 16)
645         {
646                 self.lifetime = ReadCoord();
647                 self.fadetime = ReadCoord();
648                 self.maxdistance = ReadShort();
649                 self.hideflags = ReadByte();
650         }
651
652         if(sendflags & 32)
653         {
654                 f = ReadByte();
655                 self.teamradar_icon = (f & 0x7F);
656                 if(f & 0x80)
657                 {
658                         self.(teamradar_times[self.teamradar_time_index]) = time;
659                         self.teamradar_time_index = mod(self.teamradar_time_index + 1, MAX_TEAMRADAR_TIMES);
660                 }
661                 self.teamradar_color_x = ReadByte() / 255.0;
662                 self.teamradar_color_y = ReadByte() / 255.0;
663                 self.teamradar_color_z = ReadByte() / 255.0;
664         }
665
666         InterpolateOrigin_Note();
667
668         self.entremove = Ent_RemoveWaypointSprite;
669 }
670
671 void WaypointSprite_Load_Frames(string ext)
672 {
673         float dh, n, i, o, f;
674         string s, sname, sframes;
675         dh = search_begin(strcat("models/sprites/*_frame*", ext), FALSE, FALSE);
676         if (dh < 0)
677                  return;
678         float ext_len = strlen(ext);
679         n = search_getsize(dh);
680         for(i = 0; i < n; ++i)
681         {
682                 s = search_getfilename(dh, i);
683                 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
684
685                 o = strstrofs(s, "_frame", 0);
686                 sname = strcat("/spriteframes/", substring(s, 0, o));
687                 sframes = substring(s, o + 6, strlen(s) - o - 6);
688                 f = stof(sframes) + 1;
689                 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
690         }
691         search_end(dh);
692 }
693
694 void WaypointSprite_Load()
695 {
696         waypointsprite_fadedistance = vlen(mi_scale);
697         waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
698         waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
699         waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
700         waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
701         waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
702         waypointsprite_scale = autocvar_g_waypointsprite_scale;
703         waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
704         waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
705         waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
706         waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
707         waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
708         waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
709         waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
710         waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
711         waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
712         waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
713         waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
714
715         if(!waypointsprite_initialized)
716         {
717                 WaypointSprite_Load_Frames(".tga");
718                 WaypointSprite_Load_Frames(".jpg");
719                 waypointsprite_initialized = true;
720         }
721 }