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