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