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