]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/waypointsprites.qc
Merge branch 'master' into Mario/vehicles
[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 size   = 9.0 * t;
119         float border = 1.5 * t;
120         float margin = 4.0 * t;
121
122         float borderDiag = border * 1.414;
123         vector arrowX  = eX * size;
124         vector arrowY  = eY * (size+borderDiag);
125         vector borderX = eX * (size+borderDiag);
126         vector borderY = eY * (size+borderDiag+border);
127
128         R_BeginPolygon("", DRAWFLAG_NORMAL);
129         R_PolygonVertex(o,                                  '0 0 0', '0 0 0', a);
130         R_PolygonVertex(o + rotate(arrowY  - borderX, ang), '0 0 0', '0 0 0', a);
131         R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
132         R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
133         R_PolygonVertex(o + rotate(arrowY  + borderX, ang), '0 0 0', '0 0 0', a);
134         R_EndPolygon();
135
136         R_BeginPolygon("", DRAWFLAG_ADDITIVE);
137         R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
138         R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
139         R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
140         R_EndPolygon();
141
142         return o + rotate(eY * (borderDiag+size+margin), ang);
143 }
144
145 // returns location of sprite healthbar
146 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
147 {
148         float algnx, algny;
149         float sw, w, h;
150         float aspect, sa, ca;
151
152         sw = stringwidth(s, FALSE, fontsize);
153         if(sw > minwidth)
154                 w = sw;
155         else
156                 w = minwidth;
157         h = fontsize_y;
158
159         // how do corners work?
160         aspect = vid_conwidth / vid_conheight;
161         sa = sin(ang);
162         ca = cos(ang) * aspect;
163         if(fabs(sa) > fabs(ca))
164         {
165                 algnx = (sa < 0);
166                 algny = 0.5 - 0.5 * ca / fabs(sa);
167         }
168         else
169         {
170                 algnx = 0.5 - 0.5 * sa / fabs(ca);
171                 algny = (ca < 0);
172         }
173
174         // align
175         o_x -= w * algnx;
176         o_y -= h * algny;
177
178         // we want to be onscreen
179         if(o_x < 0)
180                 o_x = 0;
181         if(o_y < 0)
182                 o_y = 0;
183         if(o_x > vid_conwidth - w)
184                 o_x = vid_conwidth - w;
185         if(o_y > vid_conheight - h)
186                 o_x = vid_conheight - h;
187
188         o_x += 0.5 * (w - sw);
189
190         drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
191
192         o_x += 0.5 * sw;
193         o_y += 0.5 * h;
194
195         return o;
196 }
197
198 float spritelookupblinkvalue(string s)
199 {
200         switch(s)
201         {
202                 case "ons-cp-atck-neut": return 2;
203                 case "ons-cp-atck-red":  return 2;
204                 case "ons-cp-atck-blue": return 2;
205                 case "ons-cp-dfnd-red":  return 0.5;
206                 case "ons-cp-dfnd-blue": return 0.5;
207                 case "item-invis":       return 2;
208                 case "item-extralife":   return 2;
209                 case "item-speed":       return 2;
210                 case "item-strength":    return 2;
211                 case "item-shield":      return 2;
212                 case "item-fuelregen":   return 2;
213                 case "item-jetpack":     return 2;
214                 case "tagged-target":    return 2;
215                 default:                 return 1;
216         }
217 }
218 vector spritelookupcolor(string s, vector def)
219 {
220         if(substring(s, 0, 4) == "wpn-")
221                 return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).wpcolor);
222
223         switch(s)
224         {
225                 case "keycarrier-friend": return '0 1 0';
226                 default:                  return def;
227         }
228 }
229 string spritelookuptext(string s)
230 {
231         if(substring(s, 0, 4) == "wpn-") { return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).message); }
232         if(substring(s, 0, 5) == "buff-") { return Buff_PrettyName(Buff_Type_FromSprite(s)); }
233
234         switch(s)
235         {
236                 case "as-push": return _("Push");
237                 case "as-destroy": return _("Destroy");
238                 case "as-defend": return _("Defend");
239                 case "bluebase": return _("Blue base");
240                 case "danger": return _("DANGER");
241                 case "enemyflagcarrier": return _("Enemy carrier");
242                 case "flagcarrier": return _("Flag carrier");
243                 case "flagdropped": return _("Dropped flag");
244                 case "helpme": return _("Help me!");
245                 case "here": return _("Here");
246                 case "key-dropped": return _("Dropped key");
247                 case "keycarrier-blue": return _("Key carrier");
248                 case "keycarrier-finish": return _("Run here");
249                 case "keycarrier-friend": return _("Key carrier");
250                 case "keycarrier-pink": return _("Key carrier");
251                 case "keycarrier-red": return _("Key carrier");
252                 case "keycarrier-yellow": return _("Key carrier");
253                 case "redbase": return _("Red base");
254                 case "waypoint": return _("Waypoint");
255                 case "ons-gen-red": return _("Generator");
256                 case "ons-gen-blue": return _("Generator");
257                 case "ons-gen-shielded": return _("Generator");
258                 case "ons-cp-neut": return _("Control point");
259                 case "ons-cp-red": return _("Control point");
260                 case "ons-cp-blue": return _("Control point");
261                 case "ons-cp-atck-neut": return _("Control point");
262                 case "ons-cp-atck-red": return _("Control point");
263                 case "ons-cp-atck-blue": return _("Control point");
264                 case "ons-cp-dfnd-red": return _("Control point");
265                 case "ons-cp-dfnd-blue": return _("Control point");
266                 case "race-checkpoint": return _("Checkpoint");
267                 case "race-finish": return _("Finish");
268                 case "race-start": return _("Start");
269                 case "race-start-finish": return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
270                 case "goal": return _("Goal");
271                 case "nb-ball": return _("Ball");
272                 case "ka-ball": return _("Ball");
273                 case "ka-ballcarrier": return _("Ball carrier");
274                 case "dom-neut": return _("Control point");
275                 case "dom-red": return _("Control point");
276                 case "dom-blue": return _("Control point");
277                 case "dom-yellow": return _("Control point");
278                 case "dom-pink": return _("Control point");
279                 case "item-invis": return _("Invisibility");
280                 case "item-extralife": return _("Extra life");
281                 case "item-speed": return _("Speed");
282                 case "item-strength": return _("Strength");
283                 case "item-shield": return _("Shield");
284                 case "item-fuelregen": return _("Fuel regen");
285                 case "item-jetpack": return _("Jet Pack");
286                 case "frozen": return _("Frozen!");
287                 case "tagged-target": return _("Tagged");
288                 case "vehicle": return _("Vehicle");
289                 case "intruder": return _("Intruder!");
290                 default: return s;
291         }
292 }
293
294 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
295 {
296         vector yvec = '0.299 0.587 0.114';
297         return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
298 }
299 vector fixrgbexcess(vector rgb)
300 {
301         if(rgb_x > 1)
302         {
303                 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
304                 if(rgb_y > 1)
305                 {
306                         rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
307                         if(rgb_z > 1)
308                                 rgb_z = 1;
309                 }
310                 else if(rgb_z > 1)
311                 {
312                         rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
313                         if(rgb_y > 1)
314                                 rgb_y = 1;
315                 }
316         }
317         else if(rgb_y > 1)
318         {
319                 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
320                 if(rgb_x > 1)
321                 {
322                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
323                         if(rgb_z > 1)
324                                 rgb_z = 1;
325                 }
326                 else if(rgb_z > 1)
327                 {
328                         rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
329                         if(rgb_x > 1)
330                                 rgb_x = 1;
331                 }
332         }
333         else if(rgb_z > 1)
334         {
335                 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
336                 if(rgb_x > 1)
337                 {
338                         rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
339                         if(rgb_y > 1)
340                                 rgb_y = 1;
341                 }
342                 else if(rgb_y > 1)
343                 {
344                         rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
345                         if(rgb_x > 1)
346                                 rgb_x = 1;
347                 }
348         }
349         return rgb;
350 }
351
352 float waypointsprite_count, waypointsprite_newcount;
353 void Draw_WaypointSprite()
354 {
355         string spriteimage;
356         float t;
357
358         if(self.lifetime)
359                 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
360         else
361                 self.alpha = 1;
362
363         if(self.hideflags & 2)
364                 return; // radar only
365
366         if(autocvar_cl_hidewaypoints >= 2)
367                 return;
368
369         if(self.hideflags & 1)
370                 if(autocvar_cl_hidewaypoints)
371                         return; // fixed waypoint
372
373         InterpolateOrigin_Do();
374
375         t = GetPlayerColor(player_localnum) + 1;
376
377         spriteimage = "";
378
379         // choose the sprite
380         switch(self.rule)
381         {
382                 case SPRITERULE_DEFAULT:
383                         if(self.team)
384                         {
385                                 if(self.team == t)
386                                         spriteimage = self.netname;
387                                 else
388                                         spriteimage = "";
389                         }
390                         else
391                                 spriteimage = self.netname;
392                         break;
393                 case SPRITERULE_TEAMPLAY:
394                         if(t == NUM_SPECTATOR + 1)
395                                 spriteimage = self.netname3;
396                         else if(self.team == t)
397                                 spriteimage = self.netname2;
398                         else
399                                 spriteimage = self.netname;
400                         break;
401                 default:
402                         error("Invalid waypointsprite rule!");
403                         break;
404         }
405
406         if(spriteimage == "")
407                 return;
408
409         ++waypointsprite_newcount;
410
411         float dist;
412         dist = vlen(self.origin - view_origin);
413
414         float a;
415         a = self.alpha * autocvar_hud_panel_fg_alpha;
416
417         if(self.maxdistance > waypointsprite_normdistance)
418                 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
419         else if(self.maxdistance > 0)
420                 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
421
422         vector rgb;
423         rgb = self.teamradar_color;
424         rgb = spritelookupcolor(spriteimage, rgb);
425         if(rgb == '0 0 0')
426         {
427                 self.teamradar_color = '1 0 1';
428                 printf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
429         }
430
431         if(time - floor(time) > 0.5)
432         {
433                 if(self.helpme && time < self.helpme)
434                         a *= SPRITE_HELPME_BLINK;
435                 else
436                         a *= spritelookupblinkvalue(spriteimage);
437         }
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
455         || o_x < (vid_conwidth * waypointsprite_edgeoffset_left)
456         || o_y < (vid_conheight * waypointsprite_edgeoffset_top)
457         || o_x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
458         || o_y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
459         {
460                 // scale it to be just in view
461                 vector d;
462                 float f1, f2;
463
464                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
465                 ang = atan2(-d_x, -d_y);
466                 if(o_z < 0)
467                         ang += M_PI;
468
469                 f1 = d_x / vid_conwidth;
470                 f2 = d_y / vid_conheight;
471
472                 if(max(f1, -f1) > max(f2, -f2))
473                 {
474                         if(d_z * f1 > 0)
475                         {
476                                 // RIGHT edge
477                                 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
478                         }
479                         else
480                         {
481                                 // LEFT edge
482                                 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
483                         }
484                 }
485                 else
486                 {
487                         if(d_z * f2 > 0)
488                         {
489                                 // BOTTOM edge
490                                 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
491                         }
492                         else
493                         {
494                                 // TOP edge
495                                 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
496                         }
497                 }
498
499                 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
500         }
501         else
502         {
503 #if 1
504                 ang = M_PI;
505 #else
506                 vector d;
507                 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
508                 ang = atan2(-d_x, -d_y);
509 #endif
510         }
511         o_z = 0;
512
513         float edgedistance_min, crosshairdistance;
514                 edgedistance_min = min((o_y - (vid_conheight * waypointsprite_edgeoffset_top)),
515         (o_x - (vid_conwidth * waypointsprite_edgeoffset_left)),
516         (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o_x,
517         (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o_y);
518
519         float vidscale;
520         vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
521
522         crosshairdistance = sqrt( pow(o_x - vid_conwidth/2, 2) + pow(o_y - vid_conheight/2, 2) );
523
524         t = waypointsprite_scale * vidscale;
525         a *= waypointsprite_alpha;
526
527         {
528                 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
529                 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
530         }
531         if (edgedistance_min < waypointsprite_edgefadedistance) {
532                 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
533                 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
534         }
535         if(crosshairdistance < waypointsprite_crosshairfadedistance) {
536                 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
537                 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
538         }
539
540         if(self.build_finished)
541         {
542                 if(time < self.build_finished + 0.25)
543                 {
544                         if(time < self.build_started)
545                                 self.health = self.build_starthealth;
546                         else if(time < self.build_finished)
547                                 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
548                         else
549                                 self.health = 1;
550                 }
551                 else
552                         self.health = -1;
553         }
554
555         o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
556
557         string txt;
558         if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
559                 txt = _("Spam");
560         else
561                 txt = spritelookuptext(spriteimage);
562         if(self.helpme && time < self.helpme)
563                 txt = sprintf(_("%s needing help!"), txt);
564         if(autocvar_g_waypointsprite_uppercase)
565                 txt = strtoupper(txt);
566
567         draw_beginBoldFont();
568         if(self.health >= 0)
569         {
570                 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
571
572                 float align, marg;
573                 if(self.build_finished)
574                         align = 0.5;
575                 else
576                         align = 0;
577                 if(cos(ang) > 0)
578                         marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
579                 else
580                         marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
581                 drawhealthbar(
582                                 o,
583                                 0,
584                                 self.health,
585                                 '0 0 0',
586                                 '0 0 0',
587                                 SPRITE_HEALTHBAR_WIDTH * t,
588                                 SPRITE_HEALTHBAR_HEIGHT * t,
589                                 marg,
590                                 SPRITE_HEALTHBAR_BORDER * t,
591                                 align,
592                                 rgb,
593                                 a * SPRITE_HEALTHBAR_BORDERALPHA,
594                                 rgb,
595                                 a * SPRITE_HEALTHBAR_HEALTHALPHA,
596                                 DRAWFLAG_NORMAL
597                              );
598         }
599         else
600         {
601                 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
602         }
603         draw_endBoldFont();
604 }
605
606 void Ent_RemoveWaypointSprite()
607 {
608         if(self.netname)
609                 strunzone(self.netname);
610         if(self.netname2)
611                 strunzone(self.netname2);
612         if(self.netname3)
613                 strunzone(self.netname3);
614 }
615
616 void Ent_WaypointSprite()
617 {
618         float sendflags, f, t;
619         sendflags = ReadByte();
620
621         if(!self.spawntime)
622                 self.spawntime = time;
623
624         self.draw2d = Draw_WaypointSprite;
625
626         InterpolateOrigin_Undo();
627         self.iflags |= IFLAG_ORIGIN;
628
629         if(sendflags & 0x80)
630         {
631                 t = ReadByte();
632                 if(t < 192)
633                 {
634                         self.health = t / 191.0;
635                         self.build_finished = 0;
636                 }
637                 else
638                 {
639                         t = (t - 192) * 256 + ReadByte();
640                         self.build_started = servertime;
641                         if(self.build_finished)
642                                 self.build_starthealth = bound(0, self.health, 1);
643                         else
644                                 self.build_starthealth = 0;
645                         self.build_finished = servertime + t / 32;
646                 }
647         }
648         else
649         {
650                 self.health = -1;
651                 self.build_finished = 0;
652         }
653
654         if(sendflags & 64)
655         {
656                 // unfortunately, this needs to be exact (for the 3D display)
657                 self.origin_x = ReadCoord();
658                 self.origin_y = ReadCoord();
659                 self.origin_z = ReadCoord();
660                 setorigin(self, self.origin);
661         }
662
663         if(sendflags & 1)
664         {
665                 self.team = ReadByte();
666                 self.rule = ReadByte();
667         }
668
669         if(sendflags & 2)
670         {
671                 if(self.netname)
672                         strunzone(self.netname);
673                 self.netname = strzone(ReadString());
674         }
675
676         if(sendflags & 4)
677         {
678                 if(self.netname2)
679                         strunzone(self.netname2);
680                 self.netname2 = strzone(ReadString());
681         }
682
683         if(sendflags & 8)
684         {
685                 if(self.netname3)
686                         strunzone(self.netname3);
687                 self.netname3 = strzone(ReadString());
688         }
689
690         if(sendflags & 16)
691         {
692                 self.lifetime = ReadCoord();
693                 self.fadetime = ReadCoord();
694                 self.maxdistance = ReadShort();
695                 self.hideflags = ReadByte();
696         }
697
698         if(sendflags & 32)
699         {
700                 f = ReadByte();
701                 self.teamradar_icon = (f & 0x7F);
702                 if(f & 0x80)
703                 {
704                         self.(teamradar_times[self.teamradar_time_index]) = time;
705                         self.teamradar_time_index = (self.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
706                 }
707                 self.teamradar_color_x = ReadByte() / 255.0;
708                 self.teamradar_color_y = ReadByte() / 255.0;
709                 self.teamradar_color_z = ReadByte() / 255.0;
710                 self.helpme = ReadByte() * 0.1;
711                 if(self.helpme > 0)
712                         self.helpme += servertime;
713         }
714
715         InterpolateOrigin_Note();
716
717         self.entremove = Ent_RemoveWaypointSprite;
718 }
719
720 void WaypointSprite_Load_Frames(string ext)
721 {
722         float dh, n, i, o, f;
723         string s, sname, sframes;
724         dh = search_begin(strcat("models/sprites/*_frame*", ext), FALSE, FALSE);
725         if (dh < 0)
726                  return;
727         float ext_len = strlen(ext);
728         n = search_getsize(dh);
729         for(i = 0; i < n; ++i)
730         {
731                 s = search_getfilename(dh, i);
732                 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
733
734                 o = strstrofs(s, "_frame", 0);
735                 sname = strcat("/spriteframes/", substring(s, 0, o));
736                 sframes = substring(s, o + 6, strlen(s) - o - 6);
737                 f = stof(sframes) + 1;
738                 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
739         }
740         search_end(dh);
741 }
742
743 void WaypointSprite_Load()
744 {
745         waypointsprite_fadedistance = vlen(mi_scale);
746         waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
747         waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
748         waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
749         waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
750         waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
751         waypointsprite_scale = autocvar_g_waypointsprite_scale;
752         waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
753         waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
754         waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
755         waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
756         waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
757         waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
758         waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
759         waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
760         waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
761         waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
762         waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
763         waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
764         waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
765         waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
766         waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
767
768         if(!waypointsprite_initialized)
769         {
770                 WaypointSprite_Load_Frames(".tga");
771                 WaypointSprite_Load_Frames(".jpg");
772                 waypointsprite_initialized = true;
773         }
774
775         waypointsprite_count = waypointsprite_newcount;
776         waypointsprite_newcount = 0;
777 }