]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/waypointsprites.qc
Get rid of some fugly nexball hakks
[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 float waypointsprite_count, waypointsprite_newcount;
384 void Draw_WaypointSprite()
385 {
386         string spriteimage;
387         float t;
388
389         if(self.lifetime)
390                 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
391         else
392                 self.alpha = 1;
393
394         if(self.hideflags & 2)
395                 return; // radar only
396
397         if(autocvar_cl_hidewaypoints >= 2)
398                 return;
399
400         if(self.hideflags & 1)
401                 if(autocvar_cl_hidewaypoints)
402                         return; // fixed waypoint
403
404         InterpolateOrigin_Do();
405
406         t = GetPlayerColor(player_localnum) + 1;
407
408         spriteimage = "";
409
410         // choose the sprite
411         switch(self.rule)
412         {
413                 case SPRITERULE_DEFAULT:
414                         if(self.team)
415                         {
416                                 if(self.team == t)
417                                         spriteimage = self.netname;
418                                 else
419                                         spriteimage = "";
420                         }
421                         else
422                                 spriteimage = self.netname;
423                         break;
424                 case SPRITERULE_TEAMPLAY:
425                         if(t == COLOR_SPECTATOR + 1)
426                                 spriteimage = self.netname3;
427                         else if(self.team == t)
428                                 spriteimage = self.netname2;
429                         else
430                                 spriteimage = self.netname;
431                         break;
432                 default:
433                         error("Invalid waypointsprite rule!");
434                         break;
435         }
436
437         if(spriteimage == "")
438                 return;
439
440         ++waypointsprite_newcount;
441         
442         float dist;
443         dist = vlen(self.origin - view_origin);
444         
445         float a;
446         a = self.alpha * autocvar_hud_panel_fg_alpha;
447
448         if(self.maxdistance > waypointsprite_normdistance)
449                 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
450         else if(self.maxdistance > 0)
451                 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
452
453         vector rgb;
454         rgb = self.teamradar_color;
455         rgb = spritelookupcolor(spriteimage, rgb);
456         if(rgb == '0 0 0')
457         {
458                 self.teamradar_color = '1 0 1';
459                 print(sprintf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage)); 
460         }
461
462         if(time - floor(time) > 0.5)
463         {
464                 if(self.helpme && time < self.helpme)
465                         a *= SPRITE_HELPME_BLINK;
466                 else
467                         a *= spritelookupblinkvalue(spriteimage);
468         }
469
470         if(a > 1)
471         {
472                 rgb *= a;
473                 a = 1;
474         }
475
476         if(a <= 0)
477                 return;
478
479         rgb = fixrgbexcess(rgb);
480
481         vector o;
482         float ang;
483
484         o = project_3d_to_2d(self.origin);
485         if(o_z < 0 
486         || o_x < (vid_conwidth * waypointsprite_edgeoffset_left) 
487         || o_y < (vid_conheight * waypointsprite_edgeoffset_top) 
488         || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))  
489         || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
490         {
491                 // scale it to be just in view
492                 vector d;
493                 float f1, f2;
494
495                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
496                 ang = atan2(-d_x, -d_y);
497                 if(o_z < 0)
498                         ang += M_PI;
499
500                 f1 = d_x / vid_conwidth;
501                 f2 = d_y / vid_conheight;
502
503                 if(max(f1, -f1) > max(f2, -f2))
504                 {
505                         if(d_z * f1 > 0)
506                         {
507                                 // RIGHT edge
508                                 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
509                         }
510                         else
511                         {
512                                 // LEFT edge
513                                 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
514                         }
515                 }
516                 else
517                 {
518                         if(d_z * f2 > 0)
519                         {
520                                 // BOTTOM edge
521                                 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
522                         }
523                         else
524                         {
525                                 // TOP edge
526                                 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
527                         }
528                 }
529
530                 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
531         }
532         else
533         {
534 #if 1
535                 ang = M_PI;
536 #else
537                 vector d;
538                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
539                 ang = atan2(-d_x, -d_y);
540 #endif
541         }
542         o_z = 0;
543
544         float edgedistance_min, crosshairdistance;
545                 edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)), 
546         (o_x - (vid_conwidth * waypointsprite_edgeoffset_left)),
547         (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x, 
548         (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y);
549
550         float vidscale;
551         vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
552
553         crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
554
555         t = waypointsprite_scale * vidscale;
556         a *= waypointsprite_alpha;
557
558         {
559                 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
560                 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
561         }
562         if (edgedistance_min < waypointsprite_edgefadedistance) {
563                 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
564                 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
565         }
566         if(crosshairdistance < waypointsprite_crosshairfadedistance) {
567                 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
568                 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
569         }
570
571         if(self.build_finished)
572         {
573                 if(time < self.build_finished + 0.25)
574                 {
575                         if(time < self.build_started)
576                                 self.health = self.build_starthealth;
577                         else if(time < self.build_finished)
578                                 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
579                         else
580                                 self.health = 1;
581                 }
582                 else
583                         self.health = -1;
584         }
585
586         o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
587         
588         string txt;
589         if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
590                 txt = _("Spam");
591         else
592                 txt = spritelookuptext(spriteimage);
593         if(self.helpme && time < self.helpme)
594                 txt = sprintf(_("%s needing help!"), txt);
595         if(autocvar_g_waypointsprite_uppercase)
596                 txt = strtoupper(txt);
597
598         if(self.health >= 0)
599         {
600                 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
601
602                 float align, marg;
603                 if(self.build_finished)
604                         align = 0.5;
605                 else
606                         align = 0;
607                 if(cos(ang) > 0)
608                         marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
609                 else
610                         marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
611                 drawhealthbar(
612                                 o,
613                                 0,
614                                 self.health,
615                                 '0 0 0',
616                                 '0 0 0',
617                                 SPRITE_HEALTHBAR_WIDTH * t,
618                                 SPRITE_HEALTHBAR_HEIGHT * t,
619                                 marg,
620                                 SPRITE_HEALTHBAR_BORDER * t,
621                                 align,
622                                 rgb,
623                                 a * SPRITE_HEALTHBAR_BORDERALPHA,
624                                 rgb,
625                                 a * SPRITE_HEALTHBAR_HEALTHALPHA,
626                                 DRAWFLAG_NORMAL
627                              );
628         }
629         else
630         {
631                 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
632         }
633 }
634
635 void Ent_RemoveWaypointSprite()
636 {
637         if(self.netname)
638                 strunzone(self.netname);
639         if(self.netname2)
640                 strunzone(self.netname2);
641         if(self.netname3)
642                 strunzone(self.netname3);
643 }
644
645 void Ent_WaypointSprite()
646 {
647         float sendflags, f, t;
648         sendflags = ReadByte();
649
650         if(!self.spawntime)
651                 self.spawntime = time;
652
653         self.draw2d = Draw_WaypointSprite;
654
655         InterpolateOrigin_Undo();
656
657         if(sendflags & 0x80)
658         {
659                 t = ReadByte();
660                 if(t < 192)
661                 {
662                         self.health = t / 191.0;
663                         self.build_finished = 0;
664                 }
665                 else
666                 {
667                         t = (t - 192) * 256 + ReadByte();
668                         self.build_started = servertime;
669                         if(self.build_finished)
670                                 self.build_starthealth = bound(0, self.health, 1);
671                         else
672                                 self.build_starthealth = 0;
673                         self.build_finished = servertime + t / 32;
674                 }
675         }
676         else
677         {
678                 self.health = -1;
679                 self.build_finished = 0;
680         }
681
682         if(sendflags & 64)
683         {
684                 // unfortunately, this needs to be exact (for the 3D display)
685                 self.origin_x = ReadCoord();
686                 self.origin_y = ReadCoord();
687                 self.origin_z = ReadCoord();
688         }
689
690         if(sendflags & 1)
691         {
692                 self.team = ReadByte();
693                 self.rule = ReadByte();
694         }
695
696         if(sendflags & 2)
697         {
698                 if(self.netname)
699                         strunzone(self.netname);
700                 self.netname = strzone(ReadString());
701         }
702
703         if(sendflags & 4)
704         {
705                 if(self.netname2)
706                         strunzone(self.netname2);
707                 self.netname2 = strzone(ReadString());
708         }
709
710         if(sendflags & 8)
711         {
712                 if(self.netname3)
713                         strunzone(self.netname3);
714                 self.netname3 = strzone(ReadString());
715         }
716
717         if(sendflags & 16)
718         {
719                 self.lifetime = ReadCoord();
720                 self.fadetime = ReadCoord();
721                 self.maxdistance = ReadShort();
722                 self.hideflags = ReadByte();
723         }
724
725         if(sendflags & 32)
726         {
727                 f = ReadByte();
728                 self.teamradar_icon = (f & 0x7F);
729                 if(f & 0x80)
730                 {
731                         self.(teamradar_times[self.teamradar_time_index]) = time;
732                         self.teamradar_time_index = mod(self.teamradar_time_index + 1, MAX_TEAMRADAR_TIMES);
733                 }
734                 self.teamradar_color_x = ReadByte() / 255.0;
735                 self.teamradar_color_y = ReadByte() / 255.0;
736                 self.teamradar_color_z = ReadByte() / 255.0;
737                 self.helpme = ReadByte() * 0.1;
738                 if(self.helpme > 0)
739                         self.helpme += servertime;
740         }
741
742         InterpolateOrigin_Note();
743
744         self.entremove = Ent_RemoveWaypointSprite;
745 }
746
747 void WaypointSprite_Load_Frames(string ext)
748 {
749         float dh, n, i, o, f;
750         string s, sname, sframes;
751         dh = search_begin(strcat("models/sprites/*_frame*", ext), FALSE, FALSE);
752         if (dh < 0)
753                  return;
754         float ext_len = strlen(ext);
755         n = search_getsize(dh);
756         for(i = 0; i < n; ++i)
757         {
758                 s = search_getfilename(dh, i);
759                 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
760
761                 o = strstrofs(s, "_frame", 0);
762                 sname = strcat("/spriteframes/", substring(s, 0, o));
763                 sframes = substring(s, o + 6, strlen(s) - o - 6);
764                 f = stof(sframes) + 1;
765                 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
766         }
767         search_end(dh);
768 }
769
770 void WaypointSprite_Load()
771 {
772         waypointsprite_fadedistance = vlen(mi_scale);
773         waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
774         waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
775         waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
776         waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
777         waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
778         waypointsprite_scale = autocvar_g_waypointsprite_scale;
779         waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
780         waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
781         waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
782         waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
783         waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
784         waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
785         waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
786         waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
787         waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
788         waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
789         waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
790         waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
791         waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
792         waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
793         waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
794
795         if(!waypointsprite_initialized)
796         {
797                 WaypointSprite_Load_Frames(".tga");
798                 WaypointSprite_Load_Frames(".jpg");
799                 waypointsprite_initialized = true;
800         }
801
802         waypointsprite_count = waypointsprite_newcount;
803         waypointsprite_newcount = 0;
804 }