1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
5 REGISTER_NET_LINKED(waypointsprites)
8 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
10 WriteHeader(MSG_ENTITY, waypointsprites);
12 sendflags = sendflags & 0x7F;
14 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
18 if(this.currentammo == 1) hide_flags |= 1; // hideable
19 else if(this.currentammo == 2) hide_flags |= 2; // radar only
20 if(this.exteriormodeltoclient == to) hide_flags |= 2; // my own
22 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, hide_flags);
23 sendflags = M_ARGV(2, int);
24 hide_flags = M_ARGV(3, int);
26 WriteByte(MSG_ENTITY, sendflags);
27 WriteByte(MSG_ENTITY, this.wp_extra);
33 WriteByte(MSG_ENTITY, (GetResource(this, RES_HEALTH) / this.max_health) * 191.0);
37 float dt = this.pain_finished - time;
38 dt = bound(0, dt * 32, 16383);
39 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
40 WriteByte(MSG_ENTITY, (dt & 0x00FF));
46 WriteVector(MSG_ENTITY, this.origin);
51 WriteByte(MSG_ENTITY, this.team);
52 WriteByte(MSG_ENTITY, this.rule);
56 WriteString(MSG_ENTITY, this.model1);
59 WriteString(MSG_ENTITY, this.model2);
62 WriteString(MSG_ENTITY, this.model3);
66 WriteCoord(MSG_ENTITY, this.fade_time);
67 WriteCoord(MSG_ENTITY, this.teleport_time);
68 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
69 WriteByte(MSG_ENTITY, hide_flags);
74 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
75 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
76 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
77 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
79 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
81 float dt = bound(0, (this.waypointsprite_helpmetime - time) / 0.1, 255);
82 WriteByte(MSG_ENTITY, dt);
85 WriteByte(MSG_ENTITY, 0);
93 void Ent_WaypointSprite(entity this, bool isnew);
94 NET_HANDLE(waypointsprites, bool isnew) {
95 Ent_WaypointSprite(this, isnew);
99 void Ent_RemoveWaypointSprite(entity this)
101 strfree(this.netname);
102 strfree(this.netname2);
103 strfree(this.netname3);
106 void Ent_WaypointSprite(entity this, bool isnew)
108 int sendflags = ReadByte();
109 this.wp_extra = ReadByte();
112 this.spawntime = time;
114 this.draw2d = Draw_WaypointSprite;
116 IL_PUSH(g_drawables_2d, this);
117 IL_PUSH(g_radaricons, this);
120 InterpolateOrigin_Undo(this);
121 this.iflags |= IFLAG_ORIGIN;
123 if (sendflags & 0x80)
128 SetResourceExplicit(this, RES_HEALTH, t / 191.0);
129 this.build_finished = 0;
133 t = (t - 192) * 256 + ReadByte();
134 this.build_started = servertime;
135 if (this.build_finished)
136 this.build_starthealth = bound(0, GetResource(this, RES_HEALTH), 1);
138 this.build_starthealth = 0;
139 this.build_finished = servertime + t / 32;
144 SetResourceExplicit(this, RES_HEALTH, -1);
145 this.build_finished = 0;
150 // unfortunately, this needs to be exact (for the 3D display)
151 this.origin = ReadVector();
152 setorigin(this, this.origin);
157 this.team = ReadByte();
158 this.rule = ReadByte();
163 strcpy(this.netname, ReadString());
168 strcpy(this.netname2, ReadString());
173 strcpy(this.netname3, ReadString());
178 this.lifetime = ReadCoord();
179 this.fadetime = ReadCoord();
180 this.maxdistance = ReadShort();
181 this.hideflags = ReadByte();
187 this.teamradar_icon = f & BITS(7);
190 this.(teamradar_times[this.teamradar_time_index]) = time;
191 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
193 this.teamradar_color_x = ReadByte() / 255.0;
194 this.teamradar_color_y = ReadByte() / 255.0;
195 this.teamradar_color_z = ReadByte() / 255.0;
196 this.helpme = ReadByte() * 0.1;
198 this.helpme += servertime;
201 InterpolateOrigin_Note(this);
203 this.entremove = Ent_RemoveWaypointSprite;
208 float spritelookupblinkvalue(entity this, string s)
210 if (s == WP_Weapon.netname) {
211 if (REGISTRY_GET(Weapons, this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
214 if (s == WP_Item.netname) return REGISTRY_GET(Items, this.wp_extra).m_waypointblink;
215 if(s == WP_FlagReturn.netname) return 2;
220 vector spritelookupcolor(entity this, string s, vector def)
222 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return REGISTRY_GET(Weapons, this.wp_extra).wpcolor;
223 if (s == WP_Item.netname || s == RADARICON_Item.netname) return REGISTRY_GET(Items, this.wp_extra).m_color;
224 if (MUTATOR_CALLHOOK(WP_Format, this, s))
226 return M_ARGV(2, vector);
231 string spritelookuptext(entity this, string s)
233 if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
234 return "Spam"; // no need to translate this debug string
235 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
236 if (s == WP_Weapon.netname) return REGISTRY_GET(Weapons, this.wp_extra).m_name;
237 if (s == WP_Item.netname) return REGISTRY_GET(Items, this.wp_extra).m_waypoint;
238 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
239 if (MUTATOR_CALLHOOK(WP_Format, this, s))
241 return M_ARGV(3, string);
244 // need to loop, as our netname could be one of three
245 FOREACH(Waypoints, it.netname == s, {
252 string spritelookupicon(entity this, string s)
254 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
255 if (s == WP_Weapon.netname) return REGISTRY_GET(Weapons, this.wp_extra).model2;
256 if (s == WP_Item.netname) return REGISTRY_GET(Items, this.wp_extra).m_icon;
257 if (s == WP_Vehicle.netname) return REGISTRY_GET(Vehicles, this.wp_extra).m_icon;
258 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
259 if (MUTATOR_CALLHOOK(WP_Format, this, s))
261 return M_ARGV(4, string);
264 // need to loop, as our netname could be one of three
265 FOREACH(Waypoints, it.netname == s, {
274 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
276 vector v1, v2, v3, v4;
278 hotspot = -1 * hotspot;
280 // hotspot-relative coordinates of the corners
282 v2 = hotspot + '1 0 0' * sz.x;
283 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
284 v4 = hotspot + '0 1 0' * sz.y;
286 // rotate them, and make them absolute
287 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
288 v1 = Rotate(v1, rot) + org;
289 v2 = Rotate(v2, rot) + org;
290 v3 = Rotate(v3, rot) + org;
291 v4 = Rotate(v4, rot) + org;
294 R_BeginPolygon(pic, f, true);
295 R_PolygonVertex(v1, '0 0 0', rgb, a);
296 R_PolygonVertex(v2, '1 0 0', rgb, a);
297 R_PolygonVertex(v3, '1 1 0', rgb, a);
298 R_PolygonVertex(v4, '0 1 0', rgb, a);
302 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
304 R_BeginPolygon(pic, f, true);
305 R_PolygonVertex(o, '0 0 0', rgb, a);
306 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
307 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
308 R_PolygonVertex(o + up, '0 1 0', rgb, a);
312 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)
315 float owidth; // outer width
317 hotspot = -1 * hotspot;
319 // hotspot-relative coordinates of the healthbar corners
324 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
325 o = Rotate(o, rot) + org;
326 ri = Rotate(ri, rot);
327 up = Rotate(up, rot);
329 owidth = width + 2 * border;
330 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
332 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
333 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
334 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
335 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
336 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
339 // returns location of sprite text
340 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
342 float size = 9.0 * t;
343 float border = 1.5 * t;
344 float margin = 4.0 * t;
346 float borderDiag = border * M_SQRT2;
347 vector arrowX = eX * size;
348 vector arrowY = eY * (size+borderDiag);
349 vector borderX = eX * (size+borderDiag);
350 vector borderY = eY * (size+borderDiag+border);
352 R_BeginPolygon("", DRAWFLAG_NORMAL, true);
353 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
354 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
355 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
356 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
357 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
360 R_BeginPolygon("", DRAWFLAG_ADDITIVE, true);
361 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
362 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
363 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
366 return o + Rotate(eY * (borderDiag+size+margin), ang);
369 // returns location of sprite healthbar
370 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str)
374 float aspect, sa, ca;
377 sw = stringwidth(str, false, sz);
387 // how do corners work?
388 aspect = vid_conwidth / vid_conheight;
390 ca = cos(ang) * aspect;
391 if (fabs(sa) > fabs(ca))
395 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
400 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
408 // we want to be onscreen
413 if (o.x > vid_conwidth - w)
414 o.x = vid_conwidth - w;
415 if (o.y > vid_conheight - h)
416 o.y = vid_conheight - h;
418 o.x += 0.5 * (w - sw);
421 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
423 drawpic(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
431 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
433 vector yvec = '0.299 0.587 0.114';
434 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
437 vector fixrgbexcess(vector rgb)
440 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
442 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
443 if (rgb.z > 1) rgb.z = 1;
444 } else if (rgb.z > 1) {
445 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
446 if (rgb.y > 1) rgb.y = 1;
448 } else if (rgb.y > 1) {
449 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
451 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
452 if (rgb.z > 1) rgb.z = 1;
453 } else if (rgb.z > 1) {
454 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
455 if (rgb.x > 1) rgb.x = 1;
457 } else if (rgb.z > 1) {
458 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
460 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
461 if (rgb.y > 1) rgb.y = 1;
462 } else if (rgb.y > 1) {
463 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
464 if (rgb.x > 1) rgb.x = 1;
470 void Draw_WaypointSprite(entity this)
472 if (this.lifetime > 0)
473 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
477 if (this.hideflags & 2)
478 return; // radar only
480 if (autocvar_cl_hidewaypoints >= 2)
483 if ((this.hideflags & 1) && autocvar_cl_hidewaypoints)
484 return; // fixed waypoint
486 InterpolateOrigin_Do(this);
488 float t = entcs_GetTeam(player_localnum) + 1;
489 string spriteimage = "";
494 case SPRITERULE_SPECTATOR:
496 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
497 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
500 spriteimage = this.netname;
502 case SPRITERULE_DEFAULT:
506 spriteimage = this.netname;
511 spriteimage = this.netname;
513 case SPRITERULE_TEAMPLAY:
514 if (t == NUM_SPECTATOR + 1)
515 spriteimage = this.netname3;
516 else if (this.team == t)
517 spriteimage = this.netname2;
519 spriteimage = this.netname;
522 error("Invalid waypointsprite rule!");
526 if (spriteimage == "")
529 ++waypointsprite_newcount;
531 float dist = vlen(this.origin - view_origin);
532 float a = this.alpha * autocvar_hud_panel_fg_alpha;
534 if(this.maxdistance > 0)
536 // restrict maximum normal distance to the waypoint's maximum distance to prevent exploiting cvars
537 float maxnormdistance = bound(0, waypointsprite_normdistance, this.maxdistance - 1);
538 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - maxnormdistance), 1) ** waypointsprite_distancealphaexponent);
541 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
544 this.teamradar_color = '1 0 1';
545 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
548 float health_val = GetResource(this, RES_HEALTH);
549 float blink_time = (health_val >= 0) ? (health_val * 10) : time;
550 if (blink_time - floor(blink_time) > 0.5)
552 if (this.helpme && time < this.helpme)
553 a *= SPRITE_HELPME_BLINK;
554 else if (!this.lifetime) // fading out waypoints don't blink
555 a *= spritelookupblinkvalue(this, spriteimage);
567 rgb = fixrgbexcess(rgb);
572 o = project_3d_to_2d(this.origin);
574 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
575 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
576 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
577 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
579 // scale it to be just in view
582 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
583 ang = atan2(-d.x, -d.y);
587 float f1 = d.x / vid_conwidth;
588 float f2 = d.y / vid_conheight;
589 if (f1 == 0) { f1 = 0.000001; }
590 if (f2 == 0) { f2 = 0.000001; }
592 if (max(f1, -f1) > max(f2, -f2)) {
595 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
598 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
603 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
606 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
610 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
618 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
619 ang = atan2(-d.x, -d.y);
624 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
625 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
626 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
627 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
629 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
631 t = waypointsprite_scale;
632 a *= waypointsprite_alpha;
635 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
636 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
638 if (edgedistance_min < waypointsprite_edgefadedistance) {
639 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
640 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
642 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
643 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
644 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
647 if (this.build_finished)
649 if (time < this.build_finished + 0.25)
651 if (time < this.build_started)
652 SetResourceExplicit(this, RES_HEALTH, this.build_starthealth);
653 else if (time < this.build_finished)
654 SetResourceExplicit(this, RES_HEALTH, (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth);
656 SetResourceExplicit(this, RES_HEALTH, 1);
659 SetResourceExplicit(this, RES_HEALTH, -1);
662 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
666 if (!autocvar_g_waypointsprite_text)
668 string spr_icon = spritelookupicon(this, spriteimage);
670 bool icon_found = !(!spr_icon || spr_icon == "");
671 if (icon_found) // it's valid, but let's make sure it exists!
673 pic = strcat(hud_skin_path, "/", spr_icon);
674 if(precache_pic(pic) == "")
676 pic = strcat("gfx/hud/default/", spr_icon);
677 if(!precache_pic(pic))
687 string txt = string_null;
690 txt = spritelookuptext(this, spriteimage);
691 if (this.helpme && time < this.helpme)
692 txt = sprintf(_("%s needing help!"), txt);
693 if (autocvar_g_waypointsprite_uppercase)
694 txt = strtoupper(txt);
696 sz = waypointsprite_fontsize * '1 1 0';
700 // for convenience icon path and color are saved to txt and txt_color
702 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
703 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
706 draw_beginBoldFont();
707 if (GetResource(this, RES_HEALTH) >= 0)
709 float align = 0, marg;
710 if (this.build_finished)
715 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
717 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
719 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
720 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
724 GetResource(this, RES_HEALTH),
727 SPRITE_HEALTHBAR_WIDTH * t,
728 SPRITE_HEALTHBAR_HEIGHT * t,
730 SPRITE_HEALTHBAR_BORDER * t,
733 a * SPRITE_HEALTHBAR_BORDERALPHA,
735 a * SPRITE_HEALTHBAR_HEALTHALPHA,
741 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
747 void WaypointSprite_Load_Frames(string ext)
749 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
751 int ext_len = strlen(ext);
752 int n = search_getsize(dh);
753 for (int i = 0; i < n; ++i)
755 string s = search_getfilename(dh, i);
756 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
758 int o = strstrofs(s, "_frame", 0);
759 string sname = strcat("/spriteframes/", substring(s, 0, o));
760 string sframes = substring(s, o + 6, strlen(s) - o - 6);
761 int f = stof(sframes) + 1;
762 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
767 void WaypointSprite_Load();
768 STATIC_INIT(WaypointSprite_Load) {
769 WaypointSprite_Load();
770 WaypointSprite_Load_Frames(".tga");
771 WaypointSprite_Load_Frames(".jpg");
773 void WaypointSprite_Load()
775 waypointsprite_fadedistance = vlen(mi_scale);
776 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
777 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
778 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
779 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
780 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
781 waypointsprite_scale = autocvar_g_waypointsprite_scale;
782 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
783 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
784 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
785 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
786 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
787 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
788 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
789 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
790 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
791 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
792 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
793 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
794 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
795 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
796 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
798 waypointsprite_count = waypointsprite_newcount;
799 waypointsprite_newcount = 0;
804 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
806 string m1 = _m1.netname;
807 string m2 = _m2.netname;
808 string m3 = _m3.netname;
826 void WaypointSprite_UpdateHealth(entity e, float f)
828 f = bound(0, f, e.max_health);
829 float step = e.max_health / 40;
830 if ((floor(f / step) != floor(GetResource(e, RES_HEALTH) / step)) || e.pain_finished)
832 SetResourceExplicit(e, RES_HEALTH, f);
838 void WaypointSprite_UpdateMaxHealth(entity e, float f)
840 if (f != e.max_health || e.pain_finished)
848 void WaypointSprite_UpdateBuildFinished(entity e, float f)
850 if (f != e.pain_finished || e.max_health)
858 void WaypointSprite_UpdateOrigin(entity e, vector o)
867 void WaypointSprite_UpdateRule(entity e, float t, float r)
869 // no check, as this is never called without doing an actual change (usually only once)
875 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
877 // no check, as this is never called without doing an actual change (usually only once)
879 int new_cnt = (e.cnt & BIT(7)) | (i & BITS(7));
880 if (new_cnt != e.cnt || col != e.colormod)
888 void WaypointSprite_Ping(entity e)
891 if (time < e.waypointsprite_pingtime) return;
892 e.waypointsprite_pingtime = time + 0.3;
893 // ALWAYS sends (this causes a radar circle), thus no check
898 void WaypointSprite_HelpMePing(entity e)
900 WaypointSprite_Ping(e);
901 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
905 void WaypointSprite_FadeOutIn(entity e, float t)
910 e.teleport_time = time + t;
912 else if (t < (e.teleport_time - time))
914 // accelerate the waypoint's dying
916 // (e.teleport_time - time) / wp.fade_time stays
917 // e.teleport_time = time + fadetime
918 float current_fadetime = e.teleport_time - time;
919 e.teleport_time = time + t;
921 e.fade_time = -e.fade_time;
922 e.fade_time = e.fade_time * t / current_fadetime;
928 void WaypointSprite_Init()
930 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
931 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
932 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
935 void WaypointSprite_Kill(entity wp)
938 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
942 void WaypointSprite_Disown(entity wp, float fadetime)
945 if (wp.classname != "sprite_waypoint")
947 backtrace("Trying to disown a non-waypointsprite");
952 if (wp.exteriormodeltoclient == wp.owner)
953 wp.exteriormodeltoclient = NULL;
954 wp.owner.(wp.owned_by_field) = NULL;
957 WaypointSprite_FadeOutIn(wp, fadetime);
961 void WaypointSprite_Think(entity this)
963 bool doremove = false;
965 if (this.fade_time && time >= this.teleport_time)
970 if (this.exteriormodeltoclient)
971 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
974 WaypointSprite_Kill(this);
976 this.nextthink = time; // WHY?!?
979 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
981 // personal waypoints
982 if (this.enemy && this.enemy != view)
986 if (this.rule == SPRITERULE_SPECTATOR)
988 if (!autocvar_sv_itemstime)
990 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
993 else if (this.team && this.rule == SPRITERULE_DEFAULT)
995 if (this.team != view.team)
997 if (!IS_PLAYER(view))
1004 entity WaypointSprite_getviewentity(entity e)
1006 if (IS_SPEC(e)) e = e.enemy;
1007 /* TODO idea (check this breaks nothing)
1008 else if (e.classname == "observer")
1014 float WaypointSprite_isteammate(entity e, entity e2)
1017 return e2.team == e.team;
1021 bool WaypointSprite_Customize(entity this, entity client)
1023 // this is not in SendEntity because it shall run every frame, not just every update
1025 // make spectators see what the player would see
1026 entity e = WaypointSprite_getviewentity(client);
1028 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1031 return this.waypointsprite_visible_for_player(this, client, e);
1034 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1036 void WaypointSprite_Reset(entity this)
1038 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1041 WaypointSprite_Kill(this);
1044 entity WaypointSprite_Spawn(
1045 entity spr, // sprite
1046 float _lifetime, float maxdistance, // lifetime, max distance
1047 entity ref, vector ofs, // position
1048 entity showto, float t, // show to whom? Use a flag to indicate a team
1049 entity own, .entity ownfield, // remove when own gets killed
1050 float hideable, // true when it should be controlled by cl_hidewaypoints
1051 entity icon // initial icon
1054 entity wp = new(sprite_waypoint);
1055 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1057 _lifetime = -_lifetime;
1058 wp.teleport_time = time + _lifetime;
1059 wp.exteriormodeltoclient = ref;
1063 setorigin(wp, ref.origin + ofs);
1070 wp.currentammo = hideable;
1074 delete(own.(ownfield));
1075 own.(ownfield) = wp;
1076 wp.owned_by_field = ownfield;
1078 wp.fade_rate = maxdistance;
1079 setthink(wp, WaypointSprite_Think);
1080 wp.nextthink = time;
1081 wp.model1 = spr.netname;
1082 setcefc(wp, WaypointSprite_Customize);
1083 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1084 wp.reset2 = WaypointSprite_Reset;
1086 wp.colormod = spr.m_color;
1087 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1091 entity WaypointSprite_SpawnFixed(
1096 entity icon // initial icon
1099 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1102 entity WaypointSprite_DeployFixed(
1107 entity icon // initial icon
1117 maxdistance = waypointsprite_limitedrange;
1120 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1123 entity WaypointSprite_DeployPersonal(
1127 entity icon // initial icon
1130 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1133 entity WaypointSprite_Attach(
1137 entity icon // initial icon
1141 if (player.waypointsprite_attachedforcarrier)
1142 return NULL; // can't attach to FC
1149 maxdistance = waypointsprite_limitedrange;
1152 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1155 entity WaypointSprite_AttachCarrier(
1158 entity icon // initial icon and color
1161 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1162 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1163 if (GetResource(carrier, RES_HEALTH))
1165 WaypointSprite_UpdateMaxHealth(e, 2 * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
1166 WaypointSprite_UpdateHealth(e, healtharmor_maxdamage(GetResource(carrier, RES_HEALTH), GetResource(carrier, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
1171 void WaypointSprite_DetachCarrier(entity carrier)
1173 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1176 void WaypointSprite_ClearPersonal(entity this)
1178 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1181 void WaypointSprite_ClearOwned(entity this)
1183 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1184 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1185 WaypointSprite_Kill(this.waypointsprite_attached);
1188 void WaypointSprite_PlayerDead(entity this)
1190 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1191 WaypointSprite_DetachCarrier(this);
1194 void WaypointSprite_PlayerGone(entity this)
1196 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1197 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1198 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1199 WaypointSprite_DetachCarrier(this);