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