1 #include "waypointsprites.qh"
5 REGISTER_MUTATOR(waypointsprites, true);
7 REGISTER_NET_LINKED(waypointsprites)
10 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
11 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
13 WriteHeader(MSG_ENTITY, waypointsprites);
15 sendflags = sendflags & 0x7F;
17 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
23 if(this.exteriormodeltoclient == to)
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);
104 NET_HANDLE(waypointsprites, bool isnew) {
105 Ent_WaypointSprite(this);
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)
119 int sendflags = ReadByte();
120 this.wp_extra = ReadByte();
123 this.spawntime = time;
125 this.draw2d = Draw_WaypointSprite;
127 InterpolateOrigin_Undo(this);
128 this.iflags |= IFLAG_ORIGIN;
130 if (sendflags & 0x80)
135 this.health = t / 191.0;
136 this.build_finished = 0;
140 t = (t - 192) * 256 + ReadByte();
141 this.build_started = servertime;
142 if (this.build_finished)
143 this.build_starthealth = bound(0, this.health, 1);
145 this.build_starthealth = 0;
146 this.build_finished = servertime + t / 32;
152 this.build_finished = 0;
157 // unfortunately, this needs to be exact (for the 3D display)
158 this.origin_x = ReadCoord();
159 this.origin_y = ReadCoord();
160 this.origin_z = ReadCoord();
161 setorigin(this, this.origin);
166 this.team = ReadByte();
167 this.rule = ReadByte();
173 strunzone(this.netname);
174 this.netname = strzone(ReadString());
180 strunzone(this.netname2);
181 this.netname2 = strzone(ReadString());
187 strunzone(this.netname3);
188 this.netname3 = strzone(ReadString());
193 this.lifetime = ReadCoord();
194 this.fadetime = ReadCoord();
195 this.maxdistance = ReadShort();
196 this.hideflags = ReadByte();
202 this.teamradar_icon = f & BITS(7);
205 this.(teamradar_times[this.teamradar_time_index]) = time;
206 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
208 this.teamradar_color_x = ReadByte() / 255.0;
209 this.teamradar_color_y = ReadByte() / 255.0;
210 this.teamradar_color_z = ReadByte() / 255.0;
211 this.helpme = ReadByte() * 0.1;
213 this.helpme += servertime;
216 InterpolateOrigin_Note(this);
218 this.entremove = Ent_RemoveWaypointSprite;
223 float spritelookupblinkvalue(entity this, string s)
225 if (s == WP_Weapon.netname) {
226 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
229 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
234 vector spritelookupcolor(entity this, string s, vector def)
236 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
237 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
238 if (MUTATOR_CALLHOOK(WP_Format, this, s))
240 return M_ARGV(2, vector);
245 string spritelookuptext(entity this, string s)
247 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
248 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
249 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
250 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
251 if (MUTATOR_CALLHOOK(WP_Format, this, s))
253 return M_ARGV(3, string);
256 // need to loop, as our netname could be one of three
257 FOREACH(Waypoints, it.netname == s, LAMBDA(
266 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
268 vector v1, v2, v3, v4;
270 hotspot = -1 * hotspot;
272 // hotspot-relative coordinates of the corners
274 v2 = hotspot + '1 0 0' * sz.x;
275 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
276 v4 = hotspot + '0 1 0' * sz.y;
278 // rotate them, and make them absolute
279 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
280 v1 = rotate(v1, rot) + org;
281 v2 = rotate(v2, rot) + org;
282 v3 = rotate(v3, rot) + org;
283 v4 = rotate(v4, rot) + org;
286 R_BeginPolygon(pic, f);
287 R_PolygonVertex(v1, '0 0 0', rgb, a);
288 R_PolygonVertex(v2, '1 0 0', rgb, a);
289 R_PolygonVertex(v3, '1 1 0', rgb, a);
290 R_PolygonVertex(v4, '0 1 0', rgb, a);
294 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
296 R_BeginPolygon(pic, f);
297 R_PolygonVertex(o, '0 0 0', rgb, a);
298 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
299 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
300 R_PolygonVertex(o + up, '0 1 0', rgb, a);
304 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)
307 float owidth; // outer width
309 hotspot = -1 * hotspot;
311 // hotspot-relative coordinates of the healthbar corners
316 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
317 o = rotate(o, rot) + org;
318 ri = rotate(ri, rot);
319 up = rotate(up, rot);
321 owidth = width + 2 * border;
322 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
324 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
325 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
326 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
327 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
328 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
331 // returns location of sprite text
332 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
334 float size = 9.0 * t;
335 float border = 1.5 * t;
336 float margin = 4.0 * t;
338 float borderDiag = border * 1.414;
339 vector arrowX = eX * size;
340 vector arrowY = eY * (size+borderDiag);
341 vector borderX = eX * (size+borderDiag);
342 vector borderY = eY * (size+borderDiag+border);
344 R_BeginPolygon("", DRAWFLAG_NORMAL);
345 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
346 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
347 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
348 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
349 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
352 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
353 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
354 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
355 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
358 return o + rotate(eY * (borderDiag+size+margin), ang);
361 // returns location of sprite healthbar
362 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
366 float aspect, sa, ca;
368 sw = stringwidth(s, false, fontsize);
375 // how do corners work?
376 aspect = vid_conwidth / vid_conheight;
378 ca = cos(ang) * aspect;
379 if (fabs(sa) > fabs(ca))
383 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
388 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
396 // we want to be onscreen
401 if (o.x > vid_conwidth - w)
402 o.x = vid_conwidth - w;
403 if (o.y > vid_conheight - h)
404 o.x = vid_conheight - h;
406 o.x += 0.5 * (w - sw);
408 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
416 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
418 vector yvec = '0.299 0.587 0.114';
419 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
422 vector fixrgbexcess(vector rgb)
425 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
427 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
428 if (rgb.z > 1) rgb.z = 1;
429 } else if (rgb.z > 1) {
430 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
431 if (rgb.y > 1) rgb.y = 1;
433 } else if (rgb.y > 1) {
434 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
436 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
437 if (rgb.z > 1) rgb.z = 1;
438 } else if (rgb.z > 1) {
439 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
440 if (rgb.x > 1) rgb.x = 1;
442 } else if (rgb.z > 1) {
443 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
445 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
446 if (rgb.y > 1) rgb.y = 1;
447 } else if (rgb.y > 1) {
448 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
449 if (rgb.x > 1) rgb.x = 1;
455 void Draw_WaypointSprite(entity this)
458 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
462 if (this.hideflags & 2)
463 return; // radar only
465 if (autocvar_cl_hidewaypoints >= 2)
468 if (this.hideflags & 1)
469 if (autocvar_cl_hidewaypoints)
470 return; // fixed waypoint
472 InterpolateOrigin_Do(this);
474 float t = entcs_GetTeam(player_localnum) + 1;
476 string spriteimage = "";
481 case SPRITERULE_SPECTATOR:
483 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
484 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
487 spriteimage = this.netname;
489 case SPRITERULE_DEFAULT:
493 spriteimage = this.netname;
498 spriteimage = this.netname;
500 case SPRITERULE_TEAMPLAY:
501 if (t == NUM_SPECTATOR + 1)
502 spriteimage = this.netname3;
503 else if (this.team == t)
504 spriteimage = this.netname2;
506 spriteimage = this.netname;
509 error("Invalid waypointsprite rule!");
513 if (spriteimage == "")
516 ++waypointsprite_newcount;
519 dist = vlen(this.origin - view_origin);
522 a = this.alpha * autocvar_hud_panel_fg_alpha;
524 if (this.maxdistance > waypointsprite_normdistance)
525 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
526 else if (this.maxdistance > 0)
527 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
529 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
532 this.teamradar_color = '1 0 1';
533 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
536 if (time - floor(time) > 0.5)
538 if (this.helpme && time < this.helpme)
539 a *= SPRITE_HELPME_BLINK;
540 else if (!this.lifetime) // fading out waypoints don't blink
541 a *= spritelookupblinkvalue(this, spriteimage);
553 rgb = fixrgbexcess(rgb);
558 o = project_3d_to_2d(this.origin);
560 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
561 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
562 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
563 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
565 // scale it to be just in view
569 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
570 ang = atan2(-d.x, -d.y);
574 f1 = d.x / vid_conwidth;
575 f2 = d.y / vid_conheight;
577 if (max(f1, -f1) > max(f2, -f2)) {
580 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
583 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
588 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
591 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
595 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
603 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
604 ang = atan2(-d.x, -d.y);
609 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
610 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
611 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
612 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
614 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
616 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
618 t = waypointsprite_scale * vidscale;
619 a *= waypointsprite_alpha;
622 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
623 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
625 if (edgedistance_min < waypointsprite_edgefadedistance) {
626 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
627 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
629 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
630 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
631 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
634 if (this.build_finished)
636 if (time < this.build_finished + 0.25)
638 if (time < this.build_started)
639 this.health = this.build_starthealth;
640 else if (time < this.build_finished)
641 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
649 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
652 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
655 txt = spritelookuptext(this, spriteimage);
656 if (this.helpme && time < this.helpme)
657 txt = sprintf(_("%s needing help!"), txt);
658 if (autocvar_g_waypointsprite_uppercase)
659 txt = strtoupper(txt);
661 draw_beginBoldFont();
662 if (this.health >= 0)
664 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
667 if (this.build_finished)
672 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
674 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
681 SPRITE_HEALTHBAR_WIDTH * t,
682 SPRITE_HEALTHBAR_HEIGHT * t,
684 SPRITE_HEALTHBAR_BORDER * t,
687 a * SPRITE_HEALTHBAR_BORDERALPHA,
689 a * SPRITE_HEALTHBAR_HEALTHALPHA,
695 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
700 void WaypointSprite_Load_Frames(string ext)
702 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
704 int ext_len = strlen(ext);
705 int n = search_getsize(dh);
706 for (int i = 0; i < n; ++i)
708 string s = search_getfilename(dh, i);
709 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
711 int o = strstrofs(s, "_frame", 0);
712 string sname = strcat("/spriteframes/", substring(s, 0, o));
713 string sframes = substring(s, o + 6, strlen(s) - o - 6);
714 int f = stof(sframes) + 1;
715 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
720 void WaypointSprite_Load();
721 STATIC_INIT(WaypointSprite_Load) {
722 WaypointSprite_Load();
723 WaypointSprite_Load_Frames(".tga");
724 WaypointSprite_Load_Frames(".jpg");
726 void WaypointSprite_Load()
728 waypointsprite_fadedistance = vlen(mi_scale);
729 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
730 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
731 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
732 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
733 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
734 waypointsprite_scale = autocvar_g_waypointsprite_scale;
735 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
736 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
737 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
738 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
739 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
740 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
741 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
742 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
743 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
744 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
745 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
746 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
747 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
748 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
749 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
751 waypointsprite_count = waypointsprite_newcount;
752 waypointsprite_newcount = 0;
757 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
759 string m1 = _m1.netname;
760 string m2 = _m2.netname;
761 string m3 = _m3.netname;
779 void WaypointSprite_UpdateHealth(entity e, float f)
781 f = bound(0, f, e.max_health);
782 if (f != e.health || e.pain_finished)
790 void WaypointSprite_UpdateMaxHealth(entity e, float f)
792 if (f != e.max_health || e.pain_finished)
800 void WaypointSprite_UpdateBuildFinished(entity e, float f)
802 if (f != e.pain_finished || e.max_health)
810 void WaypointSprite_UpdateOrigin(entity e, vector o)
819 void WaypointSprite_UpdateRule(entity e, float t, float r)
821 // no check, as this is never called without doing an actual change (usually only once)
827 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
829 // no check, as this is never called without doing an actual change (usually only once)
831 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
836 void WaypointSprite_Ping(entity e)
839 if (time < e.waypointsprite_pingtime) return;
840 e.waypointsprite_pingtime = time + 0.3;
841 // ALWAYS sends (this causes a radar circle), thus no check
846 void WaypointSprite_HelpMePing(entity e)
848 WaypointSprite_Ping(e);
849 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
853 void WaypointSprite_FadeOutIn(entity e, float t)
858 e.teleport_time = time + t;
860 else if (t < (e.teleport_time - time))
862 // accelerate the waypoint's dying
864 // (e.teleport_time - time) / wp.fade_time stays
865 // e.teleport_time = time + fadetime
866 float current_fadetime;
867 current_fadetime = e.teleport_time - time;
868 e.teleport_time = time + t;
869 e.fade_time = e.fade_time * t / current_fadetime;
875 void WaypointSprite_Init()
877 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
878 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
879 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
882 void WaypointSprite_Kill(entity wp)
885 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
889 void WaypointSprite_Disown(entity wp, float fadetime)
892 if (wp.classname != "sprite_waypoint")
894 backtrace("Trying to disown a non-waypointsprite");
899 if (wp.exteriormodeltoclient == wp.owner)
900 wp.exteriormodeltoclient = NULL;
901 wp.owner.(wp.owned_by_field) = NULL;
904 WaypointSprite_FadeOutIn(wp, fadetime);
908 void WaypointSprite_Think(entity this)
910 bool doremove = false;
912 if (this.fade_time && time >= this.teleport_time)
917 if (this.exteriormodeltoclient)
918 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
921 WaypointSprite_Kill(this);
923 this.nextthink = time; // WHY?!?
926 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
928 // personal waypoints
929 if (this.enemy && this.enemy != view)
933 if (this.rule == SPRITERULE_SPECTATOR)
935 if (!autocvar_sv_itemstime)
937 if (!warmup_stage && IS_PLAYER(view))
940 else if (this.team && this.rule == SPRITERULE_DEFAULT)
942 if (this.team != view.team)
944 if (!IS_PLAYER(view))
951 entity WaypointSprite_getviewentity(entity e)
953 if (IS_SPEC(e)) e = e.enemy;
954 /* TODO idea (check this breaks nothing)
955 else if (e.classname == "observer")
961 float WaypointSprite_isteammate(entity e, entity e2)
964 return e2.team == e.team;
968 bool WaypointSprite_Customize(entity this, entity client)
970 // this is not in SendEntity because it shall run every frame, not just every update
972 // make spectators see what the player would see
973 entity e = WaypointSprite_getviewentity(client);
975 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
978 return this.waypointsprite_visible_for_player(this, client, e);
981 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
983 void WaypointSprite_Reset(entity this)
985 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
988 WaypointSprite_Kill(this);
991 entity WaypointSprite_Spawn(
992 entity spr, // sprite
993 float _lifetime, float maxdistance, // lifetime, max distance
994 entity ref, vector ofs, // position
995 entity showto, float t, // show to whom? Use a flag to indicate a team
996 entity own, .entity ownfield, // remove when own gets killed
997 float hideable, // true when it should be controlled by cl_hidewaypoints
998 entity icon // initial icon
1001 entity wp = new(sprite_waypoint);
1002 wp.teleport_time = time + _lifetime;
1003 wp.fade_time = _lifetime;
1004 wp.exteriormodeltoclient = ref;
1008 setorigin(wp, ref.origin + ofs);
1015 wp.currentammo = hideable;
1019 remove(own.(ownfield));
1020 own.(ownfield) = wp;
1021 wp.owned_by_field = ownfield;
1023 wp.fade_rate = maxdistance;
1024 setthink(wp, WaypointSprite_Think);
1025 wp.nextthink = time;
1026 wp.model1 = spr.netname;
1027 setcefc(wp, WaypointSprite_Customize);
1028 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1029 wp.reset2 = WaypointSprite_Reset;
1031 wp.colormod = spr.m_color;
1032 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1036 entity WaypointSprite_SpawnFixed(
1041 entity icon // initial icon
1044 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1047 entity WaypointSprite_DeployFixed(
1049 float limited_range,
1052 entity icon // initial icon
1062 maxdistance = waypointsprite_limitedrange;
1065 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1068 entity WaypointSprite_DeployPersonal(
1072 entity icon // initial icon
1075 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1078 entity WaypointSprite_Attach(
1081 float limited_range,
1082 entity icon // initial icon
1086 if (player.waypointsprite_attachedforcarrier)
1087 return NULL; // can't attach to FC
1094 maxdistance = waypointsprite_limitedrange;
1097 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1100 entity WaypointSprite_AttachCarrier(
1103 entity icon // initial icon and color
1106 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1107 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1110 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1111 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1116 void WaypointSprite_DetachCarrier(entity carrier)
1118 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1121 void WaypointSprite_ClearPersonal(entity this)
1123 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1126 void WaypointSprite_ClearOwned(entity this)
1128 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1129 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1130 WaypointSprite_Kill(this.waypointsprite_attached);
1133 void WaypointSprite_PlayerDead(entity this)
1135 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1136 WaypointSprite_DetachCarrier(this);
1139 void WaypointSprite_PlayerGone(entity this)
1141 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1142 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1143 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1144 WaypointSprite_DetachCarrier(this);