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