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