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