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