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