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
588 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
589 ang = atan2(-d.x, -d.y);
593 f1 = d.x / vid_conwidth;
594 f2 = d.y / vid_conheight;
596 if (max(f1, -f1) > max(f2, -f2)) {
599 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
602 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
607 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
610 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
614 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
622 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
623 ang = atan2(-d.x, -d.y);
628 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
629 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
630 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
631 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
633 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
635 t = waypointsprite_scale;
636 a *= waypointsprite_alpha;
639 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
640 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
642 if (edgedistance_min < waypointsprite_edgefadedistance) {
643 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
644 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
646 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
647 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
648 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
651 if (this.build_finished)
653 if (time < this.build_finished + 0.25)
655 if (time < this.build_started)
656 this.health = this.build_starthealth;
657 else if (time < this.build_finished)
658 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
666 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
670 if (!autocvar_g_waypointsprite_text)
672 string spr_icon = spritelookupicon(this, spriteimage);
674 bool icon_found = !(!spr_icon || spr_icon == "");
675 if (icon_found) // it's valid, but let's make sure it exists!
677 pic = strcat(hud_skin_path, "/", spr_icon);
678 if(precache_pic(pic) == "")
680 pic = strcat("gfx/hud/default/", spr_icon);
681 if(!precache_pic(pic))
691 string txt = string_null;
694 txt = spritelookuptext(this, spriteimage);
695 if (this.helpme && time < this.helpme)
696 txt = sprintf(_("%s needing help!"), txt);
697 if (autocvar_g_waypointsprite_uppercase)
698 txt = strtoupper(txt);
700 sz = waypointsprite_fontsize * '1 1 0';
704 // for convenience icon path and color are saved to txt and txt_color
706 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
707 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
710 draw_beginBoldFont();
711 if (this.health >= 0)
713 float align = 0, marg;
714 if (this.build_finished)
719 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
721 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
723 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
724 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
731 SPRITE_HEALTHBAR_WIDTH * t,
732 SPRITE_HEALTHBAR_HEIGHT * t,
734 SPRITE_HEALTHBAR_BORDER * t,
737 a * SPRITE_HEALTHBAR_BORDERALPHA,
739 a * SPRITE_HEALTHBAR_HEALTHALPHA,
745 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
751 void WaypointSprite_Load_Frames(string ext)
753 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
755 int ext_len = strlen(ext);
756 int n = search_getsize(dh);
757 for (int i = 0; i < n; ++i)
759 string s = search_getfilename(dh, i);
760 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
762 int o = strstrofs(s, "_frame", 0);
763 string sname = strcat("/spriteframes/", substring(s, 0, o));
764 string sframes = substring(s, o + 6, strlen(s) - o - 6);
765 int f = stof(sframes) + 1;
766 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
771 void WaypointSprite_Load();
772 STATIC_INIT(WaypointSprite_Load) {
773 WaypointSprite_Load();
774 WaypointSprite_Load_Frames(".tga");
775 WaypointSprite_Load_Frames(".jpg");
777 void WaypointSprite_Load()
779 waypointsprite_fadedistance = vlen(mi_scale);
780 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
781 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
782 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
783 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
784 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
785 waypointsprite_scale = autocvar_g_waypointsprite_scale;
786 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
787 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
788 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
789 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
790 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
791 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
792 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
793 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
794 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
795 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
796 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
797 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
798 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
799 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
800 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
802 waypointsprite_count = waypointsprite_newcount;
803 waypointsprite_newcount = 0;
808 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
810 string m1 = _m1.netname;
811 string m2 = _m2.netname;
812 string m3 = _m3.netname;
830 void WaypointSprite_UpdateHealth(entity e, float f)
832 f = bound(0, f, e.max_health);
833 if (f != e.health || e.pain_finished)
841 void WaypointSprite_UpdateMaxHealth(entity e, float f)
843 if (f != e.max_health || e.pain_finished)
851 void WaypointSprite_UpdateBuildFinished(entity e, float f)
853 if (f != e.pain_finished || e.max_health)
861 void WaypointSprite_UpdateOrigin(entity e, vector o)
870 void WaypointSprite_UpdateRule(entity e, float t, float r)
872 // no check, as this is never called without doing an actual change (usually only once)
878 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
880 // no check, as this is never called without doing an actual change (usually only once)
882 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
887 void WaypointSprite_Ping(entity e)
890 if (time < e.waypointsprite_pingtime) return;
891 e.waypointsprite_pingtime = time + 0.3;
892 // ALWAYS sends (this causes a radar circle), thus no check
897 void WaypointSprite_HelpMePing(entity e)
899 WaypointSprite_Ping(e);
900 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
904 void WaypointSprite_FadeOutIn(entity e, float t)
909 e.teleport_time = time + t;
911 else if (t < (e.teleport_time - time))
913 // accelerate the waypoint's dying
915 // (e.teleport_time - time) / wp.fade_time stays
916 // e.teleport_time = time + fadetime
917 float current_fadetime = e.teleport_time - time;
918 e.teleport_time = time + t;
920 e.fade_time = -e.fade_time;
921 e.fade_time = e.fade_time * t / current_fadetime;
927 void WaypointSprite_Init()
929 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
930 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
931 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
934 void WaypointSprite_Kill(entity wp)
937 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
941 void WaypointSprite_Disown(entity wp, float fadetime)
944 if (wp.classname != "sprite_waypoint")
946 backtrace("Trying to disown a non-waypointsprite");
951 if (wp.exteriormodeltoclient == wp.owner)
952 wp.exteriormodeltoclient = NULL;
953 wp.owner.(wp.owned_by_field) = NULL;
956 WaypointSprite_FadeOutIn(wp, fadetime);
960 void WaypointSprite_Think(entity this)
962 bool doremove = false;
964 if (this.fade_time && time >= this.teleport_time)
969 if (this.exteriormodeltoclient)
970 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
973 WaypointSprite_Kill(this);
975 this.nextthink = time; // WHY?!?
978 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
980 // personal waypoints
981 if (this.enemy && this.enemy != view)
985 if (this.rule == SPRITERULE_SPECTATOR)
987 if (!autocvar_sv_itemstime)
989 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
992 else if (this.team && this.rule == SPRITERULE_DEFAULT)
994 if (this.team != view.team)
996 if (!IS_PLAYER(view))
1003 entity WaypointSprite_getviewentity(entity e)
1005 if (IS_SPEC(e)) e = e.enemy;
1006 /* TODO idea (check this breaks nothing)
1007 else if (e.classname == "observer")
1013 float WaypointSprite_isteammate(entity e, entity e2)
1016 return e2.team == e.team;
1020 bool WaypointSprite_Customize(entity this, entity client)
1022 // this is not in SendEntity because it shall run every frame, not just every update
1024 // make spectators see what the player would see
1025 entity e = WaypointSprite_getviewentity(client);
1027 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1030 return this.waypointsprite_visible_for_player(this, client, e);
1033 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1035 void WaypointSprite_Reset(entity this)
1037 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1040 WaypointSprite_Kill(this);
1043 entity WaypointSprite_Spawn(
1044 entity spr, // sprite
1045 float _lifetime, float maxdistance, // lifetime, max distance
1046 entity ref, vector ofs, // position
1047 entity showto, float t, // show to whom? Use a flag to indicate a team
1048 entity own, .entity ownfield, // remove when own gets killed
1049 float hideable, // true when it should be controlled by cl_hidewaypoints
1050 entity icon // initial icon
1053 entity wp = new(sprite_waypoint);
1054 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1056 _lifetime = -_lifetime;
1057 wp.teleport_time = time + _lifetime;
1058 wp.exteriormodeltoclient = ref;
1062 setorigin(wp, ref.origin + ofs);
1069 wp.currentammo = hideable;
1073 delete(own.(ownfield));
1074 own.(ownfield) = wp;
1075 wp.owned_by_field = ownfield;
1077 wp.fade_rate = maxdistance;
1078 setthink(wp, WaypointSprite_Think);
1079 wp.nextthink = time;
1080 wp.model1 = spr.netname;
1081 setcefc(wp, WaypointSprite_Customize);
1082 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1083 wp.reset2 = WaypointSprite_Reset;
1085 wp.colormod = spr.m_color;
1086 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1090 entity WaypointSprite_SpawnFixed(
1095 entity icon // initial icon
1098 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1101 entity WaypointSprite_DeployFixed(
1103 float limited_range,
1106 entity icon // initial icon
1116 maxdistance = waypointsprite_limitedrange;
1119 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1122 entity WaypointSprite_DeployPersonal(
1126 entity icon // initial icon
1129 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1132 entity WaypointSprite_Attach(
1135 float limited_range,
1136 entity icon // initial icon
1140 if (player.waypointsprite_attachedforcarrier)
1141 return NULL; // can't attach to FC
1148 maxdistance = waypointsprite_limitedrange;
1151 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1154 entity WaypointSprite_AttachCarrier(
1157 entity icon // initial icon and color
1160 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1161 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1164 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1165 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1170 void WaypointSprite_DetachCarrier(entity carrier)
1172 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1175 void WaypointSprite_ClearPersonal(entity this)
1177 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1180 void WaypointSprite_ClearOwned(entity this)
1182 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1183 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1184 WaypointSprite_Kill(this.waypointsprite_attached);
1187 void WaypointSprite_PlayerDead(entity this)
1189 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1190 WaypointSprite_DetachCarrier(this);
1193 void WaypointSprite_PlayerGone(entity this)
1195 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1196 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1197 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1198 WaypointSprite_DetachCarrier(this);