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