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