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