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