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