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