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, (GetResource(this, RES_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 = bound(0, (this.waypointsprite_helpmetime - time) / 0.1, 255);
86 WriteByte(MSG_ENTITY, dt);
89 WriteByte(MSG_ENTITY, 0);
97 void Ent_WaypointSprite(entity this, bool isnew);
98 NET_HANDLE(waypointsprites, bool isnew) {
99 Ent_WaypointSprite(this, isnew);
103 void Ent_RemoveWaypointSprite(entity this)
105 strfree(this.netname);
106 strfree(this.netname2);
107 strfree(this.netname3);
110 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
111 void Ent_WaypointSprite(entity this, bool isnew)
113 int sendflags = ReadByte();
114 this.wp_extra = ReadByte();
117 this.spawntime = time;
119 this.draw2d = Draw_WaypointSprite;
121 IL_PUSH(g_drawables_2d, this);
122 IL_PUSH(g_radaricons, this);
125 InterpolateOrigin_Undo(this);
126 this.iflags |= IFLAG_ORIGIN;
128 if (sendflags & 0x80)
133 SetResourceExplicit(this, RES_HEALTH, t / 191.0);
134 this.build_finished = 0;
138 t = (t - 192) * 256 + ReadByte();
139 this.build_started = servertime;
140 if (this.build_finished)
141 this.build_starthealth = bound(0, GetResource(this, RES_HEALTH), 1);
143 this.build_starthealth = 0;
144 this.build_finished = servertime + t / 32;
149 SetResourceExplicit(this, RES_HEALTH, -1);
150 this.build_finished = 0;
155 // unfortunately, this needs to be exact (for the 3D display)
156 this.origin = ReadVector();
157 setorigin(this, this.origin);
162 this.team = ReadByte();
163 this.rule = ReadByte();
168 strcpy(this.netname, ReadString());
173 strcpy(this.netname2, ReadString());
178 strcpy(this.netname3, ReadString());
183 this.lifetime = ReadCoord();
184 this.fadetime = ReadCoord();
185 this.maxdistance = ReadShort();
186 this.hideflags = ReadByte();
192 this.teamradar_icon = f & BITS(7);
195 this.(teamradar_times[this.teamradar_time_index]) = time;
196 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
198 this.teamradar_color_x = ReadByte() / 255.0;
199 this.teamradar_color_y = ReadByte() / 255.0;
200 this.teamradar_color_z = ReadByte() / 255.0;
201 this.helpme = ReadByte() * 0.1;
203 this.helpme += servertime;
206 InterpolateOrigin_Note(this);
208 this.entremove = Ent_RemoveWaypointSprite;
213 float spritelookupblinkvalue(entity this, string s)
215 if (s == WP_Weapon.netname) {
216 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
219 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
220 if(s == WP_FlagReturn.netname) return 2;
225 vector spritelookupcolor(entity this, string s, vector def)
227 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
228 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
229 if (MUTATOR_CALLHOOK(WP_Format, this, s))
231 return M_ARGV(2, vector);
236 string spritelookuptext(entity this, string s)
238 if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
239 return "Spam"; // no need to translate this debug string
240 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
241 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
242 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
243 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
244 if (MUTATOR_CALLHOOK(WP_Format, this, s))
246 return M_ARGV(3, string);
249 // need to loop, as our netname could be one of three
250 FOREACH(Waypoints, it.netname == s, {
257 string spritelookupicon(entity this, string s)
259 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
260 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
261 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
262 if (s == WP_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
263 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
264 if (MUTATOR_CALLHOOK(WP_Format, this, s))
266 return M_ARGV(4, string);
269 // need to loop, as our netname could be one of three
270 FOREACH(Waypoints, it.netname == s, {
279 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
281 vector v1, v2, v3, v4;
283 hotspot = -1 * hotspot;
285 // hotspot-relative coordinates of the corners
287 v2 = hotspot + '1 0 0' * sz.x;
288 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
289 v4 = hotspot + '0 1 0' * sz.y;
291 // rotate them, and make them absolute
292 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
293 v1 = Rotate(v1, rot) + org;
294 v2 = Rotate(v2, rot) + org;
295 v3 = Rotate(v3, rot) + org;
296 v4 = Rotate(v4, rot) + org;
299 R_BeginPolygon(pic, f);
300 R_PolygonVertex(v1, '0 0 0', rgb, a);
301 R_PolygonVertex(v2, '1 0 0', rgb, a);
302 R_PolygonVertex(v3, '1 1 0', rgb, a);
303 R_PolygonVertex(v4, '0 1 0', rgb, a);
307 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
309 R_BeginPolygon(pic, f);
310 R_PolygonVertex(o, '0 0 0', rgb, a);
311 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
312 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
313 R_PolygonVertex(o + up, '0 1 0', rgb, a);
317 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)
320 float owidth; // outer width
322 hotspot = -1 * hotspot;
324 // hotspot-relative coordinates of the healthbar corners
329 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
330 o = Rotate(o, rot) + org;
331 ri = Rotate(ri, rot);
332 up = Rotate(up, rot);
334 owidth = width + 2 * border;
335 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
337 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
338 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
339 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
340 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
341 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
344 // returns location of sprite text
345 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
347 float size = 9.0 * t;
348 float border = 1.5 * t;
349 float margin = 4.0 * t;
351 float borderDiag = border * M_SQRT2;
352 vector arrowX = eX * size;
353 vector arrowY = eY * (size+borderDiag);
354 vector borderX = eX * (size+borderDiag);
355 vector borderY = eY * (size+borderDiag+border);
357 R_BeginPolygon("", DRAWFLAG_NORMAL);
358 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
359 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
360 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
361 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
362 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
365 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
366 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
367 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
368 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
371 return o + Rotate(eY * (borderDiag+size+margin), ang);
374 // returns location of sprite healthbar
375 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str)
379 float aspect, sa, ca;
382 sw = stringwidth(str, false, sz);
392 // how do corners work?
393 aspect = vid_conwidth / vid_conheight;
395 ca = cos(ang) * aspect;
396 if (fabs(sa) > fabs(ca))
400 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
405 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
413 // we want to be onscreen
418 if (o.x > vid_conwidth - w)
419 o.x = vid_conwidth - w;
420 if (o.y > vid_conheight - h)
421 o.y = vid_conheight - h;
423 o.x += 0.5 * (w - sw);
426 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
428 drawpic(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
436 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
438 vector yvec = '0.299 0.587 0.114';
439 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
442 vector fixrgbexcess(vector rgb)
445 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
447 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
448 if (rgb.z > 1) rgb.z = 1;
449 } else if (rgb.z > 1) {
450 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
451 if (rgb.y > 1) rgb.y = 1;
453 } else if (rgb.y > 1) {
454 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
456 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
457 if (rgb.z > 1) rgb.z = 1;
458 } else if (rgb.z > 1) {
459 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
460 if (rgb.x > 1) rgb.x = 1;
462 } else if (rgb.z > 1) {
463 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
465 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
466 if (rgb.y > 1) rgb.y = 1;
467 } else if (rgb.y > 1) {
468 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
469 if (rgb.x > 1) rgb.x = 1;
475 void Draw_WaypointSprite(entity this)
477 if (this.lifetime > 0)
478 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
482 if (this.hideflags & 2)
483 return; // radar only
485 if (autocvar_cl_hidewaypoints >= 2)
488 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
489 return; // fixed waypoint
491 InterpolateOrigin_Do(this);
493 float t = entcs_GetTeam(player_localnum) + 1;
494 string spriteimage = "";
499 case SPRITERULE_SPECTATOR:
501 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
502 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
505 spriteimage = this.netname;
507 case SPRITERULE_DEFAULT:
511 spriteimage = this.netname;
516 spriteimage = this.netname;
518 case SPRITERULE_TEAMPLAY:
519 if (t == NUM_SPECTATOR + 1)
520 spriteimage = this.netname3;
521 else if (this.team == t)
522 spriteimage = this.netname2;
524 spriteimage = this.netname;
527 error("Invalid waypointsprite rule!");
531 if (spriteimage == "")
534 ++waypointsprite_newcount;
536 float dist = vlen(this.origin - view_origin);
537 float a = this.alpha * autocvar_hud_panel_fg_alpha;
539 if (this.maxdistance > waypointsprite_normdistance)
540 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent);
541 else if (this.maxdistance > 0)
542 a *= (bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
544 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
547 this.teamradar_color = '1 0 1';
548 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
551 float health_val = GetResource(this, RES_HEALTH);
552 float blink_time = (health_val >= 0) ? (health_val * 10) : time;
553 if (blink_time - floor(blink_time) > 0.5)
555 if (this.helpme && time < this.helpme)
556 a *= SPRITE_HELPME_BLINK;
557 else if (!this.lifetime) // fading out waypoints don't blink
558 a *= spritelookupblinkvalue(this, spriteimage);
570 rgb = fixrgbexcess(rgb);
575 o = project_3d_to_2d(this.origin);
577 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
578 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
579 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
580 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
582 // scale it to be just in view
585 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
586 ang = atan2(-d.x, -d.y);
590 float f1 = d.x / vid_conwidth;
591 float f2 = d.y / vid_conheight;
592 if (f1 == 0) { f1 = 0.000001; }
593 if (f2 == 0) { f2 = 0.000001; }
595 if (max(f1, -f1) > max(f2, -f2)) {
598 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
601 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
606 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
609 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
613 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
621 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
622 ang = atan2(-d.x, -d.y);
627 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
628 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
629 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
630 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
632 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
634 t = waypointsprite_scale;
635 a *= waypointsprite_alpha;
638 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
639 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
641 if (edgedistance_min < waypointsprite_edgefadedistance) {
642 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
643 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
645 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
646 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
647 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
650 if (this.build_finished)
652 if (time < this.build_finished + 0.25)
654 if (time < this.build_started)
655 SetResourceExplicit(this, RES_HEALTH, this.build_starthealth);
656 else if (time < this.build_finished)
657 SetResourceExplicit(this, RES_HEALTH, (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth);
659 SetResourceExplicit(this, RES_HEALTH, 1);
662 SetResourceExplicit(this, RES_HEALTH, -1);
665 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
669 if (!autocvar_g_waypointsprite_text)
671 string spr_icon = spritelookupicon(this, spriteimage);
673 bool icon_found = !(!spr_icon || spr_icon == "");
674 if (icon_found) // it's valid, but let's make sure it exists!
676 pic = strcat(hud_skin_path, "/", spr_icon);
677 if(precache_pic(pic) == "")
679 pic = strcat("gfx/hud/default/", spr_icon);
680 if(!precache_pic(pic))
690 string txt = string_null;
693 txt = spritelookuptext(this, spriteimage);
694 if (this.helpme && time < this.helpme)
695 txt = sprintf(_("%s needing help!"), txt);
696 if (autocvar_g_waypointsprite_uppercase)
697 txt = strtoupper(txt);
699 sz = waypointsprite_fontsize * '1 1 0';
703 // for convenience icon path and color are saved to txt and txt_color
705 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
706 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
709 draw_beginBoldFont();
710 if (GetResource(this, RES_HEALTH) >= 0)
712 float align = 0, marg;
713 if (this.build_finished)
718 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
720 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
722 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
723 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
727 GetResource(this, RES_HEALTH),
730 SPRITE_HEALTHBAR_WIDTH * t,
731 SPRITE_HEALTHBAR_HEIGHT * t,
733 SPRITE_HEALTHBAR_BORDER * t,
736 a * SPRITE_HEALTHBAR_BORDERALPHA,
738 a * SPRITE_HEALTHBAR_HEALTHALPHA,
744 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
750 void WaypointSprite_Load_Frames(string ext)
752 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
754 int ext_len = strlen(ext);
755 int n = search_getsize(dh);
756 for (int i = 0; i < n; ++i)
758 string s = search_getfilename(dh, i);
759 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
761 int o = strstrofs(s, "_frame", 0);
762 string sname = strcat("/spriteframes/", substring(s, 0, o));
763 string sframes = substring(s, o + 6, strlen(s) - o - 6);
764 int f = stof(sframes) + 1;
765 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
770 void WaypointSprite_Load();
771 STATIC_INIT(WaypointSprite_Load) {
772 WaypointSprite_Load();
773 WaypointSprite_Load_Frames(".tga");
774 WaypointSprite_Load_Frames(".jpg");
776 void WaypointSprite_Load()
778 waypointsprite_fadedistance = vlen(mi_scale);
779 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
780 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
781 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
782 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
783 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
784 waypointsprite_scale = autocvar_g_waypointsprite_scale;
785 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
786 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
787 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
788 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
789 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
790 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
791 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
792 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
793 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
794 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
795 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
796 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
797 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
798 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
799 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
801 waypointsprite_count = waypointsprite_newcount;
802 waypointsprite_newcount = 0;
807 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
809 string m1 = _m1.netname;
810 string m2 = _m2.netname;
811 string m3 = _m3.netname;
829 void WaypointSprite_UpdateHealth(entity e, float f)
831 f = bound(0, f, e.max_health);
832 float step = e.max_health / 40;
833 if ((floor(f / step) != floor(GetResource(e, RES_HEALTH) / step)) || e.pain_finished)
835 SetResourceExplicit(e, RES_HEALTH, f);
841 void WaypointSprite_UpdateMaxHealth(entity e, float f)
843 if (f != e.max_health || e.pain_finished)
851 void WaypointSprite_UpdateBuildFinished(entity e, float f)
853 if (f != e.pain_finished || e.max_health)
861 void WaypointSprite_UpdateOrigin(entity e, vector o)
870 void WaypointSprite_UpdateRule(entity e, float t, float r)
872 // no check, as this is never called without doing an actual change (usually only once)
878 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
880 // no check, as this is never called without doing an actual change (usually only once)
882 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
887 void WaypointSprite_Ping(entity e)
890 if (time < e.waypointsprite_pingtime) return;
891 e.waypointsprite_pingtime = time + 0.3;
892 // ALWAYS sends (this causes a radar circle), thus no check
897 void WaypointSprite_HelpMePing(entity e)
899 WaypointSprite_Ping(e);
900 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
904 void WaypointSprite_FadeOutIn(entity e, float t)
909 e.teleport_time = time + t;
911 else if (t < (e.teleport_time - time))
913 // accelerate the waypoint's dying
915 // (e.teleport_time - time) / wp.fade_time stays
916 // e.teleport_time = time + fadetime
917 float current_fadetime = e.teleport_time - time;
918 e.teleport_time = time + t;
920 e.fade_time = -e.fade_time;
921 e.fade_time = e.fade_time * t / current_fadetime;
927 void WaypointSprite_Init()
929 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
930 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
931 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
934 void WaypointSprite_Kill(entity wp)
937 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
941 void WaypointSprite_Disown(entity wp, float fadetime)
944 if (wp.classname != "sprite_waypoint")
946 backtrace("Trying to disown a non-waypointsprite");
951 if (wp.exteriormodeltoclient == wp.owner)
952 wp.exteriormodeltoclient = NULL;
953 wp.owner.(wp.owned_by_field) = NULL;
956 WaypointSprite_FadeOutIn(wp, fadetime);
960 void WaypointSprite_Think(entity this)
962 bool doremove = false;
964 if (this.fade_time && time >= this.teleport_time)
969 if (this.exteriormodeltoclient)
970 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
973 WaypointSprite_Kill(this);
975 this.nextthink = time; // WHY?!?
978 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
980 // personal waypoints
981 if (this.enemy && this.enemy != view)
985 if (this.rule == SPRITERULE_SPECTATOR)
987 if (!autocvar_sv_itemstime)
989 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
992 else if (this.team && this.rule == SPRITERULE_DEFAULT)
994 if (this.team != view.team)
996 if (!IS_PLAYER(view))
1003 entity WaypointSprite_getviewentity(entity e)
1005 if (IS_SPEC(e)) e = e.enemy;
1006 /* TODO idea (check this breaks nothing)
1007 else if (e.classname == "observer")
1013 float WaypointSprite_isteammate(entity e, entity e2)
1016 return e2.team == e.team;
1020 bool WaypointSprite_Customize(entity this, entity client)
1022 // this is not in SendEntity because it shall run every frame, not just every update
1024 // make spectators see what the player would see
1025 entity e = WaypointSprite_getviewentity(client);
1027 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1030 return this.waypointsprite_visible_for_player(this, client, e);
1033 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1035 void WaypointSprite_Reset(entity this)
1037 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1040 WaypointSprite_Kill(this);
1043 entity WaypointSprite_Spawn(
1044 entity spr, // sprite
1045 float _lifetime, float maxdistance, // lifetime, max distance
1046 entity ref, vector ofs, // position
1047 entity showto, float t, // show to whom? Use a flag to indicate a team
1048 entity own, .entity ownfield, // remove when own gets killed
1049 float hideable, // true when it should be controlled by cl_hidewaypoints
1050 entity icon // initial icon
1053 entity wp = new(sprite_waypoint);
1054 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1056 _lifetime = -_lifetime;
1057 wp.teleport_time = time + _lifetime;
1058 wp.exteriormodeltoclient = ref;
1062 setorigin(wp, ref.origin + ofs);
1069 wp.currentammo = hideable;
1073 delete(own.(ownfield));
1074 own.(ownfield) = wp;
1075 wp.owned_by_field = ownfield;
1077 wp.fade_rate = maxdistance;
1078 setthink(wp, WaypointSprite_Think);
1079 wp.nextthink = time;
1080 wp.model1 = spr.netname;
1081 setcefc(wp, WaypointSprite_Customize);
1082 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1083 wp.reset2 = WaypointSprite_Reset;
1085 wp.colormod = spr.m_color;
1086 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1090 entity WaypointSprite_SpawnFixed(
1095 entity icon // initial icon
1098 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1101 entity WaypointSprite_DeployFixed(
1106 entity icon // initial icon
1116 maxdistance = waypointsprite_limitedrange;
1119 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1122 entity WaypointSprite_DeployPersonal(
1126 entity icon // initial icon
1129 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1132 entity WaypointSprite_Attach(
1136 entity icon // initial icon
1140 if (player.waypointsprite_attachedforcarrier)
1141 return NULL; // can't attach to FC
1148 maxdistance = waypointsprite_limitedrange;
1151 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1154 entity WaypointSprite_AttachCarrier(
1157 entity icon // initial icon and color
1160 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1161 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1162 if (GetResource(carrier, RES_HEALTH))
1164 WaypointSprite_UpdateMaxHealth(e, 2 * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
1165 WaypointSprite_UpdateHealth(e, healtharmor_maxdamage(GetResource(carrier, RES_HEALTH), GetResource(carrier, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
1170 void WaypointSprite_DetachCarrier(entity carrier)
1172 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1175 void WaypointSprite_ClearPersonal(entity this)
1177 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1180 void WaypointSprite_ClearOwned(entity this)
1182 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1183 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1184 WaypointSprite_Kill(this.waypointsprite_attached);
1187 void WaypointSprite_PlayerDead(entity this)
1189 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1190 WaypointSprite_DetachCarrier(this);
1193 void WaypointSprite_PlayerGone(entity this)
1195 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1196 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1197 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1198 WaypointSprite_DetachCarrier(this);