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