]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/waypointsprites.qc
Remove if not & fix waypoint sprite
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / waypointsprites.qc
1 float waypointsprite_initialized;
2 float waypointsprite_fadedistance;
3 float waypointsprite_normdistance;
4 float waypointsprite_minscale;
5 float waypointsprite_minalpha;
6 float waypointsprite_distancealphaexponent;
7 float waypointsprite_timealphaexponent;
8 float waypointsprite_scale;
9 float waypointsprite_fontsize;
10 float waypointsprite_edgefadealpha;
11 float waypointsprite_edgefadescale;
12 float waypointsprite_edgefadedistance;
13 float waypointsprite_edgeoffset_bottom;
14 float waypointsprite_edgeoffset_left;
15 float waypointsprite_edgeoffset_right;
16 float waypointsprite_edgeoffset_top;
17 float waypointsprite_crosshairfadealpha;
18 float waypointsprite_crosshairfadescale;
19 float waypointsprite_crosshairfadedistance;
20 float waypointsprite_distancefadealpha;
21 float waypointsprite_distancefadescale;
22 float waypointsprite_distancefadedistance;
23 float waypointsprite_alpha;
24
25 .float helpme;
26 .float rule;
27 .string netname; // primary picture
28 .string netname2; // secondary picture
29 .string netname3; // tertiary picture
30 .float team; // team that gets netname2
31 .float lifetime;
32 .float fadetime;
33 .float maxdistance;
34 .float hideflags;
35 .float spawntime;
36 .float health;
37 .float build_started;
38 .float build_starthealth;
39 .float build_finished;
40
41 const float SPRITE_HEALTHBAR_WIDTH = 144;
42 const float SPRITE_HEALTHBAR_HEIGHT = 9;
43 const float SPRITE_HEALTHBAR_MARGIN = 6;
44 const float SPRITE_HEALTHBAR_BORDER = 2;
45 const float SPRITE_HEALTHBAR_BORDERALPHA = 1;
46 const float SPRITE_HEALTHBAR_HEALTHALPHA = 0.5;
47 const float SPRITE_ARROW_SCALE = 1.0;
48 const float SPRITE_HELPME_BLINK = 2;
49
50 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
51 {
52         vector v1, v2, v3, v4;
53
54         hotspot = -1 * hotspot;
55
56         // hotspot-relative coordinates of the corners
57         v1 = hotspot;
58         v2 = hotspot + '1 0 0' * sz_x;
59         v3 = hotspot + '1 0 0' * sz_x + '0 1 0' * sz_y;
60         v4 = hotspot                  + '0 1 0' * sz_y;
61
62         // rotate them, and make them absolute
63         rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
64         v1 = rotate(v1, rot) + org;
65         v2 = rotate(v2, rot) + org;
66         v3 = rotate(v3, rot) + org;
67         v4 = rotate(v4, rot) + org;
68
69         // draw them
70         R_BeginPolygon(pic, f);
71         R_PolygonVertex(v1, '0 0 0', rgb, a);
72         R_PolygonVertex(v2, '1 0 0', rgb, a);
73         R_PolygonVertex(v3, '1 1 0', rgb, a);
74         R_PolygonVertex(v4, '0 1 0', rgb, a);
75         R_EndPolygon();
76 }
77
78 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
79 {
80         R_BeginPolygon(pic, f);
81         R_PolygonVertex(o, '0 0 0', rgb, a);
82         R_PolygonVertex(o + ri, '1 0 0', rgb, a);
83         R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
84         R_PolygonVertex(o + up, '0 1 0', rgb, a);
85         R_EndPolygon();
86 }
87
88 void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float height, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
89 {
90         vector o, ri, up;
91         float owidth; // outer width
92
93         hotspot = -1 * hotspot;
94
95         // hotspot-relative coordinates of the healthbar corners
96         o = hotspot;
97         ri = '1 0 0';
98         up = '0 1 0';
99
100         rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
101         o = rotate(o, rot) + org;
102         ri = rotate(ri, rot);
103         up = rotate(up, rot);
104
105         owidth = width + 2 * border;
106         o = o - up * (margin + border + height) + ri * (sz_x - owidth) * 0.5;
107
108         drawquad(o - up * border,                               ri * owidth,    up * border, "", rgb,  a,  f);
109         drawquad(o + up * height,                               ri * owidth,    up * border, "", rgb,  a,  f);
110         drawquad(o,                                             ri * border,    up * height, "", rgb,  a,  f);
111         drawquad(o + ri * (owidth - border),                    ri * border,    up * height, "", rgb,  a,  f);
112         drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * height, "", hrgb, ha, f);
113 }
114
115 // returns location of sprite text
116 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
117 {
118         float SQRT2 = 1.414;
119         float BORDER; BORDER = 1.5 * t;
120         float TSIZE; TSIZE = 8 * t;
121         float RLENGTH; RLENGTH = 8 * t;
122         float RWIDTH; RWIDTH = 4 * t;
123         float MLENGTH; MLENGTH = 4 * t;
124
125         R_BeginPolygon("", DRAWFLAG_NORMAL);
126         R_PolygonVertex(o + rotate(eX * -(TSIZE + BORDER * (1 + SQRT2)) + eY * (TSIZE + BORDER), ang), '0 0 0', '0 0 0', a);
127         R_PolygonVertex(o + rotate(eX *  (TSIZE + BORDER * (1 + SQRT2)) + eY * (TSIZE + BORDER), ang), '0 0 0', '0 0 0', a);
128         R_PolygonVertex(o + rotate(eY * -(        BORDER *      SQRT2),                          ang), '0 0 0', '0 0 0', a);
129         R_EndPolygon();
130         R_BeginPolygon("", DRAWFLAG_NORMAL);
131         R_PolygonVertex(o + rotate(eX * -(RWIDTH + BORDER) + eY * (TSIZE           + BORDER), ang), '0 0 0', '0 0 0', a);
132         R_PolygonVertex(o + rotate(eX * -(RWIDTH + BORDER) + eY * (TSIZE + RLENGTH + BORDER), ang), '0 0 0', '0 0 0', a);
133         R_PolygonVertex(o + rotate(eX *  (RWIDTH + BORDER) + eY * (TSIZE + RLENGTH + BORDER), ang), '0 0 0', '0 0 0', a);
134         R_PolygonVertex(o + rotate(eX *  (RWIDTH + BORDER) + eY * (TSIZE           + BORDER), ang), '0 0 0', '0 0 0', a);
135         R_EndPolygon();
136
137         R_BeginPolygon("", DRAWFLAG_ADDITIVE);
138         R_PolygonVertex(o + rotate(eX * -TSIZE + eY * TSIZE, ang), '0 0 0', rgb, a);
139         R_PolygonVertex(o + rotate(eX *  TSIZE + eY * TSIZE, ang), '0 0 0', rgb, a);
140         R_PolygonVertex(o + rotate('0 0 0',                  ang), '0 0 0', rgb, a);
141         R_EndPolygon();
142         R_BeginPolygon("", DRAWFLAG_ADDITIVE);
143         R_PolygonVertex(o + rotate(eX * -RWIDTH + eY *  TSIZE,            ang), '0 0 0', rgb, a);
144         R_PolygonVertex(o + rotate(eX * -RWIDTH + eY * (TSIZE + RLENGTH), ang), '0 0 0', rgb, a);
145         R_PolygonVertex(o + rotate(eX *  RWIDTH + eY * (TSIZE + RLENGTH), ang), '0 0 0', rgb, a);
146         R_PolygonVertex(o + rotate(eX *  RWIDTH + eY *  TSIZE,            ang), '0 0 0', rgb, a);
147         R_EndPolygon();
148
149         return
150                 o + rotate(eY * (TSIZE + RLENGTH + MLENGTH), ang);
151 }
152
153 // returns location of sprite healthbar
154 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
155 {
156         float algnx, algny;
157         float sw, w, h;
158         float aspect, sa, ca;
159
160         sw = stringwidth(s, FALSE, fontsize);
161         if(sw > minwidth)
162                 w = sw;
163         else
164                 w = minwidth;
165         h = fontsize_y;
166
167         // how do corners work?
168         aspect = vid_conwidth / vid_conheight;
169         sa = sin(ang);
170         ca = cos(ang) * aspect;
171         if(fabs(sa) > fabs(ca))
172         {
173                 algnx = (sa < 0);
174                 algny = 0.5 - 0.5 * ca / fabs(sa);
175         }
176         else
177         {
178                 algnx = 0.5 - 0.5 * sa / fabs(ca);
179                 algny = (ca < 0);
180         }
181
182         // align
183         o_x -= w * algnx;
184         o_y -= h * algny;
185
186         // we want to be onscreen
187         if(o_x < 0)
188                 o_x = 0;
189         if(o_y < 0)
190                 o_y = 0;
191         if(o_x > vid_conwidth - w)
192                 o_x = vid_conwidth - w;
193         if(o_y > vid_conheight - h)
194                 o_x = vid_conheight - h;
195
196         o_x += 0.5 * (w - sw);
197
198         drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
199
200         o_x += 0.5 * sw;
201         o_y += 0.5 * h;
202
203         return o;
204 }
205
206 float spritelookupblinkvalue(string s)
207 {
208         switch(s)
209         {
210                 case "ons-cp-atck-neut": return 2;
211                 case "ons-cp-atck-red":  return 2;
212                 case "ons-cp-atck-blue": return 2;
213                 case "ons-cp-dfnd-red":  return 0.5;
214                 case "ons-cp-dfnd-blue": return 0.5;
215                 case "item-invis":       return 2;
216                 case "item-extralife":   return 2;
217                 case "item-speed":       return 2;
218                 case "item-strength":    return 2;
219                 case "item-shield":      return 2;
220                 case "item-fuelregen":   return 2;
221                 case "item-jetpack":     return 2;
222                 case "tagged-target":    return 2;
223                 default:                 return 1;
224         }
225 }
226 vector spritelookupcolor(string s, vector def)
227 {
228         switch(s)
229         {
230                 case "keycarrier-friend": return '0 1 0';
231                 case "wpn-laser":         return '1 0.5 0.5';
232                 case "wpn-shotgun":       return '0.5 0.25 0';
233                 case "wpn-uzi":           return '1 1 0';
234                 case "wpn-gl":            return '1 0 0';
235                 case "wpn-electro":       return '0 0.5 1';
236                 case "wpn-crylink":       return '1 0.5 1';
237                 case "wpn-nex":           return '0.5 1 1';
238                 case "wpn-hagar":         return '1 1 0.5';
239                 case "wpn-rl":            return '1 1 0';
240                 case "wpn-porto":         return '0.5 0.5 0.5';
241                 case "wpn-minstanex":     return '0.5 1 1';
242                 case "wpn-hookgun":       return '0 0.5 0';
243                 case "wpn-fireball":      return '1 0.5 0';
244                 case "wpn-hlac":          return '0 1 0';
245                 case "wpn-campingrifle":  return '0.5 1 0';
246                 case "wpn-minelayer":     return '0.75 1 0';
247                 default:                  return def;
248         }
249 }
250 string spritelookuptext(string s)
251 {
252         switch(s)
253         {
254                 case "as-push": return _("Push");
255                 case "as-destroy": return _("Destroy");
256                 case "as-defend": return _("Defend");
257                 case "bluebase": return _("Blue base");
258                 case "danger": return _("DANGER");
259                 case "enemyflagcarrier": return _("Enemy carrier");
260                 case "flagcarrier": return _("Flag carrier");
261                 case "flagdropped": return _("Dropped flag");
262                 case "helpme": return _("Help me!");
263                 case "here": return _("Here");
264                 case "key-dropped": return _("Dropped key");
265                 case "keycarrier-blue": return _("Key carrier");
266                 case "keycarrier-finish": return _("Run here");
267                 case "keycarrier-friend": return _("Key carrier");
268                 case "keycarrier-pink": return _("Key carrier");
269                 case "keycarrier-red": return _("Key carrier");
270                 case "keycarrier-yellow": return _("Key carrier");
271                 case "redbase": return _("Red base");
272                 case "waypoint": return _("Waypoint");
273                 case "ons-gen-red": return _("Generator");
274                 case "ons-gen-blue": return _("Generator");
275                 case "ons-gen-shielded": return _("Generator");
276                 case "ons-cp-neut": return _("Control point");
277                 case "ons-cp-red": return _("Control point");
278                 case "ons-cp-blue": return _("Control point");
279                 case "ons-cp-atck-neut": return _("Control point");
280                 case "ons-cp-atck-red": return _("Control point");
281                 case "ons-cp-atck-blue": return _("Control point");
282                 case "ons-cp-dfnd-red": return _("Control point");
283                 case "ons-cp-dfnd-blue": return _("Control point");
284                 case "race-checkpoint": return _("Checkpoint");
285                 case "race-finish": return _("Finish");
286                 case "race-start": return _("Start");
287                 case "race-start-finish": return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
288                 case "nb-ball": return _("Ball");
289                 case "ka-ball": return _("Ball");
290                 case "ka-ballcarrier": return _("Ball carrier");
291                 case "wpn-laser": return _("Laser");
292                 case "wpn-shotgun": return _("Shotgun");
293                 case "wpn-uzi": return _("Machine Gun");
294                 case "wpn-gl": return _("Mortar");
295                 case "wpn-electro": return _("Electro");
296                 case "wpn-crylink": return _("Crylink");
297                 case "wpn-nex": return _("Nex");
298                 case "wpn-hagar": return _("Hagar");
299                 case "wpn-rl": return _("Rocket Launcher");
300                 case "wpn-porto": return _("Port-O-Launch");
301                 case "wpn-minstanex": return _("Minstanex");
302                 case "wpn-hookgun": return _("Hook");
303                 case "wpn-fireball": return _("Fireball");
304                 case "wpn-hlac": return _("HLAC");
305                 case "wpn-campingrifle": return _("Rifle");
306                 case "wpn-minelayer": return _("Mine Layer");
307                 case "dom-neut": return _("Control point");
308                 case "dom-red": return _("Control point");
309                 case "dom-blue": return _("Control point");
310                 case "dom-yellow": return _("Control point");
311                 case "dom-pink": return _("Control point");
312                 case "item-invis": return _("Invisibility");
313                 case "item-extralife": return _("Extra life");
314                 case "item-speed": return _("Speed");
315                 case "item-strength": return _("Strength");
316                 case "item-shield": return _("Shield");
317                 case "item-fuelregen": return _("Fuel regen");
318                 case "item-jetpack": return _("Jet Pack");
319                 case "freezetag_frozen": return _("Frozen!");
320                 case "tagged-target": return _("Tagged");
321                 case "vehicle": return _("Vehicle");
322                 case "intruder": return _("Intruder!");
323                 default: return s;
324         }
325 }
326
327 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
328 {
329         vector yvec = '0.299 0.587 0.114';
330         return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
331 }
332 vector fixrgbexcess(vector rgb)
333 {
334         if(rgb_x > 1)
335         {
336                 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
337                 if(rgb_y > 1)
338                 {
339                         rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
340                         if(rgb_z > 1)
341                                 rgb_z = 1;
342                 }
343                 else if(rgb_z > 1)
344                 {
345                         rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
346                         if(rgb_y > 1)
347                                 rgb_y = 1;
348                 }
349         }
350         else if(rgb_y > 1)
351         {
352                 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
353                 if(rgb_x > 1)
354                 {
355                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
356                         if(rgb_z > 1)
357                                 rgb_z = 1;
358                 }
359                 else if(rgb_z > 1)
360                 {
361                         rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
362                         if(rgb_x > 1)
363                                 rgb_x = 1;
364                 }
365         }
366         else if(rgb_z > 1)
367         {
368                 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
369                 if(rgb_x > 1)
370                 {
371                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
372                         if(rgb_y > 1)
373                                 rgb_y = 1;
374                 }
375                 else if(rgb_y > 1)
376                 {
377                         rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
378                         if(rgb_x > 1)
379                                 rgb_x = 1;
380                 }
381         }
382         return rgb;
383 }
384
385 float waypointsprite_count, waypointsprite_newcount;
386 void Draw_WaypointSprite()
387 {
388         string spriteimage;
389         float t;
390
391         if(self.lifetime)
392                 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
393         else
394                 self.alpha = 1;
395
396         if(self.hideflags & 2)
397                 return; // radar only
398
399         if(autocvar_cl_hidewaypoints >= 2)
400                 return;
401
402         if(self.hideflags & 1)
403                 if(autocvar_cl_hidewaypoints)
404                         return; // fixed waypoint
405
406         InterpolateOrigin_Do();
407
408         t = GetPlayerColor(player_localnum) + 1;
409
410         spriteimage = "";
411
412         // choose the sprite
413         switch(self.rule)
414         {
415                 case SPRITERULE_DEFAULT:
416                         if(self.team)
417                         {
418                                 if(self.team == t)
419                                         spriteimage = self.netname;
420                                 else
421                                         spriteimage = "";
422                         }
423                         else
424                                 spriteimage = self.netname;
425                         break;
426                 case SPRITERULE_TEAMPLAY:
427                         if(t == NUM_SPECTATOR + 1)
428                                 spriteimage = self.netname3;
429                         else if(self.team == t)
430                                 spriteimage = self.netname2;
431                         else
432                                 spriteimage = self.netname;
433                         break;
434                 default:
435                         error("Invalid waypointsprite rule!");
436                         break;
437         }
438
439         if(spriteimage == "")
440                 return;
441
442         ++waypointsprite_newcount;
443
444         float dist;
445         dist = vlen(self.origin - view_origin);
446
447         float a;
448         a = self.alpha * autocvar_hud_panel_fg_alpha;
449
450         if(self.maxdistance > waypointsprite_normdistance)
451                 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
452         else if(self.maxdistance > 0)
453                 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
454
455         vector rgb;
456         rgb = self.teamradar_color;
457         rgb = spritelookupcolor(spriteimage, rgb);
458         if(rgb == '0 0 0')
459         {
460                 self.teamradar_color = '1 0 1';
461                 print(sprintf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage));
462         }
463
464         if(time - floor(time) > 0.5)
465         {
466                 if(self.helpme && time < self.helpme)
467                         a *= SPRITE_HELPME_BLINK;
468                 else
469                         a *= spritelookupblinkvalue(spriteimage);
470         }
471
472         if(a > 1)
473         {
474                 rgb *= a;
475                 a = 1;
476         }
477
478         if(a <= 0)
479                 return;
480
481         rgb = fixrgbexcess(rgb);
482
483         vector o;
484         float ang;
485
486         o = project_3d_to_2d(self.origin);
487         if(o_z < 0
488         || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
489         || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
490         || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
491         || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
492         {
493                 // scale it to be just in view
494                 vector d;
495                 float f1, f2;
496
497                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
498                 ang = atan2(-d_x, -d_y);
499                 if(o_z < 0)
500                         ang += M_PI;
501
502                 f1 = d_x / vid_conwidth;
503                 f2 = d_y / vid_conheight;
504
505                 if(max(f1, -f1) > max(f2, -f2))
506                 {
507                         if(d_z * f1 > 0)
508                         {
509                                 // RIGHT edge
510                                 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
511                         }
512                         else
513                         {
514                                 // LEFT edge
515                                 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
516                         }
517                 }
518                 else
519                 {
520                         if(d_z * f2 > 0)
521                         {
522                                 // BOTTOM edge
523                                 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
524                         }
525                         else
526                         {
527                                 // TOP edge
528                                 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
529                         }
530                 }
531
532                 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
533         }
534         else
535         {
536 #if 1
537                 ang = M_PI;
538 #else
539                 vector d;
540                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
541                 ang = atan2(-d_x, -d_y);
542 #endif
543         }
544         o_z = 0;
545
546         float edgedistance_min, crosshairdistance;
547                 edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)),
548         (o_x - (vid_conwidth * waypointsprite_edgeoffset_left)),
549         (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x,
550         (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y);
551
552         float vidscale;
553         vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
554
555         crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
556
557         t = waypointsprite_scale * vidscale;
558         a *= waypointsprite_alpha;
559
560         {
561                 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
562                 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
563         }
564         if (edgedistance_min < waypointsprite_edgefadedistance) {
565                 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
566                 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
567         }
568         if(crosshairdistance < waypointsprite_crosshairfadedistance) {
569                 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
570                 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
571         }
572
573         if(self.build_finished)
574         {
575                 if(time < self.build_finished + 0.25)
576                 {
577                         if(time < self.build_started)
578                                 self.health = self.build_starthealth;
579                         else if(time < self.build_finished)
580                                 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
581                         else
582                                 self.health = 1;
583                 }
584                 else
585                         self.health = -1;
586         }
587
588         o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
589
590         string txt;
591         if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
592                 txt = _("Spam");
593         else
594                 txt = spritelookuptext(spriteimage);
595         if(self.helpme && time < self.helpme)
596                 txt = sprintf(_("%s needing help!"), txt);
597         if(autocvar_g_waypointsprite_uppercase)
598                 txt = strtoupper(txt);
599
600         draw_beginBoldFont();
601         if(self.health >= 0)
602         {
603                 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
604
605                 float align, marg;
606                 if(self.build_finished)
607                         align = 0.5;
608                 else
609                         align = 0;
610                 if(cos(ang) > 0)
611                         marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
612                 else
613                         marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
614                 drawhealthbar(
615                                 o,
616                                 0,
617                                 self.health,
618                                 '0 0 0',
619                                 '0 0 0',
620                                 SPRITE_HEALTHBAR_WIDTH * t,
621                                 SPRITE_HEALTHBAR_HEIGHT * t,
622                                 marg,
623                                 SPRITE_HEALTHBAR_BORDER * t,
624                                 align,
625                                 rgb,
626                                 a * SPRITE_HEALTHBAR_BORDERALPHA,
627                                 rgb,
628                                 a * SPRITE_HEALTHBAR_HEALTHALPHA,
629                                 DRAWFLAG_NORMAL
630                              );
631         }
632         else
633         {
634                 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
635         }
636         draw_endBoldFont();
637 }
638
639 void Ent_RemoveWaypointSprite()
640 {
641         if(self.netname)
642                 strunzone(self.netname);
643         if(self.netname2)
644                 strunzone(self.netname2);
645         if(self.netname3)
646                 strunzone(self.netname3);
647 }
648
649 void Ent_WaypointSprite()
650 {
651         float sendflags, f, t;
652         sendflags = ReadByte();
653
654         if(!self.spawntime)
655                 self.spawntime = time;
656
657         self.draw2d = Draw_WaypointSprite;
658
659         InterpolateOrigin_Undo();
660         self.iflags |= IFLAG_ORIGIN;
661
662         if(sendflags & 0x80)
663         {
664                 t = ReadByte();
665                 if(t < 192)
666                 {
667                         self.health = t / 191.0;
668                         self.build_finished = 0;
669                 }
670                 else
671                 {
672                         t = (t - 192) * 256 + ReadByte();
673                         self.build_started = servertime;
674                         if(self.build_finished)
675                                 self.build_starthealth = bound(0, self.health, 1);
676                         else
677                                 self.build_starthealth = 0;
678                         self.build_finished = servertime + t / 32;
679                 }
680         }
681         else
682         {
683                 self.health = -1;
684                 self.build_finished = 0;
685         }
686
687         if(sendflags & 64)
688         {
689                 // unfortunately, this needs to be exact (for the 3D display)
690                 self.origin_x = ReadCoord();
691                 self.origin_y = ReadCoord();
692                 self.origin_z = ReadCoord();
693                 setorigin(self, self.origin);
694         }
695
696         if(sendflags & 1)
697         {
698                 self.team = ReadByte();
699                 self.rule = ReadByte();
700         }
701
702         if(sendflags & 2)
703         {
704                 if(self.netname)
705                         strunzone(self.netname);
706                 self.netname = strzone(ReadString());
707         }
708
709         if(sendflags & 4)
710         {
711                 if(self.netname2)
712                         strunzone(self.netname2);
713                 self.netname2 = strzone(ReadString());
714         }
715
716         if(sendflags & 8)
717         {
718                 if(self.netname3)
719                         strunzone(self.netname3);
720                 self.netname3 = strzone(ReadString());
721         }
722
723         if(sendflags & 16)
724         {
725                 self.lifetime = ReadCoord();
726                 self.fadetime = ReadCoord();
727                 self.maxdistance = ReadShort();
728                 self.hideflags = ReadByte();
729         }
730
731         if(sendflags & 32)
732         {
733                 f = ReadByte();
734                 self.teamradar_icon = (f & 0x7F);
735                 if(f & 0x80)
736                 {
737                         self.(teamradar_times[self.teamradar_time_index]) = time;
738                         self.teamradar_time_index = mod(self.teamradar_time_index + 1, MAX_TEAMRADAR_TIMES);
739                 }
740                 self.teamradar_color_x = ReadByte() / 255.0;
741                 self.teamradar_color_y = ReadByte() / 255.0;
742                 self.teamradar_color_z = ReadByte() / 255.0;
743                 self.helpme = ReadByte() * 0.1;
744                 if(self.helpme > 0)
745                         self.helpme += servertime;
746         }
747
748         InterpolateOrigin_Note();
749
750         self.entremove = Ent_RemoveWaypointSprite;
751 }
752
753 void WaypointSprite_Load_Frames(string ext)
754 {
755         float dh, n, i, o, f;
756         string s, sname, sframes;
757         dh = search_begin(strcat("models/sprites/*_frame*", ext), FALSE, FALSE);
758         if (dh < 0)
759                  return;
760         float ext_len = strlen(ext);
761         n = search_getsize(dh);
762         for(i = 0; i < n; ++i)
763         {
764                 s = search_getfilename(dh, i);
765                 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
766
767                 o = strstrofs(s, "_frame", 0);
768                 sname = strcat("/spriteframes/", substring(s, 0, o));
769                 sframes = substring(s, o + 6, strlen(s) - o - 6);
770                 f = stof(sframes) + 1;
771                 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
772         }
773         search_end(dh);
774 }
775
776 void WaypointSprite_Load()
777 {
778         waypointsprite_fadedistance = vlen(mi_scale);
779         waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
780         waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
781         waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
782         waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
783         waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
784         waypointsprite_scale = autocvar_g_waypointsprite_scale;
785         waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
786         waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
787         waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
788         waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
789         waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
790         waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
791         waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
792         waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
793         waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
794         waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
795         waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
796         waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
797         waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
798         waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
799         waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
800
801         if(!waypointsprite_initialized)
802         {
803                 WaypointSprite_Load_Frames(".tga");
804                 WaypointSprite_Load_Frames(".jpg");
805                 waypointsprite_initialized = true;
806         }
807
808         waypointsprite_count = waypointsprite_newcount;
809         waypointsprite_newcount = 0;
810 }