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