]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/waypointsprites.qc
Merge branch 'master' into Mario/turrets
[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 "nb-ball": return _("Ball");
281                 case "ka-ball": return _("Ball");
282                 case "ka-ballcarrier": return _("Ball carrier");
283                 case "wpn-laser": return _("Laser");
284                 case "wpn-shotgun": return _("Shotgun");
285                 case "wpn-uzi": return _("Machine Gun");
286                 case "wpn-gl": return _("Mortar");
287                 case "wpn-electro": return _("Electro");
288                 case "wpn-crylink": return _("Crylink");
289                 case "wpn-nex": return _("Nex");
290                 case "wpn-hagar": return _("Hagar");
291                 case "wpn-rl": return _("Rocket Launcher");
292                 case "wpn-porto": return _("Port-O-Launch");
293                 case "wpn-minstanex": return _("Minstanex");
294                 case "wpn-hookgun": return _("Hook");
295                 case "wpn-fireball": return _("Fireball");
296                 case "wpn-hlac": return _("HLAC");
297                 case "wpn-campingrifle": return _("Rifle");
298                 case "wpn-minelayer": return _("Mine Layer");
299                 case "dom-neut": return _("Control point");
300                 case "dom-red": return _("Control point");
301                 case "dom-blue": return _("Control point");
302                 case "dom-yellow": return _("Control point");
303                 case "dom-pink": return _("Control point");
304                 case "item-invis": return _("Invisibility");
305                 case "item-extralife": return _("Extra life");
306                 case "item-speed": return _("Speed");
307                 case "item-strength": return _("Strength");
308                 case "item-shield": return _("Shield");
309                 case "item-fuelregen": return _("Fuel regen");
310                 case "item-jetpack": return _("Jet Pack");
311                 case "freezetag_frozen": return _("Frozen!");
312                 case "tagged-target": return _("Tagged");
313                 case "vehicle": return _("Vehicle");
314                 default: return s;
315         }
316 }
317
318 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
319 {
320         vector yvec = '0.299 0.587 0.114';
321         return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
322 }
323 vector fixrgbexcess(vector rgb)
324 {
325         if(rgb_x > 1)
326         {
327                 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
328                 if(rgb_y > 1)
329                 {
330                         rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
331                         if(rgb_z > 1)
332                                 rgb_z = 1;
333                 }
334                 else if(rgb_z > 1)
335                 {
336                         rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
337                         if(rgb_y > 1)
338                                 rgb_y = 1;
339                 }
340         }
341         else if(rgb_y > 1)
342         {
343                 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
344                 if(rgb_x > 1)
345                 {
346                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
347                         if(rgb_z > 1)
348                                 rgb_z = 1;
349                 }
350                 else if(rgb_z > 1)
351                 {
352                         rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
353                         if(rgb_x > 1)
354                                 rgb_x = 1;
355                 }
356         }
357         else if(rgb_z > 1)
358         {
359                 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
360                 if(rgb_x > 1)
361                 {
362                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
363                         if(rgb_y > 1)
364                                 rgb_y = 1;
365                 }
366                 else if(rgb_y > 1)
367                 {
368                         rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
369                         if(rgb_x > 1)
370                                 rgb_x = 1;
371                 }
372         }
373         return rgb;
374 }
375
376 float waypointsprite_count, waypointsprite_newcount;
377 void Draw_WaypointSprite()
378 {
379         string spriteimage;
380         float t;
381
382         if(self.lifetime)
383                 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
384         else
385                 self.alpha = 1;
386
387         if(self.hideflags & 2)
388                 return; // radar only
389
390         if(autocvar_cl_hidewaypoints >= 2)
391                 return;
392
393         if(self.hideflags & 1)
394                 if(autocvar_cl_hidewaypoints)
395                         return; // fixed waypoint
396
397         InterpolateOrigin_Do();
398
399         t = GetPlayerColor(player_localnum) + 1;
400
401         spriteimage = "";
402
403         // choose the sprite
404         switch(self.rule)
405         {
406                 case SPRITERULE_DEFAULT:
407                         if(self.team)
408                         {
409                                 if(self.team == t)
410                                         spriteimage = self.netname;
411                                 else
412                                         spriteimage = "";
413                         }
414                         else
415                                 spriteimage = self.netname;
416                         break;
417                 case SPRITERULE_TEAMPLAY:
418                         if(t == NUM_SPECTATOR + 1)
419                                 spriteimage = self.netname3;
420                         else if(self.team == t)
421                                 spriteimage = self.netname2;
422                         else
423                                 spriteimage = self.netname;
424                         break;
425                 default:
426                         error("Invalid waypointsprite rule!");
427                         break;
428         }
429
430         if(spriteimage == "")
431                 return;
432
433         ++waypointsprite_newcount;
434
435         float dist;
436         dist = vlen(self.origin - view_origin);
437
438         float a;
439         a = self.alpha * autocvar_hud_panel_fg_alpha;
440
441         if(self.maxdistance > waypointsprite_normdistance)
442                 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
443         else if(self.maxdistance > 0)
444                 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
445
446         vector rgb;
447         rgb = self.teamradar_color;
448         rgb = spritelookupcolor(spriteimage, rgb);
449         if(rgb == '0 0 0')
450         {
451                 self.teamradar_color = '1 0 1';
452                 printf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
453         }
454
455         if(time - floor(time) > 0.5)
456         {
457                 if(self.helpme && time < self.helpme)
458                         a *= SPRITE_HELPME_BLINK;
459                 else
460                         a *= spritelookupblinkvalue(spriteimage);
461         }
462
463         if(a > 1)
464         {
465                 rgb *= a;
466                 a = 1;
467         }
468
469         if(a <= 0)
470                 return;
471
472         rgb = fixrgbexcess(rgb);
473
474         vector o;
475         float ang;
476
477         o = project_3d_to_2d(self.origin);
478         if(o_z < 0
479         || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
480         || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
481         || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
482         || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
483         {
484                 // scale it to be just in view
485                 vector d;
486                 float f1, f2;
487
488                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
489                 ang = atan2(-d_x, -d_y);
490                 if(o_z < 0)
491                         ang += M_PI;
492
493                 f1 = d_x / vid_conwidth;
494                 f2 = d_y / vid_conheight;
495
496                 if(max(f1, -f1) > max(f2, -f2))
497                 {
498                         if(d_z * f1 > 0)
499                         {
500                                 // RIGHT edge
501                                 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
502                         }
503                         else
504                         {
505                                 // LEFT edge
506                                 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
507                         }
508                 }
509                 else
510                 {
511                         if(d_z * f2 > 0)
512                         {
513                                 // BOTTOM edge
514                                 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
515                         }
516                         else
517                         {
518                                 // TOP edge
519                                 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
520                         }
521                 }
522
523                 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
524         }
525         else
526         {
527 #if 1
528                 ang = M_PI;
529 #else
530                 vector d;
531                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
532                 ang = atan2(-d_x, -d_y);
533 #endif
534         }
535         o_z = 0;
536
537         float edgedistance_min, crosshairdistance;
538                 edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)),
539         (o_x - (vid_conwidth * waypointsprite_edgeoffset_left)),
540         (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x,
541         (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y);
542
543         float vidscale;
544         vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
545
546         crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
547
548         t = waypointsprite_scale * vidscale;
549         a *= waypointsprite_alpha;
550
551         {
552                 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
553                 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
554         }
555         if (edgedistance_min < waypointsprite_edgefadedistance) {
556                 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
557                 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
558         }
559         if(crosshairdistance < waypointsprite_crosshairfadedistance) {
560                 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
561                 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
562         }
563
564         if(self.build_finished)
565         {
566                 if(time < self.build_finished + 0.25)
567                 {
568                         if(time < self.build_started)
569                                 self.health = self.build_starthealth;
570                         else if(time < self.build_finished)
571                                 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
572                         else
573                                 self.health = 1;
574                 }
575                 else
576                         self.health = -1;
577         }
578
579         o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
580
581         string txt;
582         if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
583                 txt = _("Spam");
584         else
585                 txt = spritelookuptext(spriteimage);
586         if(self.helpme && time < self.helpme)
587                 txt = sprintf(_("%s needing help!"), txt);
588         if(autocvar_g_waypointsprite_uppercase)
589                 txt = strtoupper(txt);
590
591         draw_beginBoldFont();
592         if(self.health >= 0)
593         {
594                 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
595
596                 float align, marg;
597                 if(self.build_finished)
598                         align = 0.5;
599                 else
600                         align = 0;
601                 if(cos(ang) > 0)
602                         marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
603                 else
604                         marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
605                 drawhealthbar(
606                                 o,
607                                 0,
608                                 self.health,
609                                 '0 0 0',
610                                 '0 0 0',
611                                 SPRITE_HEALTHBAR_WIDTH * t,
612                                 SPRITE_HEALTHBAR_HEIGHT * t,
613                                 marg,
614                                 SPRITE_HEALTHBAR_BORDER * t,
615                                 align,
616                                 rgb,
617                                 a * SPRITE_HEALTHBAR_BORDERALPHA,
618                                 rgb,
619                                 a * SPRITE_HEALTHBAR_HEALTHALPHA,
620                                 DRAWFLAG_NORMAL
621                              );
622         }
623         else
624         {
625                 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
626         }
627         draw_endBoldFont();
628 }
629
630 void Ent_RemoveWaypointSprite()
631 {
632         if(self.netname)
633                 strunzone(self.netname);
634         if(self.netname2)
635                 strunzone(self.netname2);
636         if(self.netname3)
637                 strunzone(self.netname3);
638 }
639
640 void Ent_WaypointSprite()
641 {
642         float sendflags, f, t;
643         sendflags = ReadByte();
644
645         if(!self.spawntime)
646                 self.spawntime = time;
647
648         self.draw2d = Draw_WaypointSprite;
649
650         InterpolateOrigin_Undo();
651         self.iflags |= IFLAG_ORIGIN;
652
653         if(sendflags & 0x80)
654         {
655                 t = ReadByte();
656                 if(t < 192)
657                 {
658                         self.health = t / 191.0;
659                         self.build_finished = 0;
660                 }
661                 else
662                 {
663                         t = (t - 192) * 256 + ReadByte();
664                         self.build_started = servertime;
665                         if(self.build_finished)
666                                 self.build_starthealth = bound(0, self.health, 1);
667                         else
668                                 self.build_starthealth = 0;
669                         self.build_finished = servertime + t / 32;
670                 }
671         }
672         else
673         {
674                 self.health = -1;
675                 self.build_finished = 0;
676         }
677
678         if(sendflags & 64)
679         {
680                 // unfortunately, this needs to be exact (for the 3D display)
681                 self.origin_x = ReadCoord();
682                 self.origin_y = ReadCoord();
683                 self.origin_z = ReadCoord();
684                 setorigin(self, self.origin);
685         }
686
687         if(sendflags & 1)
688         {
689                 self.team = ReadByte();
690                 self.rule = ReadByte();
691         }
692
693         if(sendflags & 2)
694         {
695                 if(self.netname)
696                         strunzone(self.netname);
697                 self.netname = strzone(ReadString());
698         }
699
700         if(sendflags & 4)
701         {
702                 if(self.netname2)
703                         strunzone(self.netname2);
704                 self.netname2 = strzone(ReadString());
705         }
706
707         if(sendflags & 8)
708         {
709                 if(self.netname3)
710                         strunzone(self.netname3);
711                 self.netname3 = strzone(ReadString());
712         }
713
714         if(sendflags & 16)
715         {
716                 self.lifetime = ReadCoord();
717                 self.fadetime = ReadCoord();
718                 self.maxdistance = ReadShort();
719                 self.hideflags = ReadByte();
720         }
721
722         if(sendflags & 32)
723         {
724                 f = ReadByte();
725                 self.teamradar_icon = (f & 0x7F);
726                 if(f & 0x80)
727                 {
728                         self.(teamradar_times[self.teamradar_time_index]) = time;
729                         self.teamradar_time_index = mod(self.teamradar_time_index + 1, MAX_TEAMRADAR_TIMES);
730                 }
731                 self.teamradar_color_x = ReadByte() / 255.0;
732                 self.teamradar_color_y = ReadByte() / 255.0;
733                 self.teamradar_color_z = ReadByte() / 255.0;
734                 self.helpme = ReadByte() * 0.1;
735                 if(self.helpme > 0)
736                         self.helpme += servertime;
737         }
738
739         InterpolateOrigin_Note();
740
741         self.entremove = Ent_RemoveWaypointSprite;
742 }
743
744 void WaypointSprite_Load_Frames(string ext)
745 {
746         float dh, n, i, o, f;
747         string s, sname, sframes;
748         dh = search_begin(strcat("models/sprites/*_frame*", ext), FALSE, FALSE);
749         if (dh < 0)
750                  return;
751         float ext_len = strlen(ext);
752         n = search_getsize(dh);
753         for(i = 0; i < n; ++i)
754         {
755                 s = search_getfilename(dh, i);
756                 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
757
758                 o = strstrofs(s, "_frame", 0);
759                 sname = strcat("/spriteframes/", substring(s, 0, o));
760                 sframes = substring(s, o + 6, strlen(s) - o - 6);
761                 f = stof(sframes) + 1;
762                 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
763         }
764         search_end(dh);
765 }
766
767 void WaypointSprite_Load()
768 {
769         waypointsprite_fadedistance = vlen(mi_scale);
770         waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
771         waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
772         waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
773         waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
774         waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
775         waypointsprite_scale = autocvar_g_waypointsprite_scale;
776         waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
777         waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
778         waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
779         waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
780         waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
781         waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
782         waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
783         waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
784         waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
785         waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
786         waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
787         waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
788         waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
789         waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
790         waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
791
792         if(!waypointsprite_initialized)
793         {
794                 WaypointSprite_Load_Frames(".tga");
795                 WaypointSprite_Load_Frames(".jpg");
796                 waypointsprite_initialized = true;
797         }
798
799         waypointsprite_count = waypointsprite_newcount;
800         waypointsprite_newcount = 0;
801 }