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