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