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();
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();
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 (s == WP_Buff.netname || s == RADARICON_Buff.netname) return Buffs_from(this.wp_extra).m_color;
240 string spritelookuptext(string s)
242 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
243 if (s == WP_Weapon.netname) return get_weaponinfo(self.wp_extra).m_name;
244 if (s == WP_Item.netname) return Items_from(self.wp_extra).m_waypoint;
245 if (s == WP_Buff.netname) return Buffs_from(self.wp_extra).m_prettyName;
246 if (s == WP_Monster.netname) return get_monsterinfo(self.wp_extra).monster_name;
248 // need to loop, as our netname could be one of three
249 FOREACH(Waypoints, it.netname == s, LAMBDA(
258 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
260 vector v1, v2, v3, v4;
262 hotspot = -1 * hotspot;
264 // hotspot-relative coordinates of the corners
266 v2 = hotspot + '1 0 0' * sz.x;
267 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
268 v4 = hotspot + '0 1 0' * sz.y;
270 // rotate them, and make them absolute
271 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
272 v1 = rotate(v1, rot) + org;
273 v2 = rotate(v2, rot) + org;
274 v3 = rotate(v3, rot) + org;
275 v4 = rotate(v4, rot) + org;
278 R_BeginPolygon(pic, f);
279 R_PolygonVertex(v1, '0 0 0', rgb, a);
280 R_PolygonVertex(v2, '1 0 0', rgb, a);
281 R_PolygonVertex(v3, '1 1 0', rgb, a);
282 R_PolygonVertex(v4, '0 1 0', rgb, a);
286 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
288 R_BeginPolygon(pic, f);
289 R_PolygonVertex(o, '0 0 0', rgb, a);
290 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
291 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
292 R_PolygonVertex(o + up, '0 1 0', rgb, a);
296 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)
299 float owidth; // outer width
301 hotspot = -1 * hotspot;
303 // hotspot-relative coordinates of the healthbar corners
308 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
309 o = rotate(o, rot) + org;
310 ri = rotate(ri, rot);
311 up = rotate(up, rot);
313 owidth = width + 2 * border;
314 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
316 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
317 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
318 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
319 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
320 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
323 // returns location of sprite text
324 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
326 float size = 9.0 * t;
327 float border = 1.5 * t;
328 float margin = 4.0 * t;
330 float borderDiag = border * 1.414;
331 vector arrowX = eX * size;
332 vector arrowY = eY * (size+borderDiag);
333 vector borderX = eX * (size+borderDiag);
334 vector borderY = eY * (size+borderDiag+border);
336 R_BeginPolygon("", DRAWFLAG_NORMAL);
337 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
338 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
339 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
340 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
341 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
344 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
345 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
346 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
347 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
350 return o + rotate(eY * (borderDiag+size+margin), ang);
353 // returns location of sprite healthbar
354 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
358 float aspect, sa, ca;
360 sw = stringwidth(s, false, fontsize);
367 // how do corners work?
368 aspect = vid_conwidth / vid_conheight;
370 ca = cos(ang) * aspect;
371 if (fabs(sa) > fabs(ca))
374 algny = 0.5 - 0.5 * ca / fabs(sa);
378 algnx = 0.5 - 0.5 * sa / fabs(ca);
386 // we want to be onscreen
391 if (o.x > vid_conwidth - w)
392 o.x = vid_conwidth - w;
393 if (o.y > vid_conheight - h)
394 o.x = vid_conheight - h;
396 o.x += 0.5 * (w - sw);
398 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
406 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
408 vector yvec = '0.299 0.587 0.114';
409 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
412 vector fixrgbexcess(vector rgb)
415 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
417 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
418 if (rgb.z > 1) rgb.z = 1;
419 } else if (rgb.z > 1) {
420 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
421 if (rgb.y > 1) rgb.y = 1;
423 } else if (rgb.y > 1) {
424 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
426 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
427 if (rgb.z > 1) rgb.z = 1;
428 } else if (rgb.z > 1) {
429 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
430 if (rgb.x > 1) rgb.x = 1;
432 } else if (rgb.z > 1) {
433 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
435 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
436 if (rgb.y > 1) rgb.y = 1;
437 } else if (rgb.y > 1) {
438 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
439 if (rgb.x > 1) rgb.x = 1;
445 void Draw_WaypointSprite(entity this)
448 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
452 if (self.hideflags & 2)
453 return; // radar only
455 if (autocvar_cl_hidewaypoints >= 2)
458 if (self.hideflags & 1)
459 if (autocvar_cl_hidewaypoints)
460 return; // fixed waypoint
462 InterpolateOrigin_Do();
464 float t = GetPlayerColor(player_localnum) + 1;
466 string spriteimage = "";
471 case SPRITERULE_SPECTATOR:
473 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
474 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
477 spriteimage = self.netname;
479 case SPRITERULE_DEFAULT:
483 spriteimage = self.netname;
488 spriteimage = self.netname;
490 case SPRITERULE_TEAMPLAY:
491 if (t == NUM_SPECTATOR + 1)
492 spriteimage = self.netname3;
493 else if (self.team == t)
494 spriteimage = self.netname2;
496 spriteimage = self.netname;
499 error("Invalid waypointsprite rule!");
503 if (spriteimage == "")
506 ++waypointsprite_newcount;
509 dist = vlen(self.origin - view_origin);
512 a = self.alpha * autocvar_hud_panel_fg_alpha;
514 if (self.maxdistance > waypointsprite_normdistance)
515 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
516 else if (self.maxdistance > 0)
517 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
519 vector rgb = spritelookupcolor(self, spriteimage, self.teamradar_color);
522 self.teamradar_color = '1 0 1';
523 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
526 if (time - floor(time) > 0.5)
528 if (self.helpme && time < self.helpme)
529 a *= SPRITE_HELPME_BLINK;
530 else if (!self.lifetime) // fading out waypoints don't blink
531 a *= spritelookupblinkvalue(spriteimage);
543 rgb = fixrgbexcess(rgb);
548 o = project_3d_to_2d(self.origin);
550 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
551 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
552 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
553 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
555 // scale it to be just in view
559 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
560 ang = atan2(-d.x, -d.y);
564 f1 = d.x / vid_conwidth;
565 f2 = d.y / vid_conheight;
567 if (max(f1, -f1) > max(f2, -f2)) {
570 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
573 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
578 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
581 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
585 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
593 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
594 ang = atan2(-d.x, -d.y);
599 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
600 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
601 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
602 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
604 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
606 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
608 t = waypointsprite_scale * vidscale;
609 a *= waypointsprite_alpha;
612 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
613 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
615 if (edgedistance_min < waypointsprite_edgefadedistance) {
616 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
617 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
619 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
620 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
621 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
624 if (self.build_finished)
626 if (time < self.build_finished + 0.25)
628 if (time < self.build_started)
629 self.health = self.build_starthealth;
630 else if (time < self.build_finished)
631 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
639 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
642 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
645 txt = spritelookuptext(spriteimage);
646 if (self.helpme && time < self.helpme)
647 txt = sprintf(_("%s needing help!"), txt);
648 if (autocvar_g_waypointsprite_uppercase)
649 txt = strtoupper(txt);
651 draw_beginBoldFont();
652 if (self.health >= 0)
654 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
657 if (self.build_finished)
662 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
664 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
671 SPRITE_HEALTHBAR_WIDTH * t,
672 SPRITE_HEALTHBAR_HEIGHT * t,
674 SPRITE_HEALTHBAR_BORDER * t,
677 a * SPRITE_HEALTHBAR_BORDERALPHA,
679 a * SPRITE_HEALTHBAR_HEALTHALPHA,
685 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
690 void WaypointSprite_Load_Frames(string ext)
692 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
694 int ext_len = strlen(ext);
695 int n = search_getsize(dh);
696 for (int i = 0; i < n; ++i)
698 string s = search_getfilename(dh, i);
699 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
701 int o = strstrofs(s, "_frame", 0);
702 string sname = strcat("/spriteframes/", substring(s, 0, o));
703 string sframes = substring(s, o + 6, strlen(s) - o - 6);
704 int f = stof(sframes) + 1;
705 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
710 void WaypointSprite_Load();
711 STATIC_INIT(WaypointSprite_Load) {
712 WaypointSprite_Load();
713 WaypointSprite_Load_Frames(".tga");
714 WaypointSprite_Load_Frames(".jpg");
716 void WaypointSprite_Load()
718 waypointsprite_fadedistance = vlen(mi_scale);
719 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
720 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
721 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
722 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
723 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
724 waypointsprite_scale = autocvar_g_waypointsprite_scale;
725 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
726 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
727 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
728 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
729 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
730 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
731 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
732 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
733 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
734 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
735 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
736 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
737 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
738 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
739 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
741 waypointsprite_count = waypointsprite_newcount;
742 waypointsprite_newcount = 0;
747 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
749 string m1 = _m1.netname;
750 string m2 = _m2.netname;
751 string m3 = _m3.netname;
769 void WaypointSprite_UpdateHealth(entity e, float f)
771 f = bound(0, f, e.max_health);
772 if (f != e.health || e.pain_finished)
780 void WaypointSprite_UpdateMaxHealth(entity e, float f)
782 if (f != e.max_health || e.pain_finished)
790 void WaypointSprite_UpdateBuildFinished(entity e, float f)
792 if (f != e.pain_finished || e.max_health)
800 void WaypointSprite_UpdateOrigin(entity e, vector o)
809 void WaypointSprite_UpdateRule(entity e, float t, float r)
811 // no check, as this is never called without doing an actual change (usually only once)
817 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
819 // no check, as this is never called without doing an actual change (usually only once)
821 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
826 void WaypointSprite_Ping(entity e)
829 if (time < e.waypointsprite_pingtime) return;
830 e.waypointsprite_pingtime = time + 0.3;
831 // ALWAYS sends (this causes a radar circle), thus no check
836 void WaypointSprite_HelpMePing(entity e)
838 WaypointSprite_Ping(e);
839 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
843 void WaypointSprite_FadeOutIn(entity e, float t)
848 e.teleport_time = time + t;
850 else if (t < (e.teleport_time - time))
852 // accelerate the waypoint's dying
854 // (e.teleport_time - time) / wp.fade_time stays
855 // e.teleport_time = time + fadetime
856 float current_fadetime;
857 current_fadetime = e.teleport_time - time;
858 e.teleport_time = time + t;
859 e.fade_time = e.fade_time * t / current_fadetime;
865 void WaypointSprite_Init()
867 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
868 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
869 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
872 void WaypointSprite_InitClient(entity e)
876 void WaypointSprite_Kill(entity wp)
879 if (wp.owner) wp.owner.(wp.owned_by_field) = world;
883 void WaypointSprite_Disown(entity wp, float fadetime)
886 if (wp.classname != "sprite_waypoint")
888 backtrace("Trying to disown a non-waypointsprite");
893 if (wp.exteriormodeltoclient == wp.owner)
894 wp.exteriormodeltoclient = world;
895 wp.owner.(wp.owned_by_field) = world;
898 WaypointSprite_FadeOutIn(wp, fadetime);
902 void WaypointSprite_Think()
904 bool doremove = false;
906 if (self.fade_time && time >= self.teleport_time)
911 if (self.exteriormodeltoclient)
912 WaypointSprite_UpdateOrigin(self, self.exteriormodeltoclient.origin + self.view_ofs);
915 WaypointSprite_Kill(self);
917 self.nextthink = time; // WHY?!?
920 float WaypointSprite_visible_for_player(entity e)
922 // personal waypoints
923 if (self.enemy && self.enemy != e)
927 if (self.rule == SPRITERULE_SPECTATOR)
929 if (!autocvar_sv_itemstime)
931 if (!warmup_stage && IS_PLAYER(e))
934 else if (self.team && self.rule == SPRITERULE_DEFAULT)
936 if (self.team != e.team)
945 entity WaypointSprite_getviewentity(entity e)
947 if (IS_SPEC(e)) e = e.enemy;
948 /* TODO idea (check this breaks nothing)
949 else if (e.classname == "observer")
955 float WaypointSprite_isteammate(entity e, entity e2)
958 return e2.team == e.team;
962 float WaypointSprite_Customize()
964 // this is not in SendEntity because it shall run every frame, not just every update
966 // make spectators see what the player would see
967 entity e = WaypointSprite_getviewentity(other);
969 if (MUTATOR_CALLHOOK(CustomizeWaypoint, self, other))
972 return self.waypointsprite_visible_for_player(e);
975 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
977 void WaypointSprite_Reset()
979 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
982 WaypointSprite_Kill(self);
985 entity WaypointSprite_Spawn(
986 entity spr, // sprite
987 float _lifetime, float maxdistance, // lifetime, max distance
988 entity ref, vector ofs, // position
989 entity showto, float t, // show to whom? Use a flag to indicate a team
990 entity own, .entity ownfield, // remove when own gets killed
991 float hideable, // true when it should be controlled by cl_hidewaypoints
992 entity icon // initial icon
995 entity wp = new(sprite_waypoint);
997 wp.teleport_time = time + _lifetime;
998 wp.fade_time = _lifetime;
999 wp.exteriormodeltoclient = ref;
1003 setorigin(wp, ref.origin + ofs);
1010 wp.currentammo = hideable;
1014 remove(own.(ownfield));
1015 own.(ownfield) = wp;
1016 wp.owned_by_field = ownfield;
1018 wp.fade_rate = maxdistance;
1019 wp.think = WaypointSprite_Think;
1020 wp.nextthink = time;
1021 wp.model1 = spr.netname;
1022 wp.customizeentityforclient = WaypointSprite_Customize;
1023 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1024 wp.reset2 = WaypointSprite_Reset;
1026 wp.colormod = spr.m_color;
1027 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1031 entity WaypointSprite_SpawnFixed(
1036 entity icon // initial icon
1039 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, own, ownfield, true, icon);
1042 entity WaypointSprite_DeployFixed(
1044 float limited_range,
1046 entity icon // initial icon
1056 maxdistance = waypointsprite_limitedrange;
1059 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, world, ofs, world, t, self, waypointsprite_deployed_fixed, false, icon);
1062 entity WaypointSprite_DeployPersonal(
1065 entity icon // initial icon
1068 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, self, waypointsprite_deployed_personal, false, icon);
1071 entity WaypointSprite_Attach(
1073 float limited_range,
1074 entity icon // initial icon
1078 if (self.waypointsprite_attachedforcarrier)
1079 return world; // can't attach to FC
1086 maxdistance = waypointsprite_limitedrange;
1089 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, self, '0 0 64', world, t, self, waypointsprite_attached, false, icon);
1092 entity WaypointSprite_AttachCarrier(
1095 entity icon // initial icon and color
1098 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1099 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', world, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1102 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1103 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1108 void WaypointSprite_DetachCarrier(entity carrier)
1110 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1113 void WaypointSprite_ClearPersonal()
1115 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1118 void WaypointSprite_ClearOwned()
1120 WaypointSprite_Kill(self.waypointsprite_deployed_fixed);
1121 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1122 WaypointSprite_Kill(self.waypointsprite_attached);
1125 void WaypointSprite_PlayerDead()
1127 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1128 WaypointSprite_DetachCarrier(self);
1131 void WaypointSprite_PlayerGone()
1133 WaypointSprite_Disown(self.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1134 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1135 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1136 WaypointSprite_DetachCarrier(self);