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 WriteCoord(MSG_ENTITY, this.origin.x);
51 WriteCoord(MSG_ENTITY, this.origin.y);
52 WriteCoord(MSG_ENTITY, this.origin.z);
57 WriteByte(MSG_ENTITY, this.team);
58 WriteByte(MSG_ENTITY, this.rule);
62 WriteString(MSG_ENTITY, this.model1);
65 WriteString(MSG_ENTITY, this.model2);
68 WriteString(MSG_ENTITY, this.model3);
72 WriteCoord(MSG_ENTITY, this.fade_time);
73 WriteCoord(MSG_ENTITY, this.teleport_time);
74 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
75 WriteByte(MSG_ENTITY, f);
80 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
81 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
82 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
83 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
85 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
87 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
92 WriteByte(MSG_ENTITY, dt);
95 WriteByte(MSG_ENTITY, 0);
103 void Ent_WaypointSprite(entity this, bool isnew);
104 NET_HANDLE(waypointsprites, bool isnew) {
105 Ent_WaypointSprite(this, isnew);
109 void Ent_RemoveWaypointSprite(entity this)
111 if (this.netname) strunzone(this.netname);
112 if (this.netname2) strunzone(this.netname2);
113 if (this.netname3) strunzone(this.netname3);
116 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
117 void Ent_WaypointSprite(entity this, bool isnew)
119 int sendflags = ReadByte();
120 this.wp_extra = ReadByte();
123 this.spawntime = time;
125 this.draw2d = Draw_WaypointSprite;
127 IL_PUSH(g_drawables_2d, this);
128 IL_PUSH(g_radaricons, this);
131 InterpolateOrigin_Undo(this);
132 this.iflags |= IFLAG_ORIGIN;
134 if (sendflags & 0x80)
139 this.health = t / 191.0;
140 this.build_finished = 0;
144 t = (t - 192) * 256 + ReadByte();
145 this.build_started = servertime;
146 if (this.build_finished)
147 this.build_starthealth = bound(0, this.health, 1);
149 this.build_starthealth = 0;
150 this.build_finished = servertime + t / 32;
156 this.build_finished = 0;
161 // unfortunately, this needs to be exact (for the 3D display)
162 this.origin_x = ReadCoord();
163 this.origin_y = ReadCoord();
164 this.origin_z = ReadCoord();
165 setorigin(this, this.origin);
170 this.team = ReadByte();
171 this.rule = ReadByte();
177 strunzone(this.netname);
178 this.netname = strzone(ReadString());
184 strunzone(this.netname2);
185 this.netname2 = strzone(ReadString());
191 strunzone(this.netname3);
192 this.netname3 = strzone(ReadString());
197 this.lifetime = ReadCoord();
198 this.fadetime = ReadCoord();
199 this.maxdistance = ReadShort();
200 this.hideflags = ReadByte();
206 this.teamradar_icon = f & BITS(7);
209 this.(teamradar_times[this.teamradar_time_index]) = time;
210 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
212 this.teamradar_color_x = ReadByte() / 255.0;
213 this.teamradar_color_y = ReadByte() / 255.0;
214 this.teamradar_color_z = ReadByte() / 255.0;
215 this.helpme = ReadByte() * 0.1;
217 this.helpme += servertime;
220 InterpolateOrigin_Note(this);
222 this.entremove = Ent_RemoveWaypointSprite;
227 float spritelookupblinkvalue(entity this, string s)
229 if (s == WP_Weapon.netname) {
230 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
233 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
234 if(s == WP_FlagReturn.netname) return 2;
239 vector spritelookupcolor(entity this, string s, vector def)
241 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
242 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
243 if (MUTATOR_CALLHOOK(WP_Format, this, s))
245 return M_ARGV(2, vector);
250 string spritelookuptext(entity this, string s)
252 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
253 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
254 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
255 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
256 if (MUTATOR_CALLHOOK(WP_Format, this, s))
258 return M_ARGV(3, string);
261 // need to loop, as our netname could be one of three
262 FOREACH(Waypoints, it.netname == s, {
269 string spritelookupicon(entity this, string s)
271 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
272 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
273 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
274 if (s == WP_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
275 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
276 if (MUTATOR_CALLHOOK(WP_Format, this, s))
278 return M_ARGV(4, string);
281 // need to loop, as our netname could be one of three
282 FOREACH(Waypoints, it.netname == s, {
291 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
293 vector v1, v2, v3, v4;
295 hotspot = -1 * hotspot;
297 // hotspot-relative coordinates of the corners
299 v2 = hotspot + '1 0 0' * sz.x;
300 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
301 v4 = hotspot + '0 1 0' * sz.y;
303 // rotate them, and make them absolute
304 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
305 v1 = Rotate(v1, rot) + org;
306 v2 = Rotate(v2, rot) + org;
307 v3 = Rotate(v3, rot) + org;
308 v4 = Rotate(v4, rot) + org;
311 R_BeginPolygon(pic, f);
312 R_PolygonVertex(v1, '0 0 0', rgb, a);
313 R_PolygonVertex(v2, '1 0 0', rgb, a);
314 R_PolygonVertex(v3, '1 1 0', rgb, a);
315 R_PolygonVertex(v4, '0 1 0', rgb, a);
319 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
321 R_BeginPolygon(pic, f);
322 R_PolygonVertex(o, '0 0 0', rgb, a);
323 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
324 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
325 R_PolygonVertex(o + up, '0 1 0', rgb, a);
329 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)
332 float owidth; // outer width
334 hotspot = -1 * hotspot;
336 // hotspot-relative coordinates of the healthbar corners
341 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
342 o = Rotate(o, rot) + org;
343 ri = Rotate(ri, rot);
344 up = Rotate(up, rot);
346 owidth = width + 2 * border;
347 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
349 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
350 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
351 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
352 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
353 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
356 // returns location of sprite text
357 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
359 float size = 9.0 * t;
360 float border = 1.5 * t;
361 float margin = 4.0 * t;
363 float borderDiag = border * 1.414;
364 vector arrowX = eX * size;
365 vector arrowY = eY * (size+borderDiag);
366 vector borderX = eX * (size+borderDiag);
367 vector borderY = eY * (size+borderDiag+border);
369 R_BeginPolygon("", DRAWFLAG_NORMAL);
370 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
371 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
372 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
373 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
374 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
377 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
378 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
379 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
380 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
383 return o + Rotate(eY * (borderDiag+size+margin), ang);
386 // returns location of sprite healthbar
387 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str, string pic)
391 float aspect, sa, ca;
394 sw = stringwidth(str, false, sz);
404 // how do corners work?
405 aspect = vid_conwidth / vid_conheight;
407 ca = cos(ang) * aspect;
408 if (fabs(sa) > fabs(ca))
412 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
417 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
425 // we want to be onscreen
430 if (o.x > vid_conwidth - w)
431 o.x = vid_conwidth - w;
432 if (o.y > vid_conheight - h)
433 o.y = vid_conheight - h;
435 o.x += 0.5 * (w - sw);
438 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
440 drawpic(o, pic, sz, rgb, a, DRAWFLAG_NORMAL);
448 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
450 vector yvec = '0.299 0.587 0.114';
451 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
454 vector fixrgbexcess(vector rgb)
457 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
459 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
460 if (rgb.z > 1) rgb.z = 1;
461 } else if (rgb.z > 1) {
462 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
463 if (rgb.y > 1) rgb.y = 1;
465 } else if (rgb.y > 1) {
466 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
468 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
469 if (rgb.z > 1) rgb.z = 1;
470 } else if (rgb.z > 1) {
471 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
472 if (rgb.x > 1) rgb.x = 1;
474 } else if (rgb.z > 1) {
475 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
477 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
478 if (rgb.y > 1) rgb.y = 1;
479 } else if (rgb.y > 1) {
480 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
481 if (rgb.x > 1) rgb.x = 1;
487 void Draw_WaypointSprite(entity this)
489 if (this.lifetime > 0)
490 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
494 if (this.hideflags & 2)
495 return; // radar only
497 if (autocvar_cl_hidewaypoints >= 2)
500 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
501 return; // fixed waypoint
503 InterpolateOrigin_Do(this);
505 float t = entcs_GetTeam(player_localnum) + 1;
506 string spriteimage = "";
511 case SPRITERULE_SPECTATOR:
513 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
514 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
517 spriteimage = this.netname;
519 case SPRITERULE_DEFAULT:
523 spriteimage = this.netname;
528 spriteimage = this.netname;
530 case SPRITERULE_TEAMPLAY:
531 if (t == NUM_SPECTATOR + 1)
532 spriteimage = this.netname3;
533 else if (this.team == t)
534 spriteimage = this.netname2;
536 spriteimage = this.netname;
539 error("Invalid waypointsprite rule!");
543 if (spriteimage == "")
546 ++waypointsprite_newcount;
548 float dist = vlen(this.origin - view_origin);
549 float a = this.alpha * autocvar_hud_panel_fg_alpha;
551 if (this.maxdistance > waypointsprite_normdistance)
552 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent);
553 else if (this.maxdistance > 0)
554 a *= (bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
556 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
559 this.teamradar_color = '1 0 1';
560 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
563 if (time - floor(time) > 0.5)
565 if (this.helpme && time < this.helpme)
566 a *= SPRITE_HELPME_BLINK;
567 else if (this.lifetime > 0) // fading out waypoints don't blink
568 a *= spritelookupblinkvalue(this, spriteimage);
580 rgb = fixrgbexcess(rgb);
585 o = project_3d_to_2d(this.origin);
587 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
588 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
589 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
590 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
592 // scale it to be just in view
596 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
597 ang = atan2(-d.x, -d.y);
601 f1 = d.x / vid_conwidth;
602 f2 = d.y / vid_conheight;
604 if (max(f1, -f1) > max(f2, -f2)) {
607 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
610 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
615 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
618 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
622 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
630 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
631 ang = atan2(-d.x, -d.y);
636 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
637 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
638 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
639 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
641 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
643 t = waypointsprite_scale;
644 a *= waypointsprite_alpha;
647 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
648 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
650 if (edgedistance_min < waypointsprite_edgefadedistance) {
651 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
652 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
654 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
655 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
656 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
659 if (this.build_finished)
661 if (time < this.build_finished + 0.25)
663 if (time < this.build_started)
664 this.health = this.build_starthealth;
665 else if (time < this.build_finished)
666 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
674 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
676 vector iconcolor = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
677 string spr_icon = spritelookupicon(this, spriteimage);
678 string pic = spr_icon;
679 bool icon_found = !(!spr_icon || spr_icon == "");
680 if (icon_found) // it's valid, but let's make sure it exists!
682 pic = strcat(hud_skin_path, "/", spr_icon);
683 if(precache_pic(pic) == "")
685 pic = strcat("gfx/hud/default/", spr_icon);
686 if(!precache_pic(pic))
691 string txt = string_null;
692 if (autocvar_g_waypointsprite_text || !icon_found)
694 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
697 txt = spritelookuptext(this, spriteimage);
698 if (this.helpme && time < this.helpme)
699 txt = sprintf(_("%s needing help!"), txt);
700 if (autocvar_g_waypointsprite_uppercase)
701 txt = strtoupper(txt);
704 draw_beginBoldFont();
705 bool is_text = (autocvar_g_waypointsprite_text || !icon_found);
708 sz = waypointsprite_fontsize * '1 1 0';
710 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
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, iconcolor, a, sz, txt, pic);
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, iconcolor, a, sz, txt, pic);
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);