1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
5 REGISTER_NET_LINKED(waypointsprites)
8 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
9 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
11 WriteHeader(MSG_ENTITY, waypointsprites);
13 sendflags = sendflags & 0x7F;
15 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
19 if(this.currentammo == 1)
21 if(this.exteriormodeltoclient == to)
23 if(this.currentammo == 2)
26 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
27 sendflags = M_ARGV(2, int);
30 WriteByte(MSG_ENTITY, sendflags);
31 WriteByte(MSG_ENTITY, this.wp_extra);
37 WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
41 float dt = this.pain_finished - time;
42 dt = bound(0, dt * 32, 16383);
43 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
44 WriteByte(MSG_ENTITY, (dt & 0x00FF));
50 WriteCoord(MSG_ENTITY, this.origin.x);
51 WriteCoord(MSG_ENTITY, this.origin.y);
52 WriteCoord(MSG_ENTITY, this.origin.z);
57 WriteByte(MSG_ENTITY, this.team);
58 WriteByte(MSG_ENTITY, this.rule);
62 WriteString(MSG_ENTITY, this.model1);
65 WriteString(MSG_ENTITY, this.model2);
68 WriteString(MSG_ENTITY, this.model3);
72 WriteCoord(MSG_ENTITY, this.fade_time);
73 WriteCoord(MSG_ENTITY, this.teleport_time);
74 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
75 WriteByte(MSG_ENTITY, f);
80 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
81 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
82 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
83 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
85 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
87 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
92 WriteByte(MSG_ENTITY, dt);
95 WriteByte(MSG_ENTITY, 0);
103 void Ent_WaypointSprite(entity this, bool isnew);
104 NET_HANDLE(waypointsprites, bool isnew) {
105 Ent_WaypointSprite(this, isnew);
109 void Ent_RemoveWaypointSprite(entity this)
111 if (this.netname) strunzone(this.netname);
112 if (this.netname2) strunzone(this.netname2);
113 if (this.netname3) strunzone(this.netname3);
116 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
117 void Ent_WaypointSprite(entity this, bool isnew)
119 int sendflags = ReadByte();
120 this.wp_extra = ReadByte();
123 this.spawntime = time;
125 this.draw2d = Draw_WaypointSprite;
127 IL_PUSH(g_drawables_2d, this);
128 IL_PUSH(g_radaricons, this);
131 InterpolateOrigin_Undo(this);
132 this.iflags |= IFLAG_ORIGIN;
134 if (sendflags & 0x80)
139 this.health = t / 191.0;
140 this.build_finished = 0;
144 t = (t - 192) * 256 + ReadByte();
145 this.build_started = servertime;
146 if (this.build_finished)
147 this.build_starthealth = bound(0, this.health, 1);
149 this.build_starthealth = 0;
150 this.build_finished = servertime + t / 32;
156 this.build_finished = 0;
161 // unfortunately, this needs to be exact (for the 3D display)
162 this.origin_x = ReadCoord();
163 this.origin_y = ReadCoord();
164 this.origin_z = ReadCoord();
165 setorigin(this, this.origin);
170 this.team = ReadByte();
171 this.rule = ReadByte();
177 strunzone(this.netname);
178 this.netname = strzone(ReadString());
184 strunzone(this.netname2);
185 this.netname2 = strzone(ReadString());
191 strunzone(this.netname3);
192 this.netname3 = strzone(ReadString());
197 this.lifetime = ReadCoord();
198 this.fadetime = ReadCoord();
199 this.maxdistance = ReadShort();
200 this.hideflags = ReadByte();
206 this.teamradar_icon = f & BITS(7);
209 this.(teamradar_times[this.teamradar_time_index]) = time;
210 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
212 this.teamradar_color_x = ReadByte() / 255.0;
213 this.teamradar_color_y = ReadByte() / 255.0;
214 this.teamradar_color_z = ReadByte() / 255.0;
215 this.helpme = ReadByte() * 0.1;
217 this.helpme += servertime;
220 InterpolateOrigin_Note(this);
222 this.entremove = Ent_RemoveWaypointSprite;
227 float spritelookupblinkvalue(entity this, string s)
229 if (s == WP_Weapon.netname) {
230 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
233 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
234 if(s == WP_FlagReturn.netname) return 2;
239 vector spritelookupcolor(entity this, string s, vector def)
241 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
242 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
243 if (MUTATOR_CALLHOOK(WP_Format, this, s))
245 return M_ARGV(2, vector);
250 string spritelookuptext(entity this, string s)
252 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
253 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
254 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
255 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
256 if (MUTATOR_CALLHOOK(WP_Format, this, s))
258 return M_ARGV(3, string);
261 // need to loop, as our netname could be one of three
262 FOREACH(Waypoints, it.netname == s, {
269 string spritelookupicon(entity this, string s)
271 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
272 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
273 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
274 if (s == WP_Buff.netname) return strcat("buff_", Buffs_from(this.wp_extra).m_name);
275 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
276 if (MUTATOR_CALLHOOK(WP_Format, this, s))
278 return M_ARGV(4, string);
281 // need to loop, as our netname could be one of three
282 FOREACH(Waypoints, it.netname == s, {
291 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
293 vector v1, v2, v3, v4;
295 hotspot = -1 * hotspot;
297 // hotspot-relative coordinates of the corners
299 v2 = hotspot + '1 0 0' * sz.x;
300 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
301 v4 = hotspot + '0 1 0' * sz.y;
303 // rotate them, and make them absolute
304 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
305 v1 = Rotate(v1, rot) + org;
306 v2 = Rotate(v2, rot) + org;
307 v3 = Rotate(v3, rot) + org;
308 v4 = Rotate(v4, rot) + org;
311 R_BeginPolygon(pic, f);
312 R_PolygonVertex(v1, '0 0 0', rgb, a);
313 R_PolygonVertex(v2, '1 0 0', rgb, a);
314 R_PolygonVertex(v3, '1 1 0', rgb, a);
315 R_PolygonVertex(v4, '0 1 0', rgb, a);
319 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
321 R_BeginPolygon(pic, f);
322 R_PolygonVertex(o, '0 0 0', rgb, a);
323 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
324 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
325 R_PolygonVertex(o + up, '0 1 0', rgb, a);
329 void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float theheight, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
332 float owidth; // outer width
334 hotspot = -1 * hotspot;
336 // hotspot-relative coordinates of the healthbar corners
341 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
342 o = Rotate(o, rot) + org;
343 ri = Rotate(ri, rot);
344 up = Rotate(up, rot);
346 owidth = width + 2 * border;
347 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
349 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
350 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
351 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
352 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
353 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
356 // returns location of sprite text
357 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
359 float size = 9.0 * t;
360 float border = 1.5 * t;
361 float margin = 4.0 * t;
363 float borderDiag = border * 1.414;
364 vector arrowX = eX * size;
365 vector arrowY = eY * (size+borderDiag);
366 vector borderX = eX * (size+borderDiag);
367 vector borderY = eY * (size+borderDiag+border);
369 R_BeginPolygon("", DRAWFLAG_NORMAL);
370 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
371 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
372 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
373 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
374 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
377 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
378 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
379 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
380 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
383 return o + Rotate(eY * (borderDiag+size+margin), ang);
386 // returns location of sprite healthbar
387 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
391 float aspect, sa, ca;
393 sw = stringwidth(s, false, fontsize);
400 // how do corners work?
401 aspect = vid_conwidth / vid_conheight;
403 ca = cos(ang) * aspect;
404 if (fabs(sa) > fabs(ca))
408 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
413 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
421 // we want to be onscreen
426 if (o.x > vid_conwidth - w)
427 o.x = vid_conwidth - w;
428 if (o.y > vid_conheight - h)
429 o.x = vid_conheight - h;
431 o.x += 0.5 * (w - sw);
433 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
441 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
443 vector yvec = '0.299 0.587 0.114';
444 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
447 vector fixrgbexcess(vector rgb)
450 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
452 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
453 if (rgb.z > 1) rgb.z = 1;
454 } else if (rgb.z > 1) {
455 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
456 if (rgb.y > 1) rgb.y = 1;
458 } else if (rgb.y > 1) {
459 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
461 rgb = fixrgbexcess_move(rgb, '1 0 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', '1 0 0');
465 if (rgb.x > 1) rgb.x = 1;
467 } else if (rgb.z > 1) {
468 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
470 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
471 if (rgb.y > 1) rgb.y = 1;
472 } else if (rgb.y > 1) {
473 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
474 if (rgb.x > 1) rgb.x = 1;
480 void Draw_WaypointSprite(entity this)
482 if (this.lifetime > 0)
483 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
487 if (this.hideflags & 2)
488 return; // radar only
490 if (autocvar_cl_hidewaypoints >= 2)
493 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
494 return; // fixed waypoint
496 InterpolateOrigin_Do(this);
498 float t = entcs_GetTeam(player_localnum) + 1;
499 string spriteimage = "";
504 case SPRITERULE_SPECTATOR:
506 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
507 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
510 spriteimage = this.netname;
512 case SPRITERULE_DEFAULT:
516 spriteimage = this.netname;
521 spriteimage = this.netname;
523 case SPRITERULE_TEAMPLAY:
524 if (t == NUM_SPECTATOR + 1)
525 spriteimage = this.netname3;
526 else if (this.team == t)
527 spriteimage = this.netname2;
529 spriteimage = this.netname;
532 error("Invalid waypointsprite rule!");
536 if (spriteimage == "")
539 ++waypointsprite_newcount;
541 float dist = vlen(this.origin - view_origin);
542 float a = this.alpha * autocvar_hud_panel_fg_alpha;
544 if (this.maxdistance > waypointsprite_normdistance)
545 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent);
546 else if (this.maxdistance > 0)
547 a *= (bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
549 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
552 this.teamradar_color = '1 0 1';
553 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
556 if (time - floor(time) > 0.5)
558 if (this.helpme && time < this.helpme)
559 a *= SPRITE_HELPME_BLINK;
560 else if (this.lifetime > 0) // fading out waypoints don't blink
561 a *= spritelookupblinkvalue(this, spriteimage);
573 rgb = fixrgbexcess(rgb);
578 o = project_3d_to_2d(this.origin);
580 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
581 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
582 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
583 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
585 // scale it to be just in view
589 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
590 ang = atan2(-d.x, -d.y);
594 f1 = d.x / vid_conwidth;
595 f2 = d.y / vid_conheight;
597 if (max(f1, -f1) > max(f2, -f2)) {
600 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
603 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
608 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
611 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
615 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
623 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
624 ang = atan2(-d.x, -d.y);
629 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
630 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
631 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
632 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
634 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
636 t = waypointsprite_scale;
637 a *= waypointsprite_alpha;
640 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
641 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
643 if (edgedistance_min < waypointsprite_edgefadedistance) {
644 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
645 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
647 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
648 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
649 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
652 if (this.build_finished)
654 if (time < this.build_finished + 0.25)
656 if (time < this.build_started)
657 this.health = this.build_starthealth;
658 else if (time < this.build_finished)
659 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
667 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
669 vector iconcolor = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
670 string spr_icon = spritelookupicon(this, spriteimage);
671 string pic = spr_icon;
672 bool icon_found = !(!spr_icon || spr_icon == "");
673 if (icon_found) // it's valid, but let's make sure it exists!
675 pic = strcat(hud_skin_path, "/", spr_icon);
676 if(precache_pic(pic) == "")
678 pic = strcat("gfx/hud/default/", spr_icon);
679 if(!precache_pic(pic))
684 string txt = string_null;
685 if (autocvar_g_waypointsprite_text || !icon_found)
687 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
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);
697 draw_beginBoldFont();
698 if (this.health >= 0)
701 if (this.build_finished)
706 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
708 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
715 SPRITE_HEALTHBAR_WIDTH * t,
716 SPRITE_HEALTHBAR_HEIGHT * t,
718 SPRITE_HEALTHBAR_BORDER * t,
721 a * SPRITE_HEALTHBAR_BORDERALPHA,
723 a * SPRITE_HEALTHBAR_HEALTHALPHA,
727 if(autocvar_g_waypointsprite_text || !icon_found)
728 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
730 drawpic(o - vec2(autocvar_g_waypointsprite_iconsize/2, autocvar_g_waypointsprite_iconsize*t + 2*marg + SPRITE_HEALTHBAR_HEIGHT*t), pic, '1 1 0'*autocvar_g_waypointsprite_iconsize, iconcolor, a, DRAWFLAG_NORMAL);
734 if (autocvar_g_waypointsprite_text || !icon_found)
735 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
737 drawpic(o - vec2(autocvar_g_waypointsprite_iconsize/2, autocvar_g_waypointsprite_iconsize*t + 2 + SPRITE_HEALTHBAR_HEIGHT*t), pic, '1 1 0'*autocvar_g_waypointsprite_iconsize, iconcolor, a, DRAWFLAG_NORMAL);
742 void WaypointSprite_Load_Frames(string ext)
744 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
746 int ext_len = strlen(ext);
747 int n = search_getsize(dh);
748 for (int i = 0; i < n; ++i)
750 string s = search_getfilename(dh, i);
751 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
753 int o = strstrofs(s, "_frame", 0);
754 string sname = strcat("/spriteframes/", substring(s, 0, o));
755 string sframes = substring(s, o + 6, strlen(s) - o - 6);
756 int f = stof(sframes) + 1;
757 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
762 void WaypointSprite_Load();
763 STATIC_INIT(WaypointSprite_Load) {
764 WaypointSprite_Load();
765 WaypointSprite_Load_Frames(".tga");
766 WaypointSprite_Load_Frames(".jpg");
768 void WaypointSprite_Load()
770 waypointsprite_fadedistance = vlen(mi_scale);
771 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
772 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
773 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
774 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
775 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
776 waypointsprite_scale = autocvar_g_waypointsprite_scale;
777 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
778 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
779 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
780 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
781 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
782 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
783 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
784 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
785 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
786 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
787 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
788 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
789 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
790 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
791 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
793 waypointsprite_count = waypointsprite_newcount;
794 waypointsprite_newcount = 0;
799 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
801 string m1 = _m1.netname;
802 string m2 = _m2.netname;
803 string m3 = _m3.netname;
821 void WaypointSprite_UpdateHealth(entity e, float f)
823 f = bound(0, f, e.max_health);
824 if (f != e.health || e.pain_finished)
832 void WaypointSprite_UpdateMaxHealth(entity e, float f)
834 if (f != e.max_health || e.pain_finished)
842 void WaypointSprite_UpdateBuildFinished(entity e, float f)
844 if (f != e.pain_finished || e.max_health)
852 void WaypointSprite_UpdateOrigin(entity e, vector o)
861 void WaypointSprite_UpdateRule(entity e, float t, float r)
863 // no check, as this is never called without doing an actual change (usually only once)
869 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
871 // no check, as this is never called without doing an actual change (usually only once)
873 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
878 void WaypointSprite_Ping(entity e)
881 if (time < e.waypointsprite_pingtime) return;
882 e.waypointsprite_pingtime = time + 0.3;
883 // ALWAYS sends (this causes a radar circle), thus no check
888 void WaypointSprite_HelpMePing(entity e)
890 WaypointSprite_Ping(e);
891 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
895 void WaypointSprite_FadeOutIn(entity e, float t)
900 e.teleport_time = time + t;
902 else if (t < (e.teleport_time - time))
904 // accelerate the waypoint's dying
906 // (e.teleport_time - time) / wp.fade_time stays
907 // e.teleport_time = time + fadetime
908 float current_fadetime = e.teleport_time - time;
909 e.teleport_time = time + t;
911 e.fade_time = -e.fade_time;
912 e.fade_time = e.fade_time * t / current_fadetime;
918 void WaypointSprite_Init()
920 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
921 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
922 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
925 void WaypointSprite_Kill(entity wp)
928 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
932 void WaypointSprite_Disown(entity wp, float fadetime)
935 if (wp.classname != "sprite_waypoint")
937 backtrace("Trying to disown a non-waypointsprite");
942 if (wp.exteriormodeltoclient == wp.owner)
943 wp.exteriormodeltoclient = NULL;
944 wp.owner.(wp.owned_by_field) = NULL;
947 WaypointSprite_FadeOutIn(wp, fadetime);
951 void WaypointSprite_Think(entity this)
953 bool doremove = false;
955 if (this.fade_time && time >= this.teleport_time)
960 if (this.exteriormodeltoclient)
961 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
964 WaypointSprite_Kill(this);
966 this.nextthink = time; // WHY?!?
969 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
971 // personal waypoints
972 if (this.enemy && this.enemy != view)
976 if (this.rule == SPRITERULE_SPECTATOR)
978 if (!autocvar_sv_itemstime)
980 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
983 else if (this.team && this.rule == SPRITERULE_DEFAULT)
985 if (this.team != view.team)
987 if (!IS_PLAYER(view))
994 entity WaypointSprite_getviewentity(entity e)
996 if (IS_SPEC(e)) e = e.enemy;
997 /* TODO idea (check this breaks nothing)
998 else if (e.classname == "observer")
1004 float WaypointSprite_isteammate(entity e, entity e2)
1007 return e2.team == e.team;
1011 bool WaypointSprite_Customize(entity this, entity client)
1013 // this is not in SendEntity because it shall run every frame, not just every update
1015 // make spectators see what the player would see
1016 entity e = WaypointSprite_getviewentity(client);
1018 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1021 return this.waypointsprite_visible_for_player(this, client, e);
1024 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1026 void WaypointSprite_Reset(entity this)
1028 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1031 WaypointSprite_Kill(this);
1034 entity WaypointSprite_Spawn(
1035 entity spr, // sprite
1036 float _lifetime, float maxdistance, // lifetime, max distance
1037 entity ref, vector ofs, // position
1038 entity showto, float t, // show to whom? Use a flag to indicate a team
1039 entity own, .entity ownfield, // remove when own gets killed
1040 float hideable, // true when it should be controlled by cl_hidewaypoints
1041 entity icon // initial icon
1044 entity wp = new(sprite_waypoint);
1045 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1047 _lifetime = -_lifetime;
1048 wp.teleport_time = time + _lifetime;
1049 wp.exteriormodeltoclient = ref;
1053 setorigin(wp, ref.origin + ofs);
1060 wp.currentammo = hideable;
1064 delete(own.(ownfield));
1065 own.(ownfield) = wp;
1066 wp.owned_by_field = ownfield;
1068 wp.fade_rate = maxdistance;
1069 setthink(wp, WaypointSprite_Think);
1070 wp.nextthink = time;
1071 wp.model1 = spr.netname;
1072 setcefc(wp, WaypointSprite_Customize);
1073 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1074 wp.reset2 = WaypointSprite_Reset;
1076 wp.colormod = spr.m_color;
1077 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1081 entity WaypointSprite_SpawnFixed(
1086 entity icon // initial icon
1089 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1092 entity WaypointSprite_DeployFixed(
1094 float limited_range,
1097 entity icon // initial icon
1107 maxdistance = waypointsprite_limitedrange;
1110 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1113 entity WaypointSprite_DeployPersonal(
1117 entity icon // initial icon
1120 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1123 entity WaypointSprite_Attach(
1126 float limited_range,
1127 entity icon // initial icon
1131 if (player.waypointsprite_attachedforcarrier)
1132 return NULL; // can't attach to FC
1139 maxdistance = waypointsprite_limitedrange;
1142 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1145 entity WaypointSprite_AttachCarrier(
1148 entity icon // initial icon and color
1151 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1152 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1155 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1156 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1161 void WaypointSprite_DetachCarrier(entity carrier)
1163 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1166 void WaypointSprite_ClearPersonal(entity this)
1168 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1171 void WaypointSprite_ClearOwned(entity this)
1173 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1174 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1175 WaypointSprite_Kill(this.waypointsprite_attached);
1178 void WaypointSprite_PlayerDead(entity this)
1180 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1181 WaypointSprite_DetachCarrier(this);
1184 void WaypointSprite_PlayerGone(entity this)
1186 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1187 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1188 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1189 WaypointSprite_DetachCarrier(this);