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