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(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
253 return "Spam"; // no need to translate this debug string
254 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
255 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
256 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
257 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
258 if (MUTATOR_CALLHOOK(WP_Format, this, s))
260 return M_ARGV(3, string);
263 // need to loop, as our netname could be one of three
264 FOREACH(Waypoints, it.netname == s, {
271 string spritelookupicon(entity this, string s)
273 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
274 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
275 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
276 if (s == WP_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
277 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
278 if (MUTATOR_CALLHOOK(WP_Format, this, s))
280 return M_ARGV(4, string);
283 // need to loop, as our netname could be one of three
284 FOREACH(Waypoints, it.netname == s, {
293 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
295 vector v1, v2, v3, v4;
297 hotspot = -1 * hotspot;
299 // hotspot-relative coordinates of the corners
301 v2 = hotspot + '1 0 0' * sz.x;
302 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
303 v4 = hotspot + '0 1 0' * sz.y;
305 // rotate them, and make them absolute
306 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
307 v1 = Rotate(v1, rot) + org;
308 v2 = Rotate(v2, rot) + org;
309 v3 = Rotate(v3, rot) + org;
310 v4 = Rotate(v4, rot) + org;
313 R_BeginPolygon(pic, f);
314 R_PolygonVertex(v1, '0 0 0', rgb, a);
315 R_PolygonVertex(v2, '1 0 0', rgb, a);
316 R_PolygonVertex(v3, '1 1 0', rgb, a);
317 R_PolygonVertex(v4, '0 1 0', rgb, a);
321 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
323 R_BeginPolygon(pic, f);
324 R_PolygonVertex(o, '0 0 0', rgb, a);
325 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
326 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
327 R_PolygonVertex(o + up, '0 1 0', rgb, a);
331 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)
334 float owidth; // outer width
336 hotspot = -1 * hotspot;
338 // hotspot-relative coordinates of the healthbar corners
343 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
344 o = Rotate(o, rot) + org;
345 ri = Rotate(ri, rot);
346 up = Rotate(up, rot);
348 owidth = width + 2 * border;
349 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
351 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
352 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
353 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
354 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
355 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
358 // returns location of sprite text
359 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
361 float size = 9.0 * t;
362 float border = 1.5 * t;
363 float margin = 4.0 * t;
365 float borderDiag = border * 1.414;
366 vector arrowX = eX * size;
367 vector arrowY = eY * (size+borderDiag);
368 vector borderX = eX * (size+borderDiag);
369 vector borderY = eY * (size+borderDiag+border);
371 R_BeginPolygon("", DRAWFLAG_NORMAL);
372 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
373 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
374 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
375 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
376 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
379 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
380 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
381 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
382 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
385 return o + Rotate(eY * (borderDiag+size+margin), ang);
388 // returns location of sprite healthbar
389 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str)
393 float aspect, sa, ca;
396 sw = stringwidth(str, false, sz);
406 // how do corners work?
407 aspect = vid_conwidth / vid_conheight;
409 ca = cos(ang) * aspect;
410 if (fabs(sa) > fabs(ca))
414 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
419 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
427 // we want to be onscreen
432 if (o.x > vid_conwidth - w)
433 o.x = vid_conwidth - w;
434 if (o.y > vid_conheight - h)
435 o.y = vid_conheight - h;
437 o.x += 0.5 * (w - sw);
440 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
442 drawpic(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
450 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
452 vector yvec = '0.299 0.587 0.114';
453 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
456 vector fixrgbexcess(vector rgb)
459 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
461 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
462 if (rgb.z > 1) rgb.z = 1;
463 } else if (rgb.z > 1) {
464 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
465 if (rgb.y > 1) rgb.y = 1;
467 } else if (rgb.y > 1) {
468 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
470 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
471 if (rgb.z > 1) rgb.z = 1;
472 } else if (rgb.z > 1) {
473 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
474 if (rgb.x > 1) rgb.x = 1;
476 } else if (rgb.z > 1) {
477 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
479 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
480 if (rgb.y > 1) rgb.y = 1;
481 } else if (rgb.y > 1) {
482 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
483 if (rgb.x > 1) rgb.x = 1;
489 void Draw_WaypointSprite(entity this)
491 if (this.lifetime > 0)
492 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
496 if (this.hideflags & 2)
497 return; // radar only
499 if (autocvar_cl_hidewaypoints >= 2)
502 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
503 return; // fixed waypoint
505 InterpolateOrigin_Do(this);
507 float t = entcs_GetTeam(player_localnum) + 1;
508 string spriteimage = "";
513 case SPRITERULE_SPECTATOR:
515 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
516 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
519 spriteimage = this.netname;
521 case SPRITERULE_DEFAULT:
525 spriteimage = this.netname;
530 spriteimage = this.netname;
532 case SPRITERULE_TEAMPLAY:
533 if (t == NUM_SPECTATOR + 1)
534 spriteimage = this.netname3;
535 else if (this.team == t)
536 spriteimage = this.netname2;
538 spriteimage = this.netname;
541 error("Invalid waypointsprite rule!");
545 if (spriteimage == "")
548 ++waypointsprite_newcount;
550 float dist = vlen(this.origin - view_origin);
551 float a = this.alpha * autocvar_hud_panel_fg_alpha;
553 if (this.maxdistance > waypointsprite_normdistance)
554 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent);
555 else if (this.maxdistance > 0)
556 a *= (bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
558 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
561 this.teamradar_color = '1 0 1';
562 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
565 if (time - floor(time) > 0.5)
567 if (this.helpme && time < this.helpme)
568 a *= SPRITE_HELPME_BLINK;
569 else if (this.lifetime > 0) // fading out waypoints don't blink
570 a *= spritelookupblinkvalue(this, spriteimage);
582 rgb = fixrgbexcess(rgb);
587 o = project_3d_to_2d(this.origin);
589 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
590 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
591 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
592 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
594 // scale it to be just in view
598 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
599 ang = atan2(-d.x, -d.y);
603 f1 = d.x / vid_conwidth;
604 f2 = d.y / vid_conheight;
606 if (max(f1, -f1) > max(f2, -f2)) {
609 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
612 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
617 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
620 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
624 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
632 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
633 ang = atan2(-d.x, -d.y);
638 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
639 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
640 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
641 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
643 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
645 t = waypointsprite_scale;
646 a *= waypointsprite_alpha;
649 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
650 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
652 if (edgedistance_min < waypointsprite_edgefadedistance) {
653 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
654 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
656 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
657 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
658 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
661 if (this.build_finished)
663 if (time < this.build_finished + 0.25)
665 if (time < this.build_started)
666 this.health = this.build_starthealth;
667 else if (time < this.build_finished)
668 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
676 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
680 if (!autocvar_g_waypointsprite_text)
682 string spr_icon = spritelookupicon(this, spriteimage);
684 bool icon_found = !(!spr_icon || spr_icon == "");
685 if (icon_found) // it's valid, but let's make sure it exists!
687 pic = strcat(hud_skin_path, "/", spr_icon);
688 if(precache_pic(pic) == "")
690 pic = strcat("gfx/hud/default/", spr_icon);
691 if(!precache_pic(pic))
701 string txt = string_null;
704 txt = spritelookuptext(this, spriteimage);
705 if (this.helpme && time < this.helpme)
706 txt = sprintf(_("%s needing help!"), txt);
707 if (autocvar_g_waypointsprite_uppercase)
708 txt = strtoupper(txt);
710 sz = waypointsprite_fontsize * '1 1 0';
714 // for convenience icon path and color are saved to txt and txt_color
716 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
717 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
720 draw_beginBoldFont();
721 if (this.health >= 0)
723 float align = 0, marg;
724 if (this.build_finished)
729 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
731 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
733 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
734 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
741 SPRITE_HEALTHBAR_WIDTH * t,
742 SPRITE_HEALTHBAR_HEIGHT * t,
744 SPRITE_HEALTHBAR_BORDER * t,
747 a * SPRITE_HEALTHBAR_BORDERALPHA,
749 a * SPRITE_HEALTHBAR_HEALTHALPHA,
755 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
761 void WaypointSprite_Load_Frames(string ext)
763 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
765 int ext_len = strlen(ext);
766 int n = search_getsize(dh);
767 for (int i = 0; i < n; ++i)
769 string s = search_getfilename(dh, i);
770 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
772 int o = strstrofs(s, "_frame", 0);
773 string sname = strcat("/spriteframes/", substring(s, 0, o));
774 string sframes = substring(s, o + 6, strlen(s) - o - 6);
775 int f = stof(sframes) + 1;
776 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
781 void WaypointSprite_Load();
782 STATIC_INIT(WaypointSprite_Load) {
783 WaypointSprite_Load();
784 WaypointSprite_Load_Frames(".tga");
785 WaypointSprite_Load_Frames(".jpg");
787 void WaypointSprite_Load()
789 waypointsprite_fadedistance = vlen(mi_scale);
790 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
791 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
792 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
793 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
794 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
795 waypointsprite_scale = autocvar_g_waypointsprite_scale;
796 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
797 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
798 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
799 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
800 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
801 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
802 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
803 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
804 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
805 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
806 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
807 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
808 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
809 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
810 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
812 waypointsprite_count = waypointsprite_newcount;
813 waypointsprite_newcount = 0;
818 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
820 string m1 = _m1.netname;
821 string m2 = _m2.netname;
822 string m3 = _m3.netname;
840 void WaypointSprite_UpdateHealth(entity e, float f)
842 f = bound(0, f, e.max_health);
843 if (f != e.health || e.pain_finished)
851 void WaypointSprite_UpdateMaxHealth(entity e, float f)
853 if (f != e.max_health || e.pain_finished)
861 void WaypointSprite_UpdateBuildFinished(entity e, float f)
863 if (f != e.pain_finished || e.max_health)
871 void WaypointSprite_UpdateOrigin(entity e, vector o)
880 void WaypointSprite_UpdateRule(entity e, float t, float r)
882 // no check, as this is never called without doing an actual change (usually only once)
888 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
890 // no check, as this is never called without doing an actual change (usually only once)
892 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
897 void WaypointSprite_Ping(entity e)
900 if (time < e.waypointsprite_pingtime) return;
901 e.waypointsprite_pingtime = time + 0.3;
902 // ALWAYS sends (this causes a radar circle), thus no check
907 void WaypointSprite_HelpMePing(entity e)
909 WaypointSprite_Ping(e);
910 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
914 void WaypointSprite_FadeOutIn(entity e, float t)
919 e.teleport_time = time + t;
921 else if (t < (e.teleport_time - time))
923 // accelerate the waypoint's dying
925 // (e.teleport_time - time) / wp.fade_time stays
926 // e.teleport_time = time + fadetime
927 float current_fadetime = e.teleport_time - time;
928 e.teleport_time = time + t;
930 e.fade_time = -e.fade_time;
931 e.fade_time = e.fade_time * t / current_fadetime;
937 void WaypointSprite_Init()
939 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
940 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
941 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
944 void WaypointSprite_Kill(entity wp)
947 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
951 void WaypointSprite_Disown(entity wp, float fadetime)
954 if (wp.classname != "sprite_waypoint")
956 backtrace("Trying to disown a non-waypointsprite");
961 if (wp.exteriormodeltoclient == wp.owner)
962 wp.exteriormodeltoclient = NULL;
963 wp.owner.(wp.owned_by_field) = NULL;
966 WaypointSprite_FadeOutIn(wp, fadetime);
970 void WaypointSprite_Think(entity this)
972 bool doremove = false;
974 if (this.fade_time && time >= this.teleport_time)
979 if (this.exteriormodeltoclient)
980 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
983 WaypointSprite_Kill(this);
985 this.nextthink = time; // WHY?!?
988 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
990 // personal waypoints
991 if (this.enemy && this.enemy != view)
995 if (this.rule == SPRITERULE_SPECTATOR)
997 if (!autocvar_sv_itemstime)
999 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
1002 else if (this.team && this.rule == SPRITERULE_DEFAULT)
1004 if (this.team != view.team)
1006 if (!IS_PLAYER(view))
1013 entity WaypointSprite_getviewentity(entity e)
1015 if (IS_SPEC(e)) e = e.enemy;
1016 /* TODO idea (check this breaks nothing)
1017 else if (e.classname == "observer")
1023 float WaypointSprite_isteammate(entity e, entity e2)
1026 return e2.team == e.team;
1030 bool WaypointSprite_Customize(entity this, entity client)
1032 // this is not in SendEntity because it shall run every frame, not just every update
1034 // make spectators see what the player would see
1035 entity e = WaypointSprite_getviewentity(client);
1037 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1040 return this.waypointsprite_visible_for_player(this, client, e);
1043 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1045 void WaypointSprite_Reset(entity this)
1047 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1050 WaypointSprite_Kill(this);
1053 entity WaypointSprite_Spawn(
1054 entity spr, // sprite
1055 float _lifetime, float maxdistance, // lifetime, max distance
1056 entity ref, vector ofs, // position
1057 entity showto, float t, // show to whom? Use a flag to indicate a team
1058 entity own, .entity ownfield, // remove when own gets killed
1059 float hideable, // true when it should be controlled by cl_hidewaypoints
1060 entity icon // initial icon
1063 entity wp = new(sprite_waypoint);
1064 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1066 _lifetime = -_lifetime;
1067 wp.teleport_time = time + _lifetime;
1068 wp.exteriormodeltoclient = ref;
1072 setorigin(wp, ref.origin + ofs);
1079 wp.currentammo = hideable;
1083 delete(own.(ownfield));
1084 own.(ownfield) = wp;
1085 wp.owned_by_field = ownfield;
1087 wp.fade_rate = maxdistance;
1088 setthink(wp, WaypointSprite_Think);
1089 wp.nextthink = time;
1090 wp.model1 = spr.netname;
1091 setcefc(wp, WaypointSprite_Customize);
1092 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1093 wp.reset2 = WaypointSprite_Reset;
1095 wp.colormod = spr.m_color;
1096 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1100 entity WaypointSprite_SpawnFixed(
1105 entity icon // initial icon
1108 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1111 entity WaypointSprite_DeployFixed(
1113 float limited_range,
1116 entity icon // initial icon
1126 maxdistance = waypointsprite_limitedrange;
1129 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1132 entity WaypointSprite_DeployPersonal(
1136 entity icon // initial icon
1139 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1142 entity WaypointSprite_Attach(
1145 float limited_range,
1146 entity icon // initial icon
1150 if (player.waypointsprite_attachedforcarrier)
1151 return NULL; // can't attach to FC
1158 maxdistance = waypointsprite_limitedrange;
1161 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1164 entity WaypointSprite_AttachCarrier(
1167 entity icon // initial icon and color
1170 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1171 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1174 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1175 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1180 void WaypointSprite_DetachCarrier(entity carrier)
1182 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1185 void WaypointSprite_ClearPersonal(entity this)
1187 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1190 void WaypointSprite_ClearOwned(entity this)
1192 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1193 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1194 WaypointSprite_Kill(this.waypointsprite_attached);
1197 void WaypointSprite_PlayerDead(entity this)
1199 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1200 WaypointSprite_DetachCarrier(this);
1203 void WaypointSprite_PlayerGone(entity this)
1205 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1206 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1207 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1208 WaypointSprite_DetachCarrier(this);