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