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_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
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 float txt_offset = 0;
700 draw_beginBoldFont();
701 if (this.health >= 0)
703 float align = 0, marg;
704 if (this.build_finished)
709 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
711 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
718 SPRITE_HEALTHBAR_WIDTH * t,
719 SPRITE_HEALTHBAR_HEIGHT * t,
721 SPRITE_HEALTHBAR_BORDER * t,
724 a * SPRITE_HEALTHBAR_BORDERALPHA,
726 a * SPRITE_HEALTHBAR_HEALTHALPHA,
730 txt_offset = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
731 icon_offset = vec2(autocvar_g_waypointsprite_iconsize/2, autocvar_g_waypointsprite_iconsize*t + 2*marg + SPRITE_HEALTHBAR_HEIGHT*t);
734 icon_offset = vec2(autocvar_g_waypointsprite_iconsize/2, autocvar_g_waypointsprite_iconsize*t + 2 + SPRITE_HEALTHBAR_HEIGHT*t);
736 if (autocvar_g_waypointsprite_text || !icon_found)
737 o = drawspritetext(o, ang, txt_offset, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
739 drawpic(o - icon_offset, pic, '1 1 0'*autocvar_g_waypointsprite_iconsize, iconcolor, a, DRAWFLAG_NORMAL);
744 void WaypointSprite_Load_Frames(string ext)
746 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
748 int ext_len = strlen(ext);
749 int n = search_getsize(dh);
750 for (int i = 0; i < n; ++i)
752 string s = search_getfilename(dh, i);
753 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
755 int o = strstrofs(s, "_frame", 0);
756 string sname = strcat("/spriteframes/", substring(s, 0, o));
757 string sframes = substring(s, o + 6, strlen(s) - o - 6);
758 int f = stof(sframes) + 1;
759 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
764 void WaypointSprite_Load();
765 STATIC_INIT(WaypointSprite_Load) {
766 WaypointSprite_Load();
767 WaypointSprite_Load_Frames(".tga");
768 WaypointSprite_Load_Frames(".jpg");
770 void WaypointSprite_Load()
772 waypointsprite_fadedistance = vlen(mi_scale);
773 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
774 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
775 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
776 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
777 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
778 waypointsprite_scale = autocvar_g_waypointsprite_scale;
779 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
780 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
781 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
782 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
783 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
784 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
785 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
786 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
787 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
788 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
789 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
790 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
791 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
792 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
793 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
795 waypointsprite_count = waypointsprite_newcount;
796 waypointsprite_newcount = 0;
801 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
803 string m1 = _m1.netname;
804 string m2 = _m2.netname;
805 string m3 = _m3.netname;
823 void WaypointSprite_UpdateHealth(entity e, float f)
825 f = bound(0, f, e.max_health);
826 if (f != e.health || e.pain_finished)
834 void WaypointSprite_UpdateMaxHealth(entity e, float f)
836 if (f != e.max_health || e.pain_finished)
844 void WaypointSprite_UpdateBuildFinished(entity e, float f)
846 if (f != e.pain_finished || e.max_health)
854 void WaypointSprite_UpdateOrigin(entity e, vector o)
863 void WaypointSprite_UpdateRule(entity e, float t, float r)
865 // no check, as this is never called without doing an actual change (usually only once)
871 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
873 // no check, as this is never called without doing an actual change (usually only once)
875 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
880 void WaypointSprite_Ping(entity e)
883 if (time < e.waypointsprite_pingtime) return;
884 e.waypointsprite_pingtime = time + 0.3;
885 // ALWAYS sends (this causes a radar circle), thus no check
890 void WaypointSprite_HelpMePing(entity e)
892 WaypointSprite_Ping(e);
893 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
897 void WaypointSprite_FadeOutIn(entity e, float t)
902 e.teleport_time = time + t;
904 else if (t < (e.teleport_time - time))
906 // accelerate the waypoint's dying
908 // (e.teleport_time - time) / wp.fade_time stays
909 // e.teleport_time = time + fadetime
910 float current_fadetime = e.teleport_time - time;
911 e.teleport_time = time + t;
913 e.fade_time = -e.fade_time;
914 e.fade_time = e.fade_time * t / current_fadetime;
920 void WaypointSprite_Init()
922 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
923 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
924 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
927 void WaypointSprite_Kill(entity wp)
930 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
934 void WaypointSprite_Disown(entity wp, float fadetime)
937 if (wp.classname != "sprite_waypoint")
939 backtrace("Trying to disown a non-waypointsprite");
944 if (wp.exteriormodeltoclient == wp.owner)
945 wp.exteriormodeltoclient = NULL;
946 wp.owner.(wp.owned_by_field) = NULL;
949 WaypointSprite_FadeOutIn(wp, fadetime);
953 void WaypointSprite_Think(entity this)
955 bool doremove = false;
957 if (this.fade_time && time >= this.teleport_time)
962 if (this.exteriormodeltoclient)
963 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
966 WaypointSprite_Kill(this);
968 this.nextthink = time; // WHY?!?
971 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
973 // personal waypoints
974 if (this.enemy && this.enemy != view)
978 if (this.rule == SPRITERULE_SPECTATOR)
980 if (!autocvar_sv_itemstime)
982 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
985 else if (this.team && this.rule == SPRITERULE_DEFAULT)
987 if (this.team != view.team)
989 if (!IS_PLAYER(view))
996 entity WaypointSprite_getviewentity(entity e)
998 if (IS_SPEC(e)) e = e.enemy;
999 /* TODO idea (check this breaks nothing)
1000 else if (e.classname == "observer")
1006 float WaypointSprite_isteammate(entity e, entity e2)
1009 return e2.team == e.team;
1013 bool WaypointSprite_Customize(entity this, entity client)
1015 // this is not in SendEntity because it shall run every frame, not just every update
1017 // make spectators see what the player would see
1018 entity e = WaypointSprite_getviewentity(client);
1020 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1023 return this.waypointsprite_visible_for_player(this, client, e);
1026 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1028 void WaypointSprite_Reset(entity this)
1030 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1033 WaypointSprite_Kill(this);
1036 entity WaypointSprite_Spawn(
1037 entity spr, // sprite
1038 float _lifetime, float maxdistance, // lifetime, max distance
1039 entity ref, vector ofs, // position
1040 entity showto, float t, // show to whom? Use a flag to indicate a team
1041 entity own, .entity ownfield, // remove when own gets killed
1042 float hideable, // true when it should be controlled by cl_hidewaypoints
1043 entity icon // initial icon
1046 entity wp = new(sprite_waypoint);
1047 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1049 _lifetime = -_lifetime;
1050 wp.teleport_time = time + _lifetime;
1051 wp.exteriormodeltoclient = ref;
1055 setorigin(wp, ref.origin + ofs);
1062 wp.currentammo = hideable;
1066 delete(own.(ownfield));
1067 own.(ownfield) = wp;
1068 wp.owned_by_field = ownfield;
1070 wp.fade_rate = maxdistance;
1071 setthink(wp, WaypointSprite_Think);
1072 wp.nextthink = time;
1073 wp.model1 = spr.netname;
1074 setcefc(wp, WaypointSprite_Customize);
1075 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1076 wp.reset2 = WaypointSprite_Reset;
1078 wp.colormod = spr.m_color;
1079 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1083 entity WaypointSprite_SpawnFixed(
1088 entity icon // initial icon
1091 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1094 entity WaypointSprite_DeployFixed(
1096 float limited_range,
1099 entity icon // initial icon
1109 maxdistance = waypointsprite_limitedrange;
1112 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1115 entity WaypointSprite_DeployPersonal(
1119 entity icon // initial icon
1122 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1125 entity WaypointSprite_Attach(
1128 float limited_range,
1129 entity icon // initial icon
1133 if (player.waypointsprite_attachedforcarrier)
1134 return NULL; // can't attach to FC
1141 maxdistance = waypointsprite_limitedrange;
1144 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1147 entity WaypointSprite_AttachCarrier(
1150 entity icon // initial icon and color
1153 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1154 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1157 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1158 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1163 void WaypointSprite_DetachCarrier(entity carrier)
1165 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1168 void WaypointSprite_ClearPersonal(entity this)
1170 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1173 void WaypointSprite_ClearOwned(entity this)
1175 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1176 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1177 WaypointSprite_Kill(this.waypointsprite_attached);
1180 void WaypointSprite_PlayerDead(entity this)
1182 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1183 WaypointSprite_DetachCarrier(this);
1186 void WaypointSprite_PlayerGone(entity this)
1188 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1189 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1190 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1191 WaypointSprite_DetachCarrier(this);