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