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