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