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