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