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, (GetResourceAmount(this, RESOURCE_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 SetResourceAmountExplicit(this, RESOURCE_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, GetResourceAmount(this, RESOURCE_HEALTH), 1);
147 this.build_starthealth = 0;
148 this.build_finished = servertime + t / 32;
153 SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
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_text;
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_Weapon.netname) return Weapons_from(this.wp_extra).m_waypoint_icon;
266 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint_icon;
267 if (s == WP_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
268 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
269 if (MUTATOR_CALLHOOK(WP_Format, this, s))
271 return M_ARGV(4, string);
274 // need to loop, as our netname could be one of three
275 FOREACH(Waypoints, it.netname == s, {
284 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
286 vector v1, v2, v3, v4;
288 hotspot = -1 * hotspot;
290 // hotspot-relative coordinates of the corners
292 v2 = hotspot + '1 0 0' * sz.x;
293 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
294 v4 = hotspot + '0 1 0' * sz.y;
296 // rotate them, and make them absolute
297 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
298 v1 = Rotate(v1, rot) + org;
299 v2 = Rotate(v2, rot) + org;
300 v3 = Rotate(v3, rot) + org;
301 v4 = Rotate(v4, rot) + org;
304 R_BeginPolygon(pic, f);
305 R_PolygonVertex(v1, '0 0 0', rgb, a);
306 R_PolygonVertex(v2, '1 0 0', rgb, a);
307 R_PolygonVertex(v3, '1 1 0', rgb, a);
308 R_PolygonVertex(v4, '0 1 0', rgb, a);
312 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
314 R_BeginPolygon(pic, f);
315 R_PolygonVertex(o, '0 0 0', rgb, a);
316 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
317 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
318 R_PolygonVertex(o + up, '0 1 0', rgb, a);
322 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)
325 float owidth; // outer width
327 hotspot = -1 * hotspot;
329 // hotspot-relative coordinates of the healthbar corners
334 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
335 o = Rotate(o, rot) + org;
336 ri = Rotate(ri, rot);
337 up = Rotate(up, rot);
339 owidth = width + 2 * border;
340 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
342 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
343 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
344 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
345 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
346 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
349 // returns location of sprite text
350 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
352 float size = 9.0 * t;
353 float border = 1.5 * t;
354 float margin = 4.0 * t;
356 float borderDiag = border * 1.414;
357 vector arrowX = eX * size;
358 vector arrowY = eY * (size+borderDiag);
359 vector borderX = eX * (size+borderDiag);
360 vector borderY = eY * (size+borderDiag+border);
362 R_BeginPolygon("", DRAWFLAG_NORMAL);
363 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
364 R_PolygonVertex(o + Rotate(arrowY - 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(borderY + borderX, ang), '0 0 0', '0 0 0', a);
367 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
370 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
371 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
372 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
373 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
376 return o + Rotate(eY * (borderDiag+size+margin), ang);
379 // returns location of sprite healthbar
380 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str)
384 float aspect, sa, ca;
387 sw = stringwidth(str, false, sz);
397 // how do corners work?
398 aspect = vid_conwidth / vid_conheight;
400 ca = cos(ang) * aspect;
401 if (fabs(sa) > fabs(ca))
405 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
410 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
418 // we want to be onscreen
423 if (o.x > vid_conwidth - w)
424 o.x = vid_conwidth - w;
425 if (o.y > vid_conheight - h)
426 o.y = vid_conheight - h;
428 o.x += 0.5 * (w - sw);
432 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
436 drawpic(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
437 // https://docs.gimp.org/en/gimp-tool-desaturate.html
438 //float gray = rgb.x * 0.21 + rgb.y * 0.72 + rgb.z * 0.07;
439 //drawpic(o, str, sz, '1 1 1', gray * a, DRAWFLAG_ADDITIVE);
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 float health_val = GetResourceAmount(this, RESOURCE_HEALTH);
564 float blink_time = (health_val >= 0) ? (health_val * 10) : time;
565 if (blink_time - floor(blink_time) > 0.5)
567 if (this.helpme && time < this.helpme)
568 a *= SPRITE_HELPME_BLINK;
569 else if (!this.lifetime) // 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
597 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
598 ang = atan2(-d.x, -d.y);
602 float f1 = d.x / vid_conwidth;
603 float f2 = d.y / vid_conheight;
604 if (f1 == 0) { f1 = 0.000001; }
605 if (f2 == 0) { f2 = 0.000001; }
607 if (max(f1, -f1) > max(f2, -f2)) {
610 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
613 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
618 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
621 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
625 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
633 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
634 ang = atan2(-d.x, -d.y);
639 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
640 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
641 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
642 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
644 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
646 t = waypointsprite_scale;
647 a *= waypointsprite_alpha;
650 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
651 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
653 if (edgedistance_min < waypointsprite_edgefadedistance) {
654 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
655 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
657 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
658 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
659 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
662 if (this.build_finished)
664 if (time < this.build_finished + 0.25)
666 if (time < this.build_started)
667 SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.build_starthealth);
668 else if (time < this.build_finished)
669 SetResourceAmountExplicit(this, RESOURCE_HEALTH, (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth);
671 SetResourceAmountExplicit(this, RESOURCE_HEALTH, 1);
674 SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
677 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
681 if (!autocvar_g_waypointsprite_text)
683 string spr_icon = spritelookupicon(this, spriteimage);
685 bool icon_found = !(!spr_icon || spr_icon == "");
686 if (icon_found) // it's valid, but let's make sure it exists!
688 pic = strcat(hud_skin_path, "/", spr_icon);
689 if(precache_pic(pic) == "")
691 pic = strcat("gfx/hud/default/", spr_icon);
692 if(!precache_pic(pic))
702 string txt = string_null;
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 (GetResourceAmount(this, RESOURCE_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);
739 GetResourceAmount(this, RESOURCE_HEALTH),
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 != GetResourceAmount(e, RESOURCE_HEALTH) || e.pain_finished)
846 SetResourceAmountExplicit(e, RESOURCE_HEALTH, f);
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(
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(
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);
1173 if (GetResourceAmount(carrier, RESOURCE_HEALTH))
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(GetResourceAmount(carrier, RESOURCE_HEALTH), GetResourceAmount(carrier, RESOURCE_ARMOR), 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);