]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/waypointsprites.qc
workaround for when a sprite is already attached: append a "needing help!" text
[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(self.helpme && time < self.helpme)
439                 a *= SPRITE_HELPME_BLINK;
440         else if(time - floor(time) > 0.5)
441                 a *= spritelookupblinkvalue(spriteimage);
442
443         if(a > 1)
444         {
445                 rgb *= a;
446                 a = 1;
447         }
448
449         if(a <= 0)
450                 return;
451
452         rgb = fixrgbexcess(rgb);
453
454         vector o;
455         float ang;
456
457         o = project_3d_to_2d(self.origin);
458         if(o_z < 0 || o_x < 0 || o_y < 0 || o_x > vid_conwidth || o_y > vid_conheight)
459         {
460                 // scale it to be just in view
461                 vector d;
462                 float f1, f2;
463
464                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
465                 ang = atan2(-d_x, -d_y);
466                 if(o_z < 0)
467                         ang += M_PI;
468
469                 f1 = d_x / vid_conwidth;
470                 f2 = d_y / vid_conheight;
471
472                 if(max(f1, -f1) > max(f2, -f2))
473                 {
474                         if(d_z * f1 > 0)
475                         {
476                                 // RIGHT edge
477                                 d = d * (0.5 / f1);
478                         }
479                         else
480                         {
481                                 // LEFT edge
482                                 d = d * (-0.5 / f1);
483                         }
484                 }
485                 else
486                 {
487                         if(d_z * f2 > 0)
488                         {
489                                 // BOTTOM edge
490                                 d = d * (0.5 / f2);
491                         }
492                         else
493                         {
494                                 // TOP edge
495                                 d = d * (-0.5 / f2);
496                         }
497                 }
498
499                 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
500         }
501         else
502         {
503 #if 1
504                 ang = M_PI;
505 #else
506                 vector d;
507                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
508                 ang = atan2(-d_x, -d_y);
509 #endif
510         }
511         o_z = 0;
512
513         float edgedistance_min, crosshairdistance;
514         edgedistance_min = min4(o_y, o_x,vid_conwidth - o_x, vid_conheight - o_y);
515
516         float vidscale;
517         vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
518
519         crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
520
521         t = waypointsprite_scale * vidscale;
522         a *= waypointsprite_alpha;
523
524         {
525                 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
526                 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
527         }
528         if (edgedistance_min < waypointsprite_edgefadedistance) {
529                 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
530                 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
531         }
532         if(crosshairdistance < waypointsprite_crosshairfadedistance) {
533                 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
534                 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
535         }
536
537         if(self.build_finished)
538         {
539                 if(time < self.build_finished + 0.25)
540                 {
541                         if(time < self.build_started)
542                                 self.health = self.build_starthealth;
543                         else if(time < self.build_finished)
544                                 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
545                         else
546                                 self.health = 1;
547                 }
548                 else
549                         self.health = -1;
550         }
551
552         o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
553         
554         string txt;
555         txt = spritelookuptext(spriteimage);
556         if(self.helpme && time < self.helpme)
557                 txt = sprintf(_("%s needing help!"), txt);
558         if(autocvar_g_waypointsprite_uppercase)
559                 txt = strtoupper(txt);
560
561         if(self.health >= 0)
562         {
563                 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
564
565                 float align, marg;
566                 if(self.build_finished)
567                         align = 0.5;
568                 else
569                         align = 0;
570                 if(cos(ang) > 0)
571                         marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
572                 else
573                         marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
574                 drawhealthbar(
575                                 o,
576                                 0,
577                                 self.health,
578                                 '0 0 0',
579                                 '0 0 0',
580                                 SPRITE_HEALTHBAR_WIDTH * t,
581                                 SPRITE_HEALTHBAR_HEIGHT * t,
582                                 marg,
583                                 SPRITE_HEALTHBAR_BORDER * t,
584                                 align,
585                                 rgb,
586                                 a * SPRITE_HEALTHBAR_BORDERALPHA,
587                                 rgb,
588                                 a * SPRITE_HEALTHBAR_HEALTHALPHA,
589                                 DRAWFLAG_NORMAL
590                              );
591         }
592         else
593         {
594                 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
595         }
596 }
597
598 void Ent_RemoveWaypointSprite()
599 {
600         if(self.netname)
601                 strunzone(self.netname);
602         if(self.netname2)
603                 strunzone(self.netname2);
604         if(self.netname3)
605                 strunzone(self.netname3);
606 }
607
608 void Ent_WaypointSprite()
609 {
610         float sendflags, f, t;
611         sendflags = ReadByte();
612
613         if(!self.spawntime)
614                 self.spawntime = time;
615
616         self.draw2d = Draw_WaypointSprite;
617
618         InterpolateOrigin_Undo();
619
620         if(sendflags & 0x80)
621         {
622                 t = ReadByte();
623                 if(t < 192)
624                 {
625                         self.health = t / 191.0;
626                         self.build_finished = 0;
627                 }
628                 else
629                 {
630                         t = (t - 192) * 256 + ReadByte();
631                         self.build_started = servertime;
632                         if(self.build_finished)
633                                 self.build_starthealth = bound(0, self.health, 1);
634                         else
635                                 self.build_starthealth = 0;
636                         self.build_finished = servertime + t / 32;
637                 }
638         }
639         else
640         {
641                 self.health = -1;
642                 self.build_finished = 0;
643         }
644
645         if(sendflags & 64)
646         {
647                 // unfortunately, this needs to be exact (for the 3D display)
648                 self.origin_x = ReadCoord();
649                 self.origin_y = ReadCoord();
650                 self.origin_z = ReadCoord();
651         }
652
653         if(sendflags & 1)
654         {
655                 self.team = ReadByte();
656                 self.rule = ReadByte();
657         }
658
659         if(sendflags & 2)
660         {
661                 if(self.netname)
662                         strunzone(self.netname);
663                 self.netname = strzone(ReadString());
664         }
665
666         if(sendflags & 4)
667         {
668                 if(self.netname2)
669                         strunzone(self.netname2);
670                 self.netname2 = strzone(ReadString());
671         }
672
673         if(sendflags & 8)
674         {
675                 if(self.netname3)
676                         strunzone(self.netname3);
677                 self.netname3 = strzone(ReadString());
678         }
679
680         if(sendflags & 16)
681         {
682                 self.lifetime = ReadCoord();
683                 self.fadetime = ReadCoord();
684                 self.maxdistance = ReadShort();
685                 self.hideflags = ReadByte();
686         }
687
688         if(sendflags & 32)
689         {
690                 f = ReadByte();
691                 self.teamradar_icon = (f & 0x7F);
692                 if(f & 0x80)
693                 {
694                         self.(teamradar_times[self.teamradar_time_index]) = time;
695                         self.teamradar_time_index = mod(self.teamradar_time_index + 1, MAX_TEAMRADAR_TIMES);
696                 }
697                 self.teamradar_color_x = ReadByte() / 255.0;
698                 self.teamradar_color_y = ReadByte() / 255.0;
699                 self.teamradar_color_z = ReadByte() / 255.0;
700                 self.helpme = ReadByte() * 0.1;
701                 if(self.helpme > 0)
702                         self.helpme += servertime;
703         }
704
705         InterpolateOrigin_Note();
706
707         self.entremove = Ent_RemoveWaypointSprite;
708 }
709
710 void WaypointSprite_Load_Frames(string ext)
711 {
712         float dh, n, i, o, f;
713         string s, sname, sframes;
714         dh = search_begin(strcat("models/sprites/*_frame*", ext), FALSE, FALSE);
715         if (dh < 0)
716                  return;
717         float ext_len = strlen(ext);
718         n = search_getsize(dh);
719         for(i = 0; i < n; ++i)
720         {
721                 s = search_getfilename(dh, i);
722                 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
723
724                 o = strstrofs(s, "_frame", 0);
725                 sname = strcat("/spriteframes/", substring(s, 0, o));
726                 sframes = substring(s, o + 6, strlen(s) - o - 6);
727                 f = stof(sframes) + 1;
728                 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
729         }
730         search_end(dh);
731 }
732
733 void WaypointSprite_Load()
734 {
735         waypointsprite_fadedistance = vlen(mi_scale);
736         waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
737         waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
738         waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
739         waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
740         waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
741         waypointsprite_scale = autocvar_g_waypointsprite_scale;
742         waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
743         waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
744         waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
745         waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
746         waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
747         waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
748         waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
749         waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
750         waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
751         waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
752         waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
753
754         if(!waypointsprite_initialized)
755         {
756                 WaypointSprite_Load_Frames(".tga");
757                 WaypointSprite_Load_Frames(".jpg");
758                 waypointsprite_initialized = true;
759         }
760 }