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)
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, str, 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);
678 if (!autocvar_g_waypointsprite_text)
680 string spr_icon = spritelookupicon(this, spriteimage);
682 bool icon_found = !(!spr_icon || spr_icon == "");
683 if (icon_found) // it's valid, but let's make sure it exists!
685 pic = strcat(hud_skin_path, "/", spr_icon);
686 if(precache_pic(pic) == "")
688 pic = strcat("gfx/hud/default/", spr_icon);
689 if(!precache_pic(pic))
699 string txt = string_null;
702 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
703 txt = "Spam"; // no need to translate this debug string
705 txt = spritelookuptext(this, spriteimage);
706 if (this.helpme && time < this.helpme)
707 txt = sprintf(_("%s needing help!"), txt);
708 if (autocvar_g_waypointsprite_uppercase)
709 txt = strtoupper(txt);
711 sz = waypointsprite_fontsize * '1 1 0';
715 // for convenience icon path and color are saved to txt and txt_color
717 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
718 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
721 draw_beginBoldFont();
722 if (this.health >= 0)
724 float align = 0, marg;
725 if (this.build_finished)
730 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
732 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
734 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
735 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
742 SPRITE_HEALTHBAR_WIDTH * t,
743 SPRITE_HEALTHBAR_HEIGHT * t,
745 SPRITE_HEALTHBAR_BORDER * t,
748 a * SPRITE_HEALTHBAR_BORDERALPHA,
750 a * SPRITE_HEALTHBAR_HEALTHALPHA,
756 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
762 void WaypointSprite_Load_Frames(string ext)
764 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
766 int ext_len = strlen(ext);
767 int n = search_getsize(dh);
768 for (int i = 0; i < n; ++i)
770 string s = search_getfilename(dh, i);
771 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
773 int o = strstrofs(s, "_frame", 0);
774 string sname = strcat("/spriteframes/", substring(s, 0, o));
775 string sframes = substring(s, o + 6, strlen(s) - o - 6);
776 int f = stof(sframes) + 1;
777 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
782 void WaypointSprite_Load();
783 STATIC_INIT(WaypointSprite_Load) {
784 WaypointSprite_Load();
785 WaypointSprite_Load_Frames(".tga");
786 WaypointSprite_Load_Frames(".jpg");
788 void WaypointSprite_Load()
790 waypointsprite_fadedistance = vlen(mi_scale);
791 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
792 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
793 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
794 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
795 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
796 waypointsprite_scale = autocvar_g_waypointsprite_scale;
797 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
798 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
799 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
800 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
801 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
802 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
803 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
804 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
805 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
806 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
807 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
808 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
809 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
810 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
811 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
813 waypointsprite_count = waypointsprite_newcount;
814 waypointsprite_newcount = 0;
819 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
821 string m1 = _m1.netname;
822 string m2 = _m2.netname;
823 string m3 = _m3.netname;
841 void WaypointSprite_UpdateHealth(entity e, float f)
843 f = bound(0, f, e.max_health);
844 if (f != e.health || e.pain_finished)
852 void WaypointSprite_UpdateMaxHealth(entity e, float f)
854 if (f != e.max_health || e.pain_finished)
862 void WaypointSprite_UpdateBuildFinished(entity e, float f)
864 if (f != e.pain_finished || e.max_health)
872 void WaypointSprite_UpdateOrigin(entity e, vector o)
881 void WaypointSprite_UpdateRule(entity e, float t, float r)
883 // no check, as this is never called without doing an actual change (usually only once)
889 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
891 // no check, as this is never called without doing an actual change (usually only once)
893 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
898 void WaypointSprite_Ping(entity e)
901 if (time < e.waypointsprite_pingtime) return;
902 e.waypointsprite_pingtime = time + 0.3;
903 // ALWAYS sends (this causes a radar circle), thus no check
908 void WaypointSprite_HelpMePing(entity e)
910 WaypointSprite_Ping(e);
911 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
915 void WaypointSprite_FadeOutIn(entity e, float t)
920 e.teleport_time = time + t;
922 else if (t < (e.teleport_time - time))
924 // accelerate the waypoint's dying
926 // (e.teleport_time - time) / wp.fade_time stays
927 // e.teleport_time = time + fadetime
928 float current_fadetime = e.teleport_time - time;
929 e.teleport_time = time + t;
931 e.fade_time = -e.fade_time;
932 e.fade_time = e.fade_time * t / current_fadetime;
938 void WaypointSprite_Init()
940 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
941 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
942 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
945 void WaypointSprite_Kill(entity wp)
948 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
952 void WaypointSprite_Disown(entity wp, float fadetime)
955 if (wp.classname != "sprite_waypoint")
957 backtrace("Trying to disown a non-waypointsprite");
962 if (wp.exteriormodeltoclient == wp.owner)
963 wp.exteriormodeltoclient = NULL;
964 wp.owner.(wp.owned_by_field) = NULL;
967 WaypointSprite_FadeOutIn(wp, fadetime);
971 void WaypointSprite_Think(entity this)
973 bool doremove = false;
975 if (this.fade_time && time >= this.teleport_time)
980 if (this.exteriormodeltoclient)
981 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
984 WaypointSprite_Kill(this);
986 this.nextthink = time; // WHY?!?
989 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
991 // personal waypoints
992 if (this.enemy && this.enemy != view)
996 if (this.rule == SPRITERULE_SPECTATOR)
998 if (!autocvar_sv_itemstime)
1000 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
1003 else if (this.team && this.rule == SPRITERULE_DEFAULT)
1005 if (this.team != view.team)
1007 if (!IS_PLAYER(view))
1014 entity WaypointSprite_getviewentity(entity e)
1016 if (IS_SPEC(e)) e = e.enemy;
1017 /* TODO idea (check this breaks nothing)
1018 else if (e.classname == "observer")
1024 float WaypointSprite_isteammate(entity e, entity e2)
1027 return e2.team == e.team;
1031 bool WaypointSprite_Customize(entity this, entity client)
1033 // this is not in SendEntity because it shall run every frame, not just every update
1035 // make spectators see what the player would see
1036 entity e = WaypointSprite_getviewentity(client);
1038 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1041 return this.waypointsprite_visible_for_player(this, client, e);
1044 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1046 void WaypointSprite_Reset(entity this)
1048 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1051 WaypointSprite_Kill(this);
1054 entity WaypointSprite_Spawn(
1055 entity spr, // sprite
1056 float _lifetime, float maxdistance, // lifetime, max distance
1057 entity ref, vector ofs, // position
1058 entity showto, float t, // show to whom? Use a flag to indicate a team
1059 entity own, .entity ownfield, // remove when own gets killed
1060 float hideable, // true when it should be controlled by cl_hidewaypoints
1061 entity icon // initial icon
1064 entity wp = new(sprite_waypoint);
1065 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1067 _lifetime = -_lifetime;
1068 wp.teleport_time = time + _lifetime;
1069 wp.exteriormodeltoclient = ref;
1073 setorigin(wp, ref.origin + ofs);
1080 wp.currentammo = hideable;
1084 delete(own.(ownfield));
1085 own.(ownfield) = wp;
1086 wp.owned_by_field = ownfield;
1088 wp.fade_rate = maxdistance;
1089 setthink(wp, WaypointSprite_Think);
1090 wp.nextthink = time;
1091 wp.model1 = spr.netname;
1092 setcefc(wp, WaypointSprite_Customize);
1093 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1094 wp.reset2 = WaypointSprite_Reset;
1096 wp.colormod = spr.m_color;
1097 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1101 entity WaypointSprite_SpawnFixed(
1106 entity icon // initial icon
1109 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1112 entity WaypointSprite_DeployFixed(
1114 float limited_range,
1117 entity icon // initial icon
1127 maxdistance = waypointsprite_limitedrange;
1130 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1133 entity WaypointSprite_DeployPersonal(
1137 entity icon // initial icon
1140 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1143 entity WaypointSprite_Attach(
1146 float limited_range,
1147 entity icon // initial icon
1151 if (player.waypointsprite_attachedforcarrier)
1152 return NULL; // can't attach to FC
1159 maxdistance = waypointsprite_limitedrange;
1162 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1165 entity WaypointSprite_AttachCarrier(
1168 entity icon // initial icon and color
1171 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1172 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1175 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1176 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1181 void WaypointSprite_DetachCarrier(entity carrier)
1183 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1186 void WaypointSprite_ClearPersonal(entity this)
1188 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1191 void WaypointSprite_ClearOwned(entity this)
1193 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1194 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1195 WaypointSprite_Kill(this.waypointsprite_attached);
1198 void WaypointSprite_PlayerDead(entity this)
1200 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1201 WaypointSprite_DetachCarrier(this);
1204 void WaypointSprite_PlayerGone(entity this)
1206 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1207 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1208 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1209 WaypointSprite_DetachCarrier(this);