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 strfree(this.netname);
110 strfree(this.netname2);
111 strfree(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();
172 strcpy(this.netname, ReadString());
177 strcpy(this.netname2, ReadString());
182 strcpy(this.netname3, ReadString());
187 this.lifetime = ReadCoord();
188 this.fadetime = ReadCoord();
189 this.maxdistance = ReadShort();
190 this.hideflags = ReadByte();
196 this.teamradar_icon = f & BITS(7);
199 this.(teamradar_times[this.teamradar_time_index]) = time;
200 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
202 this.teamradar_color_x = ReadByte() / 255.0;
203 this.teamradar_color_y = ReadByte() / 255.0;
204 this.teamradar_color_z = ReadByte() / 255.0;
205 this.helpme = ReadByte() * 0.1;
207 this.helpme += servertime;
210 InterpolateOrigin_Note(this);
212 this.entremove = Ent_RemoveWaypointSprite;
217 float spritelookupblinkvalue(entity this, string s)
219 if (s == WP_Weapon.netname) {
220 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
223 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
224 if(s == WP_FlagReturn.netname) return 2;
229 vector spritelookupcolor(entity this, string s, vector def)
231 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
232 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
233 if (MUTATOR_CALLHOOK(WP_Format, this, s))
235 return M_ARGV(2, vector);
240 string spritelookuptext(entity this, string s)
242 if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
243 return "Spam"; // no need to translate this debug string
244 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
245 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
246 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
247 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
248 if (MUTATOR_CALLHOOK(WP_Format, this, s))
250 return M_ARGV(3, string);
253 // need to loop, as our netname could be one of three
254 FOREACH(Waypoints, it.netname == s, {
261 string spritelookupicon(entity this, string s)
263 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
264 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
265 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
266 if (s == WP_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
267 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
268 if (MUTATOR_CALLHOOK(WP_Format, this, s))
270 return M_ARGV(4, string);
273 // need to loop, as our netname could be one of three
274 FOREACH(Waypoints, it.netname == s, {
283 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
285 vector v1, v2, v3, v4;
287 hotspot = -1 * hotspot;
289 // hotspot-relative coordinates of the corners
291 v2 = hotspot + '1 0 0' * sz.x;
292 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
293 v4 = hotspot + '0 1 0' * sz.y;
295 // rotate them, and make them absolute
296 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
297 v1 = Rotate(v1, rot) + org;
298 v2 = Rotate(v2, rot) + org;
299 v3 = Rotate(v3, rot) + org;
300 v4 = Rotate(v4, rot) + org;
303 R_BeginPolygon(pic, f);
304 R_PolygonVertex(v1, '0 0 0', rgb, a);
305 R_PolygonVertex(v2, '1 0 0', rgb, a);
306 R_PolygonVertex(v3, '1 1 0', rgb, a);
307 R_PolygonVertex(v4, '0 1 0', rgb, a);
311 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
313 R_BeginPolygon(pic, f);
314 R_PolygonVertex(o, '0 0 0', rgb, a);
315 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
316 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
317 R_PolygonVertex(o + up, '0 1 0', rgb, a);
321 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)
324 float owidth; // outer width
326 hotspot = -1 * hotspot;
328 // hotspot-relative coordinates of the healthbar corners
333 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
334 o = Rotate(o, rot) + org;
335 ri = Rotate(ri, rot);
336 up = Rotate(up, rot);
338 owidth = width + 2 * border;
339 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
341 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
342 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
343 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
344 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
345 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
348 // returns location of sprite text
349 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
351 float size = 9.0 * t;
352 float border = 1.5 * t;
353 float margin = 4.0 * t;
355 float borderDiag = border * 1.414;
356 vector arrowX = eX * size;
357 vector arrowY = eY * (size+borderDiag);
358 vector borderX = eX * (size+borderDiag);
359 vector borderY = eY * (size+borderDiag+border);
361 R_BeginPolygon("", DRAWFLAG_NORMAL);
362 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
363 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
364 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
365 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
366 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
369 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
370 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
371 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
372 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
375 return o + Rotate(eY * (borderDiag+size+margin), ang);
378 // returns location of sprite healthbar
379 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str)
383 float aspect, sa, ca;
386 sw = stringwidth(str, false, sz);
396 // how do corners work?
397 aspect = vid_conwidth / vid_conheight;
399 ca = cos(ang) * aspect;
400 if (fabs(sa) > fabs(ca))
404 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
409 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
417 // we want to be onscreen
422 if (o.x > vid_conwidth - w)
423 o.x = vid_conwidth - w;
424 if (o.y > vid_conheight - h)
425 o.y = vid_conheight - h;
427 o.x += 0.5 * (w - sw);
430 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
432 drawpic(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
440 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
442 vector yvec = '0.299 0.587 0.114';
443 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
446 vector fixrgbexcess(vector rgb)
449 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
451 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
452 if (rgb.z > 1) rgb.z = 1;
453 } else if (rgb.z > 1) {
454 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
455 if (rgb.y > 1) rgb.y = 1;
457 } else if (rgb.y > 1) {
458 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
460 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
461 if (rgb.z > 1) rgb.z = 1;
462 } else if (rgb.z > 1) {
463 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
464 if (rgb.x > 1) rgb.x = 1;
466 } else if (rgb.z > 1) {
467 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
469 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
470 if (rgb.y > 1) rgb.y = 1;
471 } else if (rgb.y > 1) {
472 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
473 if (rgb.x > 1) rgb.x = 1;
479 void Draw_WaypointSprite(entity this)
481 if (this.lifetime > 0)
482 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
486 if (this.hideflags & 2)
487 return; // radar only
489 if (autocvar_cl_hidewaypoints >= 2)
492 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
493 return; // fixed waypoint
495 InterpolateOrigin_Do(this);
497 float t = entcs_GetTeam(player_localnum) + 1;
498 string spriteimage = "";
503 case SPRITERULE_SPECTATOR:
505 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
506 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
509 spriteimage = this.netname;
511 case SPRITERULE_DEFAULT:
515 spriteimage = this.netname;
520 spriteimage = this.netname;
522 case SPRITERULE_TEAMPLAY:
523 if (t == NUM_SPECTATOR + 1)
524 spriteimage = this.netname3;
525 else if (this.team == t)
526 spriteimage = this.netname2;
528 spriteimage = this.netname;
531 error("Invalid waypointsprite rule!");
535 if (spriteimage == "")
538 ++waypointsprite_newcount;
540 float dist = vlen(this.origin - view_origin);
541 float a = this.alpha * autocvar_hud_panel_fg_alpha;
543 if (this.maxdistance > waypointsprite_normdistance)
544 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent);
545 else if (this.maxdistance > 0)
546 a *= (bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
548 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
551 this.teamradar_color = '1 0 1';
552 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
555 if (time - floor(time) > 0.5)
557 if (this.helpme && time < this.helpme)
558 a *= SPRITE_HELPME_BLINK;
559 else if (this.lifetime > 0) // fading out waypoints don't blink
560 a *= spritelookupblinkvalue(this, spriteimage);
572 rgb = fixrgbexcess(rgb);
577 o = project_3d_to_2d(this.origin);
579 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
580 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
581 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
582 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
584 // scale it to be just in view
587 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
588 ang = atan2(-d.x, -d.y);
592 float f1 = d.x / vid_conwidth;
593 float f2 = d.y / vid_conheight;
594 if (f1 == 0) { f1 = 0.000001; }
595 if (f2 == 0) { f2 = 0.000001; }
597 if (max(f1, -f1) > max(f2, -f2)) {
600 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
603 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
608 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
611 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
615 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
623 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
624 ang = atan2(-d.x, -d.y);
629 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
630 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
631 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
632 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
634 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
636 t = waypointsprite_scale;
637 a *= waypointsprite_alpha;
640 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
641 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
643 if (edgedistance_min < waypointsprite_edgefadedistance) {
644 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
645 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
647 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
648 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
649 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
652 if (this.build_finished)
654 if (time < this.build_finished + 0.25)
656 if (time < this.build_started)
657 this.health = this.build_starthealth;
658 else if (time < this.build_finished)
659 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
667 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
671 if (!autocvar_g_waypointsprite_text)
673 string spr_icon = spritelookupicon(this, spriteimage);
675 bool icon_found = !(!spr_icon || spr_icon == "");
676 if (icon_found) // it's valid, but let's make sure it exists!
678 pic = strcat(hud_skin_path, "/", spr_icon);
679 if(precache_pic(pic) == "")
681 pic = strcat("gfx/hud/default/", spr_icon);
682 if(!precache_pic(pic))
692 string txt = string_null;
695 txt = spritelookuptext(this, spriteimage);
696 if (this.helpme && time < this.helpme)
697 txt = sprintf(_("%s needing help!"), txt);
698 if (autocvar_g_waypointsprite_uppercase)
699 txt = strtoupper(txt);
701 sz = waypointsprite_fontsize * '1 1 0';
705 // for convenience icon path and color are saved to txt and txt_color
707 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
708 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
711 draw_beginBoldFont();
712 if (this.health >= 0)
714 float align = 0, marg;
715 if (this.build_finished)
720 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
722 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
724 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
725 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
732 SPRITE_HEALTHBAR_WIDTH * t,
733 SPRITE_HEALTHBAR_HEIGHT * t,
735 SPRITE_HEALTHBAR_BORDER * t,
738 a * SPRITE_HEALTHBAR_BORDERALPHA,
740 a * SPRITE_HEALTHBAR_HEALTHALPHA,
746 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
752 void WaypointSprite_Load_Frames(string ext)
754 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
756 int ext_len = strlen(ext);
757 int n = search_getsize(dh);
758 for (int i = 0; i < n; ++i)
760 string s = search_getfilename(dh, i);
761 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
763 int o = strstrofs(s, "_frame", 0);
764 string sname = strcat("/spriteframes/", substring(s, 0, o));
765 string sframes = substring(s, o + 6, strlen(s) - o - 6);
766 int f = stof(sframes) + 1;
767 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
772 void WaypointSprite_Load();
773 STATIC_INIT(WaypointSprite_Load) {
774 WaypointSprite_Load();
775 WaypointSprite_Load_Frames(".tga");
776 WaypointSprite_Load_Frames(".jpg");
778 void WaypointSprite_Load()
780 waypointsprite_fadedistance = vlen(mi_scale);
781 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
782 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
783 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
784 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
785 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
786 waypointsprite_scale = autocvar_g_waypointsprite_scale;
787 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
788 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
789 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
790 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
791 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
792 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
793 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
794 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
795 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
796 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
797 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
798 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
799 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
800 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
801 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
803 waypointsprite_count = waypointsprite_newcount;
804 waypointsprite_newcount = 0;
809 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
811 string m1 = _m1.netname;
812 string m2 = _m2.netname;
813 string m3 = _m3.netname;
831 void WaypointSprite_UpdateHealth(entity e, float f)
833 f = bound(0, f, e.max_health);
834 if (f != e.health || e.pain_finished)
842 void WaypointSprite_UpdateMaxHealth(entity e, float f)
844 if (f != e.max_health || e.pain_finished)
852 void WaypointSprite_UpdateBuildFinished(entity e, float f)
854 if (f != e.pain_finished || e.max_health)
862 void WaypointSprite_UpdateOrigin(entity e, vector o)
871 void WaypointSprite_UpdateRule(entity e, float t, float r)
873 // no check, as this is never called without doing an actual change (usually only once)
879 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
881 // no check, as this is never called without doing an actual change (usually only once)
883 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
888 void WaypointSprite_Ping(entity e)
891 if (time < e.waypointsprite_pingtime) return;
892 e.waypointsprite_pingtime = time + 0.3;
893 // ALWAYS sends (this causes a radar circle), thus no check
898 void WaypointSprite_HelpMePing(entity e)
900 WaypointSprite_Ping(e);
901 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
905 void WaypointSprite_FadeOutIn(entity e, float t)
910 e.teleport_time = time + t;
912 else if (t < (e.teleport_time - time))
914 // accelerate the waypoint's dying
916 // (e.teleport_time - time) / wp.fade_time stays
917 // e.teleport_time = time + fadetime
918 float current_fadetime = e.teleport_time - time;
919 e.teleport_time = time + t;
921 e.fade_time = -e.fade_time;
922 e.fade_time = e.fade_time * t / current_fadetime;
928 void WaypointSprite_Init()
930 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
931 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
932 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
935 void WaypointSprite_Kill(entity wp)
938 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
942 void WaypointSprite_Disown(entity wp, float fadetime)
945 if (wp.classname != "sprite_waypoint")
947 backtrace("Trying to disown a non-waypointsprite");
952 if (wp.exteriormodeltoclient == wp.owner)
953 wp.exteriormodeltoclient = NULL;
954 wp.owner.(wp.owned_by_field) = NULL;
957 WaypointSprite_FadeOutIn(wp, fadetime);
961 void WaypointSprite_Think(entity this)
963 bool doremove = false;
965 if (this.fade_time && time >= this.teleport_time)
970 if (this.exteriormodeltoclient)
971 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
974 WaypointSprite_Kill(this);
976 this.nextthink = time; // WHY?!?
979 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
981 // personal waypoints
982 if (this.enemy && this.enemy != view)
986 if (this.rule == SPRITERULE_SPECTATOR)
988 if (!autocvar_sv_itemstime)
990 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
993 else if (this.team && this.rule == SPRITERULE_DEFAULT)
995 if (this.team != view.team)
997 if (!IS_PLAYER(view))
1004 entity WaypointSprite_getviewentity(entity e)
1006 if (IS_SPEC(e)) e = e.enemy;
1007 /* TODO idea (check this breaks nothing)
1008 else if (e.classname == "observer")
1014 float WaypointSprite_isteammate(entity e, entity e2)
1017 return e2.team == e.team;
1021 bool WaypointSprite_Customize(entity this, entity client)
1023 // this is not in SendEntity because it shall run every frame, not just every update
1025 // make spectators see what the player would see
1026 entity e = WaypointSprite_getviewentity(client);
1028 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1031 return this.waypointsprite_visible_for_player(this, client, e);
1034 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1036 void WaypointSprite_Reset(entity this)
1038 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1041 WaypointSprite_Kill(this);
1044 entity WaypointSprite_Spawn(
1045 entity spr, // sprite
1046 float _lifetime, float maxdistance, // lifetime, max distance
1047 entity ref, vector ofs, // position
1048 entity showto, float t, // show to whom? Use a flag to indicate a team
1049 entity own, .entity ownfield, // remove when own gets killed
1050 float hideable, // true when it should be controlled by cl_hidewaypoints
1051 entity icon // initial icon
1054 entity wp = new(sprite_waypoint);
1055 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1057 _lifetime = -_lifetime;
1058 wp.teleport_time = time + _lifetime;
1059 wp.exteriormodeltoclient = ref;
1063 setorigin(wp, ref.origin + ofs);
1070 wp.currentammo = hideable;
1074 delete(own.(ownfield));
1075 own.(ownfield) = wp;
1076 wp.owned_by_field = ownfield;
1078 wp.fade_rate = maxdistance;
1079 setthink(wp, WaypointSprite_Think);
1080 wp.nextthink = time;
1081 wp.model1 = spr.netname;
1082 setcefc(wp, WaypointSprite_Customize);
1083 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1084 wp.reset2 = WaypointSprite_Reset;
1086 wp.colormod = spr.m_color;
1087 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1091 entity WaypointSprite_SpawnFixed(
1096 entity icon // initial icon
1099 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1102 entity WaypointSprite_DeployFixed(
1104 float limited_range,
1107 entity icon // initial icon
1117 maxdistance = waypointsprite_limitedrange;
1120 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1123 entity WaypointSprite_DeployPersonal(
1127 entity icon // initial icon
1130 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1133 entity WaypointSprite_Attach(
1136 float limited_range,
1137 entity icon // initial icon
1141 if (player.waypointsprite_attachedforcarrier)
1142 return NULL; // can't attach to FC
1149 maxdistance = waypointsprite_limitedrange;
1152 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1155 entity WaypointSprite_AttachCarrier(
1158 entity icon // initial icon and color
1161 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1162 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1165 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1166 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1171 void WaypointSprite_DetachCarrier(entity carrier)
1173 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1176 void WaypointSprite_ClearPersonal(entity this)
1178 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1181 void WaypointSprite_ClearOwned(entity this)
1183 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1184 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1185 WaypointSprite_Kill(this.waypointsprite_attached);
1188 void WaypointSprite_PlayerDead(entity this)
1190 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1191 WaypointSprite_DetachCarrier(this);
1194 void WaypointSprite_PlayerGone(entity this)
1196 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1197 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1198 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1199 WaypointSprite_DetachCarrier(this);