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