1 #include "waypointsprites.qh"
5 REGISTER_MUTATOR(waypointsprites, true);
7 REGISTER_NET_LINKED(waypointsprites)
10 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
11 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
13 WriteHeader(MSG_ENTITY, waypointsprites);
15 sendflags = sendflags & 0x7F;
17 if (self.max_health || (self.pain_finished && (time < self.pain_finished + 0.25)))
23 if(self.exteriormodeltoclient == to)
26 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
28 WriteByte(MSG_ENTITY, sendflags);
29 WriteByte(MSG_ENTITY, self.wp_extra);
35 WriteByte(MSG_ENTITY, (self.health / self.max_health) * 191.0);
39 float dt = self.pain_finished - time;
40 dt = bound(0, dt * 32, 16383);
41 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
42 WriteByte(MSG_ENTITY, (dt & 0x00FF));
48 WriteCoord(MSG_ENTITY, self.origin.x);
49 WriteCoord(MSG_ENTITY, self.origin.y);
50 WriteCoord(MSG_ENTITY, self.origin.z);
55 WriteByte(MSG_ENTITY, self.team);
56 WriteByte(MSG_ENTITY, self.rule);
60 WriteString(MSG_ENTITY, self.model1);
63 WriteString(MSG_ENTITY, self.model2);
66 WriteString(MSG_ENTITY, self.model3);
70 WriteCoord(MSG_ENTITY, self.fade_time);
71 WriteCoord(MSG_ENTITY, self.teleport_time);
72 WriteShort(MSG_ENTITY, self.fade_rate); // maxdist
73 WriteByte(MSG_ENTITY, f);
78 WriteByte(MSG_ENTITY, self.cnt); // icon on radar
79 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
80 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
81 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
83 if (WaypointSprite_isteammate(self.owner, WaypointSprite_getviewentity(to)))
85 float dt = (self.waypointsprite_helpmetime - time) / 0.1;
90 WriteByte(MSG_ENTITY, dt);
93 WriteByte(MSG_ENTITY, 0);
101 void Ent_WaypointSprite();
102 NET_HANDLE(waypointsprites, bool isnew) {
103 Ent_WaypointSprite();
107 void Ent_RemoveWaypointSprite()
109 if (self.netname) strunzone(self.netname);
110 if (self.netname2) strunzone(self.netname2);
111 if (self.netname3) strunzone(self.netname3);
114 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
115 void Ent_WaypointSprite()
117 int sendflags = ReadByte();
118 self.wp_extra = ReadByte();
121 self.spawntime = time;
123 self.draw2d = Draw_WaypointSprite;
125 InterpolateOrigin_Undo(self);
126 self.iflags |= IFLAG_ORIGIN;
128 if (sendflags & 0x80)
133 self.health = t / 191.0;
134 self.build_finished = 0;
138 t = (t - 192) * 256 + ReadByte();
139 self.build_started = servertime;
140 if (self.build_finished)
141 self.build_starthealth = bound(0, self.health, 1);
143 self.build_starthealth = 0;
144 self.build_finished = servertime + t / 32;
150 self.build_finished = 0;
155 // unfortunately, this needs to be exact (for the 3D display)
156 self.origin_x = ReadCoord();
157 self.origin_y = ReadCoord();
158 self.origin_z = ReadCoord();
159 setorigin(self, self.origin);
164 self.team = ReadByte();
165 self.rule = ReadByte();
171 strunzone(self.netname);
172 self.netname = strzone(ReadString());
178 strunzone(self.netname2);
179 self.netname2 = strzone(ReadString());
185 strunzone(self.netname3);
186 self.netname3 = strzone(ReadString());
191 self.lifetime = ReadCoord();
192 self.fadetime = ReadCoord();
193 self.maxdistance = ReadShort();
194 self.hideflags = ReadByte();
200 self.teamradar_icon = f & BITS(7);
203 self.(teamradar_times[self.teamradar_time_index]) = time;
204 self.teamradar_time_index = (self.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
206 self.teamradar_color_x = ReadByte() / 255.0;
207 self.teamradar_color_y = ReadByte() / 255.0;
208 self.teamradar_color_z = ReadByte() / 255.0;
209 self.helpme = ReadByte() * 0.1;
211 self.helpme += servertime;
214 InterpolateOrigin_Note(this);
216 self.entremove = Ent_RemoveWaypointSprite;
221 float spritelookupblinkvalue(string s)
223 if (s == WP_Weapon.netname) {
224 if (get_weaponinfo(self.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
227 if (s == WP_Item.netname) return Items_from(self.wp_extra).m_waypointblink;
232 vector spritelookupcolor(entity this, string s, vector def)
234 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return get_weaponinfo(this.wp_extra).wpcolor;
235 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
236 if (MUTATOR_CALLHOOK(WP_Format, this, s))
238 return MUTATOR_ARGV(0, vector);
243 string spritelookuptext(string s)
245 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
246 if (s == WP_Weapon.netname) return get_weaponinfo(self.wp_extra).m_name;
247 if (s == WP_Item.netname) return Items_from(self.wp_extra).m_waypoint;
248 if (s == WP_Monster.netname) return get_monsterinfo(self.wp_extra).monster_name;
249 if (MUTATOR_CALLHOOK(WP_Format, this, s))
251 return MUTATOR_ARGV(0, string);
254 // need to loop, as our netname could be one of three
255 FOREACH(Waypoints, it.netname == s, LAMBDA(
264 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
266 vector v1, v2, v3, v4;
268 hotspot = -1 * hotspot;
270 // hotspot-relative coordinates of the corners
272 v2 = hotspot + '1 0 0' * sz.x;
273 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
274 v4 = hotspot + '0 1 0' * sz.y;
276 // rotate them, and make them absolute
277 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
278 v1 = rotate(v1, rot) + org;
279 v2 = rotate(v2, rot) + org;
280 v3 = rotate(v3, rot) + org;
281 v4 = rotate(v4, rot) + org;
284 R_BeginPolygon(pic, f);
285 R_PolygonVertex(v1, '0 0 0', rgb, a);
286 R_PolygonVertex(v2, '1 0 0', rgb, a);
287 R_PolygonVertex(v3, '1 1 0', rgb, a);
288 R_PolygonVertex(v4, '0 1 0', rgb, a);
292 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
294 R_BeginPolygon(pic, f);
295 R_PolygonVertex(o, '0 0 0', rgb, a);
296 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
297 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
298 R_PolygonVertex(o + up, '0 1 0', rgb, a);
302 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)
305 float owidth; // outer width
307 hotspot = -1 * hotspot;
309 // hotspot-relative coordinates of the healthbar corners
314 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
315 o = rotate(o, rot) + org;
316 ri = rotate(ri, rot);
317 up = rotate(up, rot);
319 owidth = width + 2 * border;
320 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
322 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
323 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
324 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
325 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
326 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
329 // returns location of sprite text
330 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
332 float size = 9.0 * t;
333 float border = 1.5 * t;
334 float margin = 4.0 * t;
336 float borderDiag = border * 1.414;
337 vector arrowX = eX * size;
338 vector arrowY = eY * (size+borderDiag);
339 vector borderX = eX * (size+borderDiag);
340 vector borderY = eY * (size+borderDiag+border);
342 R_BeginPolygon("", DRAWFLAG_NORMAL);
343 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
344 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
345 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
346 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
347 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
350 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
351 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
352 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
353 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
356 return o + rotate(eY * (borderDiag+size+margin), ang);
359 // returns location of sprite healthbar
360 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
364 float aspect, sa, ca;
366 sw = stringwidth(s, false, fontsize);
373 // how do corners work?
374 aspect = vid_conwidth / vid_conheight;
376 ca = cos(ang) * aspect;
377 if (fabs(sa) > fabs(ca))
381 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
386 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
394 // we want to be onscreen
399 if (o.x > vid_conwidth - w)
400 o.x = vid_conwidth - w;
401 if (o.y > vid_conheight - h)
402 o.x = vid_conheight - h;
404 o.x += 0.5 * (w - sw);
406 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
414 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
416 vector yvec = '0.299 0.587 0.114';
417 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
420 vector fixrgbexcess(vector rgb)
423 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
425 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
426 if (rgb.z > 1) rgb.z = 1;
427 } else if (rgb.z > 1) {
428 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
429 if (rgb.y > 1) rgb.y = 1;
431 } else if (rgb.y > 1) {
432 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
434 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
435 if (rgb.z > 1) rgb.z = 1;
436 } else if (rgb.z > 1) {
437 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
438 if (rgb.x > 1) rgb.x = 1;
440 } else if (rgb.z > 1) {
441 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
443 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
444 if (rgb.y > 1) rgb.y = 1;
445 } else if (rgb.y > 1) {
446 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
447 if (rgb.x > 1) rgb.x = 1;
453 void Draw_WaypointSprite(entity this)
456 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
460 if (self.hideflags & 2)
461 return; // radar only
463 if (autocvar_cl_hidewaypoints >= 2)
466 if (self.hideflags & 1)
467 if (autocvar_cl_hidewaypoints)
468 return; // fixed waypoint
470 InterpolateOrigin_Do(self);
472 float t = entcs_GetTeam(player_localnum) + 1;
474 string spriteimage = "";
479 case SPRITERULE_SPECTATOR:
481 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
482 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
485 spriteimage = self.netname;
487 case SPRITERULE_DEFAULT:
491 spriteimage = self.netname;
496 spriteimage = self.netname;
498 case SPRITERULE_TEAMPLAY:
499 if (t == NUM_SPECTATOR + 1)
500 spriteimage = self.netname3;
501 else if (self.team == t)
502 spriteimage = self.netname2;
504 spriteimage = self.netname;
507 error("Invalid waypointsprite rule!");
511 if (spriteimage == "")
514 ++waypointsprite_newcount;
517 dist = vlen(self.origin - view_origin);
520 a = self.alpha * autocvar_hud_panel_fg_alpha;
522 if (self.maxdistance > waypointsprite_normdistance)
523 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
524 else if (self.maxdistance > 0)
525 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
527 vector rgb = spritelookupcolor(self, spriteimage, self.teamradar_color);
530 self.teamradar_color = '1 0 1';
531 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
534 if (time - floor(time) > 0.5)
536 if (self.helpme && time < self.helpme)
537 a *= SPRITE_HELPME_BLINK;
538 else if (!self.lifetime) // fading out waypoints don't blink
539 a *= spritelookupblinkvalue(spriteimage);
551 rgb = fixrgbexcess(rgb);
556 o = project_3d_to_2d(self.origin);
558 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
559 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
560 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
561 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
563 // scale it to be just in view
567 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
568 ang = atan2(-d.x, -d.y);
572 f1 = d.x / vid_conwidth;
573 f2 = d.y / vid_conheight;
575 if (max(f1, -f1) > max(f2, -f2)) {
578 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
581 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
586 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
589 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
593 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
601 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
602 ang = atan2(-d.x, -d.y);
607 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
608 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
609 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
610 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
612 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
614 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
616 t = waypointsprite_scale * vidscale;
617 a *= waypointsprite_alpha;
620 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
621 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
623 if (edgedistance_min < waypointsprite_edgefadedistance) {
624 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
625 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
627 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
628 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
629 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
632 if (self.build_finished)
634 if (time < self.build_finished + 0.25)
636 if (time < self.build_started)
637 self.health = self.build_starthealth;
638 else if (time < self.build_finished)
639 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
647 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
650 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
653 txt = spritelookuptext(spriteimage);
654 if (self.helpme && time < self.helpme)
655 txt = sprintf(_("%s needing help!"), txt);
656 if (autocvar_g_waypointsprite_uppercase)
657 txt = strtoupper(txt);
659 draw_beginBoldFont();
660 if (self.health >= 0)
662 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
665 if (self.build_finished)
670 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
672 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
679 SPRITE_HEALTHBAR_WIDTH * t,
680 SPRITE_HEALTHBAR_HEIGHT * t,
682 SPRITE_HEALTHBAR_BORDER * t,
685 a * SPRITE_HEALTHBAR_BORDERALPHA,
687 a * SPRITE_HEALTHBAR_HEALTHALPHA,
693 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
698 void WaypointSprite_Load_Frames(string ext)
700 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
702 int ext_len = strlen(ext);
703 int n = search_getsize(dh);
704 for (int i = 0; i < n; ++i)
706 string s = search_getfilename(dh, i);
707 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
709 int o = strstrofs(s, "_frame", 0);
710 string sname = strcat("/spriteframes/", substring(s, 0, o));
711 string sframes = substring(s, o + 6, strlen(s) - o - 6);
712 int f = stof(sframes) + 1;
713 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
718 void WaypointSprite_Load();
719 STATIC_INIT(WaypointSprite_Load) {
720 WaypointSprite_Load();
721 WaypointSprite_Load_Frames(".tga");
722 WaypointSprite_Load_Frames(".jpg");
724 void WaypointSprite_Load()
726 waypointsprite_fadedistance = vlen(mi_scale);
727 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
728 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
729 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
730 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
731 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
732 waypointsprite_scale = autocvar_g_waypointsprite_scale;
733 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
734 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
735 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
736 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
737 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
738 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
739 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
740 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
741 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
742 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
743 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
744 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
745 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
746 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
747 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
749 waypointsprite_count = waypointsprite_newcount;
750 waypointsprite_newcount = 0;
755 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
757 string m1 = _m1.netname;
758 string m2 = _m2.netname;
759 string m3 = _m3.netname;
777 void WaypointSprite_UpdateHealth(entity e, float f)
779 f = bound(0, f, e.max_health);
780 if (f != e.health || e.pain_finished)
788 void WaypointSprite_UpdateMaxHealth(entity e, float f)
790 if (f != e.max_health || e.pain_finished)
798 void WaypointSprite_UpdateBuildFinished(entity e, float f)
800 if (f != e.pain_finished || e.max_health)
808 void WaypointSprite_UpdateOrigin(entity e, vector o)
817 void WaypointSprite_UpdateRule(entity e, float t, float r)
819 // no check, as this is never called without doing an actual change (usually only once)
825 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
827 // no check, as this is never called without doing an actual change (usually only once)
829 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
834 void WaypointSprite_Ping(entity e)
837 if (time < e.waypointsprite_pingtime) return;
838 e.waypointsprite_pingtime = time + 0.3;
839 // ALWAYS sends (this causes a radar circle), thus no check
844 void WaypointSprite_HelpMePing(entity e)
846 WaypointSprite_Ping(e);
847 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
851 void WaypointSprite_FadeOutIn(entity e, float t)
856 e.teleport_time = time + t;
858 else if (t < (e.teleport_time - time))
860 // accelerate the waypoint's dying
862 // (e.teleport_time - time) / wp.fade_time stays
863 // e.teleport_time = time + fadetime
864 float current_fadetime;
865 current_fadetime = e.teleport_time - time;
866 e.teleport_time = time + t;
867 e.fade_time = e.fade_time * t / current_fadetime;
873 void WaypointSprite_Init()
875 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
876 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
877 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
880 void WaypointSprite_InitClient(entity e)
884 void WaypointSprite_Kill(entity wp)
887 if (wp.owner) wp.owner.(wp.owned_by_field) = world;
891 void WaypointSprite_Disown(entity wp, float fadetime)
894 if (wp.classname != "sprite_waypoint")
896 backtrace("Trying to disown a non-waypointsprite");
901 if (wp.exteriormodeltoclient == wp.owner)
902 wp.exteriormodeltoclient = world;
903 wp.owner.(wp.owned_by_field) = world;
906 WaypointSprite_FadeOutIn(wp, fadetime);
910 void WaypointSprite_Think()
912 bool doremove = false;
914 if (self.fade_time && time >= self.teleport_time)
919 if (self.exteriormodeltoclient)
920 WaypointSprite_UpdateOrigin(self, self.exteriormodeltoclient.origin + self.view_ofs);
923 WaypointSprite_Kill(self);
925 self.nextthink = time; // WHY?!?
928 float WaypointSprite_visible_for_player(entity e)
930 // personal waypoints
931 if (self.enemy && self.enemy != e)
935 if (self.rule == SPRITERULE_SPECTATOR)
937 if (!autocvar_sv_itemstime)
939 if (!warmup_stage && IS_PLAYER(e))
942 else if (self.team && self.rule == SPRITERULE_DEFAULT)
944 if (self.team != e.team)
953 entity WaypointSprite_getviewentity(entity e)
955 if (IS_SPEC(e)) e = e.enemy;
956 /* TODO idea (check this breaks nothing)
957 else if (e.classname == "observer")
963 float WaypointSprite_isteammate(entity e, entity e2)
966 return e2.team == e.team;
970 float WaypointSprite_Customize()
972 // this is not in SendEntity because it shall run every frame, not just every update
974 // make spectators see what the player would see
975 entity e = WaypointSprite_getviewentity(other);
977 if (MUTATOR_CALLHOOK(CustomizeWaypoint, self, other))
980 return self.waypointsprite_visible_for_player(e);
983 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
985 void WaypointSprite_Reset()
987 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
990 WaypointSprite_Kill(self);
993 entity WaypointSprite_Spawn(
994 entity spr, // sprite
995 float _lifetime, float maxdistance, // lifetime, max distance
996 entity ref, vector ofs, // position
997 entity showto, float t, // show to whom? Use a flag to indicate a team
998 entity own, .entity ownfield, // remove when own gets killed
999 float hideable, // true when it should be controlled by cl_hidewaypoints
1000 entity icon // initial icon
1003 entity wp = new(sprite_waypoint);
1005 wp.teleport_time = time + _lifetime;
1006 wp.fade_time = _lifetime;
1007 wp.exteriormodeltoclient = ref;
1011 setorigin(wp, ref.origin + ofs);
1018 wp.currentammo = hideable;
1022 remove(own.(ownfield));
1023 own.(ownfield) = wp;
1024 wp.owned_by_field = ownfield;
1026 wp.fade_rate = maxdistance;
1027 wp.think = WaypointSprite_Think;
1028 wp.nextthink = time;
1029 wp.model1 = spr.netname;
1030 wp.customizeentityforclient = WaypointSprite_Customize;
1031 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1032 wp.reset2 = WaypointSprite_Reset;
1034 wp.colormod = spr.m_color;
1035 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1039 entity WaypointSprite_SpawnFixed(
1044 entity icon // initial icon
1047 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, own, ownfield, true, icon);
1050 entity WaypointSprite_DeployFixed(
1052 float limited_range,
1054 entity icon // initial icon
1064 maxdistance = waypointsprite_limitedrange;
1067 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, world, ofs, world, t, self, waypointsprite_deployed_fixed, false, icon);
1070 entity WaypointSprite_DeployPersonal(
1073 entity icon // initial icon
1076 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, self, waypointsprite_deployed_personal, false, icon);
1079 entity WaypointSprite_Attach(
1081 float limited_range,
1082 entity icon // initial icon
1086 if (self.waypointsprite_attachedforcarrier)
1087 return world; // can't attach to FC
1094 maxdistance = waypointsprite_limitedrange;
1097 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, self, '0 0 64', world, t, self, waypointsprite_attached, false, icon);
1100 entity WaypointSprite_AttachCarrier(
1103 entity icon // initial icon and color
1106 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1107 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', world, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1110 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1111 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1116 void WaypointSprite_DetachCarrier(entity carrier)
1118 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1121 void WaypointSprite_ClearPersonal()
1123 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1126 void WaypointSprite_ClearOwned()
1128 WaypointSprite_Kill(self.waypointsprite_deployed_fixed);
1129 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1130 WaypointSprite_Kill(self.waypointsprite_attached);
1133 void WaypointSprite_PlayerDead()
1135 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1136 WaypointSprite_DetachCarrier(self);
1139 void WaypointSprite_PlayerGone()
1141 WaypointSprite_Disown(self.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1142 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1143 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1144 WaypointSprite_DetachCarrier(self);