1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
5 REGISTER_NET_LINKED(waypointsprites)
8 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
9 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
11 WriteHeader(MSG_ENTITY, waypointsprites);
13 sendflags = sendflags & 0x7F;
15 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
19 if(this.currentammo == 1)
21 if(this.exteriormodeltoclient == to)
23 if(this.currentammo == 2)
26 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
27 sendflags = M_ARGV(2, int);
30 WriteByte(MSG_ENTITY, sendflags);
31 WriteByte(MSG_ENTITY, this.wp_extra);
37 WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
41 float dt = this.pain_finished - time;
42 dt = bound(0, dt * 32, 16383);
43 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
44 WriteByte(MSG_ENTITY, (dt & 0x00FF));
50 WriteVector(MSG_ENTITY, this.origin);
55 WriteByte(MSG_ENTITY, this.team);
56 WriteByte(MSG_ENTITY, this.rule);
60 WriteString(MSG_ENTITY, this.model1);
63 WriteString(MSG_ENTITY, this.model2);
66 WriteString(MSG_ENTITY, this.model3);
70 WriteCoord(MSG_ENTITY, this.fade_time);
71 WriteCoord(MSG_ENTITY, this.teleport_time);
72 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
73 WriteByte(MSG_ENTITY, f);
78 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
79 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
80 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
81 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
83 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
85 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
90 WriteByte(MSG_ENTITY, dt);
93 WriteByte(MSG_ENTITY, 0);
101 void Ent_WaypointSprite(entity this, bool isnew);
102 NET_HANDLE(waypointsprites, bool isnew) {
103 Ent_WaypointSprite(this, isnew);
107 void Ent_RemoveWaypointSprite(entity this)
109 if (this.netname) strunzone(this.netname);
110 if (this.netname2) strunzone(this.netname2);
111 if (this.netname3) strunzone(this.netname3);
114 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
115 void Ent_WaypointSprite(entity this, bool isnew)
117 int sendflags = ReadByte();
118 this.wp_extra = ReadByte();
121 this.spawntime = time;
123 this.draw2d = Draw_WaypointSprite;
125 IL_PUSH(g_drawables_2d, this);
126 IL_PUSH(g_radaricons, this);
129 InterpolateOrigin_Undo(this);
130 this.iflags |= IFLAG_ORIGIN;
132 if (sendflags & 0x80)
137 this.health = t / 191.0;
138 this.build_finished = 0;
142 t = (t - 192) * 256 + ReadByte();
143 this.build_started = servertime;
144 if (this.build_finished)
145 this.build_starthealth = bound(0, this.health, 1);
147 this.build_starthealth = 0;
148 this.build_finished = servertime + t / 32;
154 this.build_finished = 0;
159 // unfortunately, this needs to be exact (for the 3D display)
160 this.origin = ReadVector();
161 setorigin(this, this.origin);
166 this.team = ReadByte();
167 this.rule = ReadByte();
173 strunzone(this.netname);
174 this.netname = strzone(ReadString());
180 strunzone(this.netname2);
181 this.netname2 = strzone(ReadString());
187 strunzone(this.netname3);
188 this.netname3 = strzone(ReadString());
193 this.lifetime = ReadCoord();
194 this.fadetime = ReadCoord();
195 this.maxdistance = ReadShort();
196 this.hideflags = ReadByte();
202 this.teamradar_icon = f & BITS(7);
205 this.(teamradar_times[this.teamradar_time_index]) = time;
206 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
208 this.teamradar_color_x = ReadByte() / 255.0;
209 this.teamradar_color_y = ReadByte() / 255.0;
210 this.teamradar_color_z = ReadByte() / 255.0;
211 this.helpme = ReadByte() * 0.1;
213 this.helpme += servertime;
216 InterpolateOrigin_Note(this);
218 this.entremove = Ent_RemoveWaypointSprite;
223 float spritelookupblinkvalue(entity this, string s)
225 if (s == WP_Weapon.netname) {
226 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
229 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
230 if(s == WP_FlagReturn.netname) return 2;
235 vector spritelookupcolor(entity this, string s, vector def)
237 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
238 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
239 if (MUTATOR_CALLHOOK(WP_Format, this, s))
241 return M_ARGV(2, vector);
246 string spritelookuptext(entity this, string s)
248 if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
249 return "Spam"; // no need to translate this debug string
250 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
251 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
252 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
253 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
254 if (MUTATOR_CALLHOOK(WP_Format, this, s))
256 return M_ARGV(3, string);
259 // need to loop, as our netname could be one of three
260 FOREACH(Waypoints, it.netname == s, {
267 string spritelookupicon(entity this, string s)
269 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
270 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
271 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
272 if (s == WP_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
273 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
274 if (MUTATOR_CALLHOOK(WP_Format, this, s))
276 return M_ARGV(4, string);
279 // need to loop, as our netname could be one of three
280 FOREACH(Waypoints, it.netname == s, {
289 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
291 vector v1, v2, v3, v4;
293 hotspot = -1 * hotspot;
295 // hotspot-relative coordinates of the corners
297 v2 = hotspot + '1 0 0' * sz.x;
298 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
299 v4 = hotspot + '0 1 0' * sz.y;
301 // rotate them, and make them absolute
302 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
303 v1 = Rotate(v1, rot) + org;
304 v2 = Rotate(v2, rot) + org;
305 v3 = Rotate(v3, rot) + org;
306 v4 = Rotate(v4, rot) + org;
309 R_BeginPolygon(pic, f);
310 R_PolygonVertex(v1, '0 0 0', rgb, a);
311 R_PolygonVertex(v2, '1 0 0', rgb, a);
312 R_PolygonVertex(v3, '1 1 0', rgb, a);
313 R_PolygonVertex(v4, '0 1 0', rgb, a);
317 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
319 R_BeginPolygon(pic, f);
320 R_PolygonVertex(o, '0 0 0', rgb, a);
321 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
322 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
323 R_PolygonVertex(o + up, '0 1 0', rgb, a);
327 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)
330 float owidth; // outer width
332 hotspot = -1 * hotspot;
334 // hotspot-relative coordinates of the healthbar corners
339 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
340 o = Rotate(o, rot) + org;
341 ri = Rotate(ri, rot);
342 up = Rotate(up, rot);
344 owidth = width + 2 * border;
345 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
347 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
348 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
349 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
350 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
351 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
354 // returns location of sprite text
355 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
357 float size = 9.0 * t;
358 float border = 1.5 * t;
359 float margin = 4.0 * t;
361 float borderDiag = border * 1.414;
362 vector arrowX = eX * size;
363 vector arrowY = eY * (size+borderDiag);
364 vector borderX = eX * (size+borderDiag);
365 vector borderY = eY * (size+borderDiag+border);
367 R_BeginPolygon("", DRAWFLAG_NORMAL);
368 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
369 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
370 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
371 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
372 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
375 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
376 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
377 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
378 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
381 return o + Rotate(eY * (borderDiag+size+margin), ang);
384 // returns location of sprite healthbar
385 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str)
389 float aspect, sa, ca;
392 sw = stringwidth(str, false, sz);
402 // how do corners work?
403 aspect = vid_conwidth / vid_conheight;
405 ca = cos(ang) * aspect;
406 if (fabs(sa) > fabs(ca))
410 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
415 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
423 // we want to be onscreen
428 if (o.x > vid_conwidth - w)
429 o.x = vid_conwidth - w;
430 if (o.y > vid_conheight - h)
431 o.y = vid_conheight - h;
433 o.x += 0.5 * (w - sw);
436 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
438 drawpic(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
446 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
448 vector yvec = '0.299 0.587 0.114';
449 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
452 vector fixrgbexcess(vector rgb)
455 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
457 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
458 if (rgb.z > 1) rgb.z = 1;
459 } else if (rgb.z > 1) {
460 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
461 if (rgb.y > 1) rgb.y = 1;
463 } else if (rgb.y > 1) {
464 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
466 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
467 if (rgb.z > 1) rgb.z = 1;
468 } else if (rgb.z > 1) {
469 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
470 if (rgb.x > 1) rgb.x = 1;
472 } else if (rgb.z > 1) {
473 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
475 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
476 if (rgb.y > 1) rgb.y = 1;
477 } else if (rgb.y > 1) {
478 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
479 if (rgb.x > 1) rgb.x = 1;
485 void Draw_WaypointSprite(entity this)
487 if (this.lifetime > 0)
488 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
492 if (this.hideflags & 2)
493 return; // radar only
495 if (autocvar_cl_hidewaypoints >= 2)
498 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
499 return; // fixed waypoint
501 InterpolateOrigin_Do(this);
503 float t = entcs_GetTeam(player_localnum) + 1;
504 string spriteimage = "";
509 case SPRITERULE_SPECTATOR:
511 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
512 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
515 spriteimage = this.netname;
517 case SPRITERULE_DEFAULT:
521 spriteimage = this.netname;
526 spriteimage = this.netname;
528 case SPRITERULE_TEAMPLAY:
529 if (t == NUM_SPECTATOR + 1)
530 spriteimage = this.netname3;
531 else if (this.team == t)
532 spriteimage = this.netname2;
534 spriteimage = this.netname;
537 error("Invalid waypointsprite rule!");
541 if (spriteimage == "")
544 ++waypointsprite_newcount;
546 float dist = vlen(this.origin - view_origin);
547 float a = this.alpha * autocvar_hud_panel_fg_alpha;
549 if (this.maxdistance > waypointsprite_normdistance)
550 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent);
551 else if (this.maxdistance > 0)
552 a *= (bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
554 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
557 this.teamradar_color = '1 0 1';
558 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
561 if (time - floor(time) > 0.5)
563 if (this.helpme && time < this.helpme)
564 a *= SPRITE_HELPME_BLINK;
565 else if (this.lifetime > 0) // fading out waypoints don't blink
566 a *= spritelookupblinkvalue(this, spriteimage);
578 rgb = fixrgbexcess(rgb);
583 o = project_3d_to_2d(this.origin);
585 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
586 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
587 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
588 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
590 // scale it to be just in view
594 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
595 ang = atan2(-d.x, -d.y);
599 f1 = d.x / vid_conwidth;
600 f2 = d.y / vid_conheight;
602 if (max(f1, -f1) > max(f2, -f2)) {
605 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
608 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
613 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
616 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
620 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
628 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
629 ang = atan2(-d.x, -d.y);
634 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
635 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
636 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
637 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
639 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
641 t = waypointsprite_scale;
642 a *= waypointsprite_alpha;
645 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
646 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
648 if (edgedistance_min < waypointsprite_edgefadedistance) {
649 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
650 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
652 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
653 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
654 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
657 if (this.build_finished)
659 if (time < this.build_finished + 0.25)
661 if (time < this.build_started)
662 this.health = this.build_starthealth;
663 else if (time < this.build_finished)
664 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
672 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
676 if (!autocvar_g_waypointsprite_text)
678 string spr_icon = spritelookupicon(this, spriteimage);
680 bool icon_found = !(!spr_icon || spr_icon == "");
681 if (icon_found) // it's valid, but let's make sure it exists!
683 pic = strcat(hud_skin_path, "/", spr_icon);
684 if(precache_pic(pic) == "")
686 pic = strcat("gfx/hud/default/", spr_icon);
687 if(!precache_pic(pic))
697 string txt = string_null;
700 txt = spritelookuptext(this, spriteimage);
701 if (this.helpme && time < this.helpme)
702 txt = sprintf(_("%s needing help!"), txt);
703 if (autocvar_g_waypointsprite_uppercase)
704 txt = strtoupper(txt);
706 sz = waypointsprite_fontsize * '1 1 0';
710 // for convenience icon path and color are saved to txt and txt_color
712 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
713 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
716 draw_beginBoldFont();
717 if (this.health >= 0)
719 float align = 0, marg;
720 if (this.build_finished)
725 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
727 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
729 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
730 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
737 SPRITE_HEALTHBAR_WIDTH * t,
738 SPRITE_HEALTHBAR_HEIGHT * t,
740 SPRITE_HEALTHBAR_BORDER * t,
743 a * SPRITE_HEALTHBAR_BORDERALPHA,
745 a * SPRITE_HEALTHBAR_HEALTHALPHA,
751 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
757 void WaypointSprite_Load_Frames(string ext)
759 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
761 int ext_len = strlen(ext);
762 int n = search_getsize(dh);
763 for (int i = 0; i < n; ++i)
765 string s = search_getfilename(dh, i);
766 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
768 int o = strstrofs(s, "_frame", 0);
769 string sname = strcat("/spriteframes/", substring(s, 0, o));
770 string sframes = substring(s, o + 6, strlen(s) - o - 6);
771 int f = stof(sframes) + 1;
772 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
777 void WaypointSprite_Load();
778 STATIC_INIT(WaypointSprite_Load) {
779 WaypointSprite_Load();
780 WaypointSprite_Load_Frames(".tga");
781 WaypointSprite_Load_Frames(".jpg");
783 void WaypointSprite_Load()
785 waypointsprite_fadedistance = vlen(mi_scale);
786 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
787 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
788 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
789 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
790 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
791 waypointsprite_scale = autocvar_g_waypointsprite_scale;
792 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
793 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
794 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
795 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
796 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
797 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
798 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
799 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
800 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
801 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
802 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
803 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
804 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
805 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
806 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
808 waypointsprite_count = waypointsprite_newcount;
809 waypointsprite_newcount = 0;
814 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
816 string m1 = _m1.netname;
817 string m2 = _m2.netname;
818 string m3 = _m3.netname;
836 void WaypointSprite_UpdateHealth(entity e, float f)
838 f = bound(0, f, e.max_health);
839 if (f != e.health || e.pain_finished)
847 void WaypointSprite_UpdateMaxHealth(entity e, float f)
849 if (f != e.max_health || e.pain_finished)
857 void WaypointSprite_UpdateBuildFinished(entity e, float f)
859 if (f != e.pain_finished || e.max_health)
867 void WaypointSprite_UpdateOrigin(entity e, vector o)
876 void WaypointSprite_UpdateRule(entity e, float t, float r)
878 // no check, as this is never called without doing an actual change (usually only once)
884 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
886 // no check, as this is never called without doing an actual change (usually only once)
888 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
893 void WaypointSprite_Ping(entity e)
896 if (time < e.waypointsprite_pingtime) return;
897 e.waypointsprite_pingtime = time + 0.3;
898 // ALWAYS sends (this causes a radar circle), thus no check
903 void WaypointSprite_HelpMePing(entity e)
905 WaypointSprite_Ping(e);
906 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
910 void WaypointSprite_FadeOutIn(entity e, float t)
915 e.teleport_time = time + t;
917 else if (t < (e.teleport_time - time))
919 // accelerate the waypoint's dying
921 // (e.teleport_time - time) / wp.fade_time stays
922 // e.teleport_time = time + fadetime
923 float current_fadetime = e.teleport_time - time;
924 e.teleport_time = time + t;
926 e.fade_time = -e.fade_time;
927 e.fade_time = e.fade_time * t / current_fadetime;
933 void WaypointSprite_Init()
935 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
936 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
937 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
940 void WaypointSprite_Kill(entity wp)
943 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
947 void WaypointSprite_Disown(entity wp, float fadetime)
950 if (wp.classname != "sprite_waypoint")
952 backtrace("Trying to disown a non-waypointsprite");
957 if (wp.exteriormodeltoclient == wp.owner)
958 wp.exteriormodeltoclient = NULL;
959 wp.owner.(wp.owned_by_field) = NULL;
962 WaypointSprite_FadeOutIn(wp, fadetime);
966 void WaypointSprite_Think(entity this)
968 bool doremove = false;
970 if (this.fade_time && time >= this.teleport_time)
975 if (this.exteriormodeltoclient)
976 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
979 WaypointSprite_Kill(this);
981 this.nextthink = time; // WHY?!?
984 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
986 // personal waypoints
987 if (this.enemy && this.enemy != view)
991 if (this.rule == SPRITERULE_SPECTATOR)
993 if (!autocvar_sv_itemstime)
995 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
998 else if (this.team && this.rule == SPRITERULE_DEFAULT)
1000 if (this.team != view.team)
1002 if (!IS_PLAYER(view))
1009 entity WaypointSprite_getviewentity(entity e)
1011 if (IS_SPEC(e)) e = e.enemy;
1012 /* TODO idea (check this breaks nothing)
1013 else if (e.classname == "observer")
1019 float WaypointSprite_isteammate(entity e, entity e2)
1022 return e2.team == e.team;
1026 bool WaypointSprite_Customize(entity this, entity client)
1028 // this is not in SendEntity because it shall run every frame, not just every update
1030 // make spectators see what the player would see
1031 entity e = WaypointSprite_getviewentity(client);
1033 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1036 return this.waypointsprite_visible_for_player(this, client, e);
1039 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1041 void WaypointSprite_Reset(entity this)
1043 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1046 WaypointSprite_Kill(this);
1049 entity WaypointSprite_Spawn(
1050 entity spr, // sprite
1051 float _lifetime, float maxdistance, // lifetime, max distance
1052 entity ref, vector ofs, // position
1053 entity showto, float t, // show to whom? Use a flag to indicate a team
1054 entity own, .entity ownfield, // remove when own gets killed
1055 float hideable, // true when it should be controlled by cl_hidewaypoints
1056 entity icon // initial icon
1059 entity wp = new(sprite_waypoint);
1060 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1062 _lifetime = -_lifetime;
1063 wp.teleport_time = time + _lifetime;
1064 wp.exteriormodeltoclient = ref;
1068 setorigin(wp, ref.origin + ofs);
1075 wp.currentammo = hideable;
1079 delete(own.(ownfield));
1080 own.(ownfield) = wp;
1081 wp.owned_by_field = ownfield;
1083 wp.fade_rate = maxdistance;
1084 setthink(wp, WaypointSprite_Think);
1085 wp.nextthink = time;
1086 wp.model1 = spr.netname;
1087 setcefc(wp, WaypointSprite_Customize);
1088 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1089 wp.reset2 = WaypointSprite_Reset;
1091 wp.colormod = spr.m_color;
1092 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1096 entity WaypointSprite_SpawnFixed(
1101 entity icon // initial icon
1104 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1107 entity WaypointSprite_DeployFixed(
1109 float limited_range,
1112 entity icon // initial icon
1122 maxdistance = waypointsprite_limitedrange;
1125 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1128 entity WaypointSprite_DeployPersonal(
1132 entity icon // initial icon
1135 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1138 entity WaypointSprite_Attach(
1141 float limited_range,
1142 entity icon // initial icon
1146 if (player.waypointsprite_attachedforcarrier)
1147 return NULL; // can't attach to FC
1154 maxdistance = waypointsprite_limitedrange;
1157 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1160 entity WaypointSprite_AttachCarrier(
1163 entity icon // initial icon and color
1166 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1167 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1170 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1171 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1176 void WaypointSprite_DetachCarrier(entity carrier)
1178 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1181 void WaypointSprite_ClearPersonal(entity this)
1183 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1186 void WaypointSprite_ClearOwned(entity this)
1188 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1189 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1190 WaypointSprite_Kill(this.waypointsprite_attached);
1193 void WaypointSprite_PlayerDead(entity this)
1195 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1196 WaypointSprite_DetachCarrier(this);
1199 void WaypointSprite_PlayerGone(entity this)
1201 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1202 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1203 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1204 WaypointSprite_DetachCarrier(this);