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