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