]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/waypointsprites.qc
Use weaponinfo to get weapon sprite info
[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         {
241                 print(s, "\n");
242                 return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).message);
243         }
244
245         switch(s)
246         {
247                 case "as-push": return _("Push");
248                 case "as-destroy": return _("Destroy");
249                 case "as-defend": return _("Defend");
250                 case "bluebase": return _("Blue base");
251                 case "danger": return _("DANGER");
252                 case "enemyflagcarrier": return _("Enemy carrier");
253                 case "flagcarrier": return _("Flag carrier");
254                 case "flagdropped": return _("Dropped flag");
255                 case "helpme": return _("Help me!");
256                 case "here": return _("Here");
257                 case "key-dropped": return _("Dropped key");
258                 case "keycarrier-blue": return _("Key carrier");
259                 case "keycarrier-finish": return _("Run here");
260                 case "keycarrier-friend": return _("Key carrier");
261                 case "keycarrier-pink": return _("Key carrier");
262                 case "keycarrier-red": return _("Key carrier");
263                 case "keycarrier-yellow": return _("Key carrier");
264                 case "redbase": return _("Red base");
265                 case "waypoint": return _("Waypoint");
266                 case "ons-gen-red": return _("Generator");
267                 case "ons-gen-blue": return _("Generator");
268                 case "ons-gen-shielded": return _("Generator");
269                 case "ons-cp-neut": return _("Control point");
270                 case "ons-cp-red": return _("Control point");
271                 case "ons-cp-blue": return _("Control point");
272                 case "ons-cp-atck-neut": return _("Control point");
273                 case "ons-cp-atck-red": return _("Control point");
274                 case "ons-cp-atck-blue": return _("Control point");
275                 case "ons-cp-dfnd-red": return _("Control point");
276                 case "ons-cp-dfnd-blue": return _("Control point");
277                 case "race-checkpoint": return _("Checkpoint");
278                 case "race-finish": return _("Finish");
279                 case "race-start": return _("Start");
280                 case "race-start-finish": return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
281                 case "nb-ball": return _("Ball");
282                 case "ka-ball": return _("Ball");
283                 case "ka-ballcarrier": return _("Ball carrier");
284                 case "dom-neut": return _("Control point");
285                 case "dom-red": return _("Control point");
286                 case "dom-blue": return _("Control point");
287                 case "dom-yellow": return _("Control point");
288                 case "dom-pink": return _("Control point");
289                 case "item-invis": return _("Invisibility");
290                 case "item-extralife": return _("Extra life");
291                 case "item-speed": return _("Speed");
292                 case "item-strength": return _("Strength");
293                 case "item-shield": return _("Shield");
294                 case "item-fuelregen": return _("Fuel regen");
295                 case "item-jetpack": return _("Jet Pack");
296                 case "freezetag_frozen": return _("Frozen!");
297                 case "tagged-target": return _("Tagged");
298                 case "vehicle": return _("Vehicle");
299                 default: return s;
300         }
301 }
302
303 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
304 {
305         vector yvec = '0.299 0.587 0.114';
306         return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
307 }
308 vector fixrgbexcess(vector rgb)
309 {
310         if(rgb_x > 1)
311         {
312                 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
313                 if(rgb_y > 1)
314                 {
315                         rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
316                         if(rgb_z > 1)
317                                 rgb_z = 1;
318                 }
319                 else if(rgb_z > 1)
320                 {
321                         rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
322                         if(rgb_y > 1)
323                                 rgb_y = 1;
324                 }
325         }
326         else if(rgb_y > 1)
327         {
328                 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
329                 if(rgb_x > 1)
330                 {
331                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
332                         if(rgb_z > 1)
333                                 rgb_z = 1;
334                 }
335                 else if(rgb_z > 1)
336                 {
337                         rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
338                         if(rgb_x > 1)
339                                 rgb_x = 1;
340                 }
341         }
342         else if(rgb_z > 1)
343         {
344                 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
345                 if(rgb_x > 1)
346                 {
347                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
348                         if(rgb_y > 1)
349                                 rgb_y = 1;
350                 }
351                 else if(rgb_y > 1)
352                 {
353                         rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
354                         if(rgb_x > 1)
355                                 rgb_x = 1;
356                 }
357         }
358         return rgb;
359 }
360
361 float waypointsprite_count, waypointsprite_newcount;
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_localnum) + 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 == NUM_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         ++waypointsprite_newcount;
419
420         float dist;
421         dist = vlen(self.origin - view_origin);
422
423         float a;
424         a = self.alpha * autocvar_hud_panel_fg_alpha;
425
426         if(self.maxdistance > waypointsprite_normdistance)
427                 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
428         else if(self.maxdistance > 0)
429                 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
430
431         vector rgb;
432         rgb = self.teamradar_color;
433         rgb = spritelookupcolor(spriteimage, rgb);
434         if(rgb == '0 0 0')
435         {
436                 self.teamradar_color = '1 0 1';
437                 print(sprintf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage));
438         }
439
440         if(time - floor(time) > 0.5)
441         {
442                 if(self.helpme && time < self.helpme)
443                         a *= SPRITE_HELPME_BLINK;
444                 else
445                         a *= spritelookupblinkvalue(spriteimage);
446         }
447
448         if(a > 1)
449         {
450                 rgb *= a;
451                 a = 1;
452         }
453
454         if(a <= 0)
455                 return;
456
457         rgb = fixrgbexcess(rgb);
458
459         vector o;
460         float ang;
461
462         o = project_3d_to_2d(self.origin);
463         if(o_z < 0
464         || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
465         || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
466         || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
467         || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
468         {
469                 // scale it to be just in view
470                 vector d;
471                 float f1, f2;
472
473                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
474                 ang = atan2(-d_x, -d_y);
475                 if(o_z < 0)
476                         ang += M_PI;
477
478                 f1 = d_x / vid_conwidth;
479                 f2 = d_y / vid_conheight;
480
481                 if(max(f1, -f1) > max(f2, -f2))
482                 {
483                         if(d_z * f1 > 0)
484                         {
485                                 // RIGHT edge
486                                 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
487                         }
488                         else
489                         {
490                                 // LEFT edge
491                                 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
492                         }
493                 }
494                 else
495                 {
496                         if(d_z * f2 > 0)
497                         {
498                                 // BOTTOM edge
499                                 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
500                         }
501                         else
502                         {
503                                 // TOP edge
504                                 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
505                         }
506                 }
507
508                 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
509         }
510         else
511         {
512 #if 1
513                 ang = M_PI;
514 #else
515                 vector d;
516                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
517                 ang = atan2(-d_x, -d_y);
518 #endif
519         }
520         o_z = 0;
521
522         float edgedistance_min, crosshairdistance;
523                 edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)),
524         (o_x - (vid_conwidth * waypointsprite_edgeoffset_left)),
525         (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x,
526         (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y);
527
528         float vidscale;
529         vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
530
531         crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
532
533         t = waypointsprite_scale * vidscale;
534         a *= waypointsprite_alpha;
535
536         {
537                 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
538                 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
539         }
540         if (edgedistance_min < waypointsprite_edgefadedistance) {
541                 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
542                 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
543         }
544         if(crosshairdistance < waypointsprite_crosshairfadedistance) {
545                 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
546                 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
547         }
548
549         if(self.build_finished)
550         {
551                 if(time < self.build_finished + 0.25)
552                 {
553                         if(time < self.build_started)
554                                 self.health = self.build_starthealth;
555                         else if(time < self.build_finished)
556                                 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
557                         else
558                                 self.health = 1;
559                 }
560                 else
561                         self.health = -1;
562         }
563
564         o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
565
566         string txt;
567         if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
568                 txt = _("Spam");
569         else
570                 txt = spritelookuptext(spriteimage);
571         if(self.helpme && time < self.helpme)
572                 txt = sprintf(_("%s needing help!"), txt);
573         if(autocvar_g_waypointsprite_uppercase)
574                 txt = strtoupper(txt);
575
576         draw_beginBoldFont();
577         if(self.health >= 0)
578         {
579                 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
580
581                 float align, marg;
582                 if(self.build_finished)
583                         align = 0.5;
584                 else
585                         align = 0;
586                 if(cos(ang) > 0)
587                         marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
588                 else
589                         marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
590                 drawhealthbar(
591                                 o,
592                                 0,
593                                 self.health,
594                                 '0 0 0',
595                                 '0 0 0',
596                                 SPRITE_HEALTHBAR_WIDTH * t,
597                                 SPRITE_HEALTHBAR_HEIGHT * t,
598                                 marg,
599                                 SPRITE_HEALTHBAR_BORDER * t,
600                                 align,
601                                 rgb,
602                                 a * SPRITE_HEALTHBAR_BORDERALPHA,
603                                 rgb,
604                                 a * SPRITE_HEALTHBAR_HEALTHALPHA,
605                                 DRAWFLAG_NORMAL
606                              );
607         }
608         else
609         {
610                 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
611         }
612         draw_endBoldFont();
613 }
614
615 void Ent_RemoveWaypointSprite()
616 {
617         if(self.netname)
618                 strunzone(self.netname);
619         if(self.netname2)
620                 strunzone(self.netname2);
621         if(self.netname3)
622                 strunzone(self.netname3);
623 }
624
625 void Ent_WaypointSprite()
626 {
627         float sendflags, f, t;
628         sendflags = ReadByte();
629
630         if(!self.spawntime)
631                 self.spawntime = time;
632
633         self.draw2d = Draw_WaypointSprite;
634
635         InterpolateOrigin_Undo();
636         self.iflags |= IFLAG_ORIGIN;
637
638         if(sendflags & 0x80)
639         {
640                 t = ReadByte();
641                 if(t < 192)
642                 {
643                         self.health = t / 191.0;
644                         self.build_finished = 0;
645                 }
646                 else
647                 {
648                         t = (t - 192) * 256 + ReadByte();
649                         self.build_started = servertime;
650                         if(self.build_finished)
651                                 self.build_starthealth = bound(0, self.health, 1);
652                         else
653                                 self.build_starthealth = 0;
654                         self.build_finished = servertime + t / 32;
655                 }
656         }
657         else
658         {
659                 self.health = -1;
660                 self.build_finished = 0;
661         }
662
663         if(sendflags & 64)
664         {
665                 // unfortunately, this needs to be exact (for the 3D display)
666                 self.origin_x = ReadCoord();
667                 self.origin_y = ReadCoord();
668                 self.origin_z = ReadCoord();
669                 setorigin(self, self.origin);
670         }
671
672         if(sendflags & 1)
673         {
674                 self.team = ReadByte();
675                 self.rule = ReadByte();
676         }
677
678         if(sendflags & 2)
679         {
680                 if(self.netname)
681                         strunzone(self.netname);
682                 self.netname = strzone(ReadString());
683         }
684
685         if(sendflags & 4)
686         {
687                 if(self.netname2)
688                         strunzone(self.netname2);
689                 self.netname2 = strzone(ReadString());
690         }
691
692         if(sendflags & 8)
693         {
694                 if(self.netname3)
695                         strunzone(self.netname3);
696                 self.netname3 = strzone(ReadString());
697         }
698
699         if(sendflags & 16)
700         {
701                 self.lifetime = ReadCoord();
702                 self.fadetime = ReadCoord();
703                 self.maxdistance = ReadShort();
704                 self.hideflags = ReadByte();
705         }
706
707         if(sendflags & 32)
708         {
709                 f = ReadByte();
710                 self.teamradar_icon = (f & 0x7F);
711                 if(f & 0x80)
712                 {
713                         self.(teamradar_times[self.teamradar_time_index]) = time;
714                         self.teamradar_time_index = mod(self.teamradar_time_index + 1, MAX_TEAMRADAR_TIMES);
715                 }
716                 self.teamradar_color_x = ReadByte() / 255.0;
717                 self.teamradar_color_y = ReadByte() / 255.0;
718                 self.teamradar_color_z = ReadByte() / 255.0;
719                 self.helpme = ReadByte() * 0.1;
720                 if(self.helpme > 0)
721                         self.helpme += servertime;
722         }
723
724         InterpolateOrigin_Note();
725
726         self.entremove = Ent_RemoveWaypointSprite;
727 }
728
729 void WaypointSprite_Load_Frames(string ext)
730 {
731         float dh, n, i, o, f;
732         string s, sname, sframes;
733         dh = search_begin(strcat("models/sprites/*_frame*", ext), FALSE, FALSE);
734         if (dh < 0)
735                  return;
736         float ext_len = strlen(ext);
737         n = search_getsize(dh);
738         for(i = 0; i < n; ++i)
739         {
740                 s = search_getfilename(dh, i);
741                 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
742
743                 o = strstrofs(s, "_frame", 0);
744                 sname = strcat("/spriteframes/", substring(s, 0, o));
745                 sframes = substring(s, o + 6, strlen(s) - o - 6);
746                 f = stof(sframes) + 1;
747                 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
748         }
749         search_end(dh);
750 }
751
752 void WaypointSprite_Load()
753 {
754         waypointsprite_fadedistance = vlen(mi_scale);
755         waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
756         waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
757         waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
758         waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
759         waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
760         waypointsprite_scale = autocvar_g_waypointsprite_scale;
761         waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
762         waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
763         waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
764         waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
765         waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
766         waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
767         waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
768         waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
769         waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
770         waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
771         waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
772         waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
773         waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
774         waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
775         waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
776
777         if(!waypointsprite_initialized)
778         {
779                 WaypointSprite_Load_Frames(".tga");
780                 WaypointSprite_Load_Frames(".jpg");
781                 waypointsprite_initialized = true;
782         }
783
784         waypointsprite_count = waypointsprite_newcount;
785         waypointsprite_newcount = 0;
786 }