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);
28 WriteByte(MSG_ENTITY, sendflags);
29 WriteByte(MSG_ENTITY, this.wp_extra);
35 WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
39 float dt = this.pain_finished - time;
40 dt = bound(0, dt * 32, 16383);
41 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
42 WriteByte(MSG_ENTITY, (dt & 0x00FF));
48 WriteCoord(MSG_ENTITY, this.origin.x);
49 WriteCoord(MSG_ENTITY, this.origin.y);
50 WriteCoord(MSG_ENTITY, this.origin.z);
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 = (this.waypointsprite_helpmetime - time) / 0.1;
90 WriteByte(MSG_ENTITY, dt);
93 WriteByte(MSG_ENTITY, 0);
101 void Ent_WaypointSprite(entity this);
102 NET_HANDLE(waypointsprites, bool isnew) {
103 Ent_WaypointSprite(this);
107 void Ent_RemoveWaypointSprite(entity this)
109 if (this.netname) strunzone(this.netname);
110 if (this.netname2) strunzone(this.netname2);
111 if (this.netname3) strunzone(this.netname3);
114 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
115 void Ent_WaypointSprite(entity this)
117 int sendflags = ReadByte();
118 this.wp_extra = ReadByte();
121 this.spawntime = time;
123 this.draw2d = Draw_WaypointSprite;
125 InterpolateOrigin_Undo(this);
126 this.iflags |= IFLAG_ORIGIN;
128 if (sendflags & 0x80)
133 this.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, this.health, 1);
143 this.build_starthealth = 0;
144 this.build_finished = servertime + t / 32;
150 this.build_finished = 0;
155 // unfortunately, this needs to be exact (for the 3D display)
156 this.origin_x = ReadCoord();
157 this.origin_y = ReadCoord();
158 this.origin_z = ReadCoord();
159 setorigin(this, this.origin);
164 this.team = ReadByte();
165 this.rule = ReadByte();
171 strunzone(this.netname);
172 this.netname = strzone(ReadString());
178 strunzone(this.netname2);
179 this.netname2 = strzone(ReadString());
185 strunzone(this.netname3);
186 this.netname3 = strzone(ReadString());
191 this.lifetime = ReadCoord();
192 this.fadetime = ReadCoord();
193 this.maxdistance = ReadShort();
194 this.hideflags = ReadByte();
200 this.teamradar_icon = f & BITS(7);
203 this.(teamradar_times[this.teamradar_time_index]) = time;
204 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
206 this.teamradar_color_x = ReadByte() / 255.0;
207 this.teamradar_color_y = ReadByte() / 255.0;
208 this.teamradar_color_z = ReadByte() / 255.0;
209 this.helpme = ReadByte() * 0.1;
211 this.helpme += servertime;
214 InterpolateOrigin_Note(this);
216 this.entremove = Ent_RemoveWaypointSprite;
221 float spritelookupblinkvalue(entity this, string s)
223 if (s == WP_Weapon.netname) {
224 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
227 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
232 vector spritelookupcolor(entity this, string s, vector def)
234 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
235 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
236 if (MUTATOR_CALLHOOK(WP_Format, this, s))
238 return M_ARGV(2, vector);
243 string spritelookuptext(entity this, string s)
245 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
246 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
247 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
248 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
249 if (MUTATOR_CALLHOOK(WP_Format, this, s))
251 return M_ARGV(3, string);
254 // need to loop, as our netname could be one of three
255 FOREACH(Waypoints, it.netname == s, LAMBDA(
264 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
266 vector v1, v2, v3, v4;
268 hotspot = -1 * hotspot;
270 // hotspot-relative coordinates of the corners
272 v2 = hotspot + '1 0 0' * sz.x;
273 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
274 v4 = hotspot + '0 1 0' * sz.y;
276 // rotate them, and make them absolute
277 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
278 v1 = rotate(v1, rot) + org;
279 v2 = rotate(v2, rot) + org;
280 v3 = rotate(v3, rot) + org;
281 v4 = rotate(v4, rot) + org;
284 R_BeginPolygon(pic, f);
285 R_PolygonVertex(v1, '0 0 0', rgb, a);
286 R_PolygonVertex(v2, '1 0 0', rgb, a);
287 R_PolygonVertex(v3, '1 1 0', rgb, a);
288 R_PolygonVertex(v4, '0 1 0', rgb, a);
292 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
294 R_BeginPolygon(pic, f);
295 R_PolygonVertex(o, '0 0 0', rgb, a);
296 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
297 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
298 R_PolygonVertex(o + up, '0 1 0', rgb, a);
302 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)
305 float owidth; // outer width
307 hotspot = -1 * hotspot;
309 // hotspot-relative coordinates of the healthbar corners
314 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
315 o = rotate(o, rot) + org;
316 ri = rotate(ri, rot);
317 up = rotate(up, rot);
319 owidth = width + 2 * border;
320 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
322 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
323 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
324 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
325 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
326 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
329 // returns location of sprite text
330 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
332 float size = 9.0 * t;
333 float border = 1.5 * t;
334 float margin = 4.0 * t;
336 float borderDiag = border * 1.414;
337 vector arrowX = eX * size;
338 vector arrowY = eY * (size+borderDiag);
339 vector borderX = eX * (size+borderDiag);
340 vector borderY = eY * (size+borderDiag+border);
342 R_BeginPolygon("", DRAWFLAG_NORMAL);
343 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
344 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
345 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
346 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
347 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
350 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
351 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
352 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
353 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
356 return o + rotate(eY * (borderDiag+size+margin), ang);
359 // returns location of sprite healthbar
360 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
364 float aspect, sa, ca;
366 sw = stringwidth(s, false, fontsize);
373 // how do corners work?
374 aspect = vid_conwidth / vid_conheight;
376 ca = cos(ang) * aspect;
377 if (fabs(sa) > fabs(ca))
381 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
386 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
394 // we want to be onscreen
399 if (o.x > vid_conwidth - w)
400 o.x = vid_conwidth - w;
401 if (o.y > vid_conheight - h)
402 o.x = vid_conheight - h;
404 o.x += 0.5 * (w - sw);
406 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
414 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
416 vector yvec = '0.299 0.587 0.114';
417 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
420 vector fixrgbexcess(vector rgb)
423 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
425 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
426 if (rgb.z > 1) rgb.z = 1;
427 } else if (rgb.z > 1) {
428 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
429 if (rgb.y > 1) rgb.y = 1;
431 } else if (rgb.y > 1) {
432 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
434 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
435 if (rgb.z > 1) rgb.z = 1;
436 } else if (rgb.z > 1) {
437 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
438 if (rgb.x > 1) rgb.x = 1;
440 } else if (rgb.z > 1) {
441 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
443 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
444 if (rgb.y > 1) rgb.y = 1;
445 } else if (rgb.y > 1) {
446 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
447 if (rgb.x > 1) rgb.x = 1;
453 void Draw_WaypointSprite(entity this)
456 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
460 if (this.hideflags & 2)
461 return; // radar only
463 if (autocvar_cl_hidewaypoints >= 2)
466 if (this.hideflags & 1)
467 if (autocvar_cl_hidewaypoints)
468 return; // fixed waypoint
470 InterpolateOrigin_Do(this);
472 float t = entcs_GetTeam(player_localnum) + 1;
474 string spriteimage = "";
479 case SPRITERULE_SPECTATOR:
481 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
482 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
485 spriteimage = this.netname;
487 case SPRITERULE_DEFAULT:
491 spriteimage = this.netname;
496 spriteimage = this.netname;
498 case SPRITERULE_TEAMPLAY:
499 if (t == NUM_SPECTATOR + 1)
500 spriteimage = this.netname3;
501 else if (this.team == t)
502 spriteimage = this.netname2;
504 spriteimage = this.netname;
507 error("Invalid waypointsprite rule!");
511 if (spriteimage == "")
514 ++waypointsprite_newcount;
517 dist = vlen(this.origin - view_origin);
520 a = this.alpha * autocvar_hud_panel_fg_alpha;
522 if (this.maxdistance > waypointsprite_normdistance)
523 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
524 else if (this.maxdistance > 0)
525 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
527 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
530 this.teamradar_color = '1 0 1';
531 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
534 if (time - floor(time) > 0.5)
536 if (this.helpme && time < this.helpme)
537 a *= SPRITE_HELPME_BLINK;
538 else if (!this.lifetime) // fading out waypoints don't blink
539 a *= spritelookupblinkvalue(this, spriteimage);
551 rgb = fixrgbexcess(rgb);
556 o = project_3d_to_2d(this.origin);
558 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
559 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
560 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
561 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
563 // scale it to be just in view
567 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
568 ang = atan2(-d.x, -d.y);
572 f1 = d.x / vid_conwidth;
573 f2 = d.y / vid_conheight;
575 if (max(f1, -f1) > max(f2, -f2)) {
578 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
581 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
586 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
589 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
593 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
601 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
602 ang = atan2(-d.x, -d.y);
607 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
608 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
609 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
610 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
612 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
614 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
616 t = waypointsprite_scale * vidscale;
617 a *= waypointsprite_alpha;
620 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
621 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
623 if (edgedistance_min < waypointsprite_edgefadedistance) {
624 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
625 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
627 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
628 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
629 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
632 if (this.build_finished)
634 if (time < this.build_finished + 0.25)
636 if (time < this.build_started)
637 this.health = this.build_starthealth;
638 else if (time < this.build_finished)
639 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
647 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
650 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
653 txt = spritelookuptext(this, spriteimage);
654 if (this.helpme && time < this.helpme)
655 txt = sprintf(_("%s needing help!"), txt);
656 if (autocvar_g_waypointsprite_uppercase)
657 txt = strtoupper(txt);
659 draw_beginBoldFont();
660 if (this.health >= 0)
662 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
665 if (this.build_finished)
670 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
672 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
679 SPRITE_HEALTHBAR_WIDTH * t,
680 SPRITE_HEALTHBAR_HEIGHT * t,
682 SPRITE_HEALTHBAR_BORDER * t,
685 a * SPRITE_HEALTHBAR_BORDERALPHA,
687 a * SPRITE_HEALTHBAR_HEALTHALPHA,
693 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
698 void WaypointSprite_Load_Frames(string ext)
700 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
702 int ext_len = strlen(ext);
703 int n = search_getsize(dh);
704 for (int i = 0; i < n; ++i)
706 string s = search_getfilename(dh, i);
707 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
709 int o = strstrofs(s, "_frame", 0);
710 string sname = strcat("/spriteframes/", substring(s, 0, o));
711 string sframes = substring(s, o + 6, strlen(s) - o - 6);
712 int f = stof(sframes) + 1;
713 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
718 void WaypointSprite_Load();
719 STATIC_INIT(WaypointSprite_Load) {
720 WaypointSprite_Load();
721 WaypointSprite_Load_Frames(".tga");
722 WaypointSprite_Load_Frames(".jpg");
724 void WaypointSprite_Load()
726 waypointsprite_fadedistance = vlen(mi_scale);
727 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
728 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
729 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
730 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
731 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
732 waypointsprite_scale = autocvar_g_waypointsprite_scale;
733 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
734 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
735 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
736 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
737 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
738 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
739 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
740 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
741 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
742 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
743 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
744 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
745 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
746 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
747 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
749 waypointsprite_count = waypointsprite_newcount;
750 waypointsprite_newcount = 0;
755 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
757 string m1 = _m1.netname;
758 string m2 = _m2.netname;
759 string m3 = _m3.netname;
777 void WaypointSprite_UpdateHealth(entity e, float f)
779 f = bound(0, f, e.max_health);
780 if (f != e.health || e.pain_finished)
788 void WaypointSprite_UpdateMaxHealth(entity e, float f)
790 if (f != e.max_health || e.pain_finished)
798 void WaypointSprite_UpdateBuildFinished(entity e, float f)
800 if (f != e.pain_finished || e.max_health)
808 void WaypointSprite_UpdateOrigin(entity e, vector o)
817 void WaypointSprite_UpdateRule(entity e, float t, float r)
819 // no check, as this is never called without doing an actual change (usually only once)
825 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
827 // no check, as this is never called without doing an actual change (usually only once)
829 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
834 void WaypointSprite_Ping(entity e)
837 if (time < e.waypointsprite_pingtime) return;
838 e.waypointsprite_pingtime = time + 0.3;
839 // ALWAYS sends (this causes a radar circle), thus no check
844 void WaypointSprite_HelpMePing(entity e)
846 WaypointSprite_Ping(e);
847 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
851 void WaypointSprite_FadeOutIn(entity e, float t)
856 e.teleport_time = time + t;
858 else if (t < (e.teleport_time - time))
860 // accelerate the waypoint's dying
862 // (e.teleport_time - time) / wp.fade_time stays
863 // e.teleport_time = time + fadetime
864 float current_fadetime;
865 current_fadetime = e.teleport_time - time;
866 e.teleport_time = time + t;
867 e.fade_time = e.fade_time * t / current_fadetime;
873 void WaypointSprite_Init()
875 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
876 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
877 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
880 void WaypointSprite_Kill(entity wp)
883 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
887 void WaypointSprite_Disown(entity wp, float fadetime)
890 if (wp.classname != "sprite_waypoint")
892 backtrace("Trying to disown a non-waypointsprite");
897 if (wp.exteriormodeltoclient == wp.owner)
898 wp.exteriormodeltoclient = NULL;
899 wp.owner.(wp.owned_by_field) = NULL;
902 WaypointSprite_FadeOutIn(wp, fadetime);
906 void WaypointSprite_Think(entity this)
908 bool doremove = false;
910 if (this.fade_time && time >= this.teleport_time)
915 if (this.exteriormodeltoclient)
916 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
919 WaypointSprite_Kill(this);
921 this.nextthink = time; // WHY?!?
924 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
926 // personal waypoints
927 if (this.enemy && this.enemy != view)
931 if (this.rule == SPRITERULE_SPECTATOR)
933 if (!autocvar_sv_itemstime)
935 if (!warmup_stage && IS_PLAYER(view))
938 else if (this.team && this.rule == SPRITERULE_DEFAULT)
940 if (this.team != view.team)
942 if (!IS_PLAYER(view))
949 entity WaypointSprite_getviewentity(entity e)
951 if (IS_SPEC(e)) e = e.enemy;
952 /* TODO idea (check this breaks nothing)
953 else if (e.classname == "observer")
959 float WaypointSprite_isteammate(entity e, entity e2)
962 return e2.team == e.team;
966 float WaypointSprite_Customize(entity this)
968 // this is not in SendEntity because it shall run every frame, not just every update
970 // make spectators see what the player would see
971 entity e = WaypointSprite_getviewentity(other);
973 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, other))
976 return this.waypointsprite_visible_for_player(this, other, e);
979 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
981 void WaypointSprite_Reset(entity this)
983 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
986 WaypointSprite_Kill(this);
989 entity WaypointSprite_Spawn(
990 entity spr, // sprite
991 float _lifetime, float maxdistance, // lifetime, max distance
992 entity ref, vector ofs, // position
993 entity showto, float t, // show to whom? Use a flag to indicate a team
994 entity own, .entity ownfield, // remove when own gets killed
995 float hideable, // true when it should be controlled by cl_hidewaypoints
996 entity icon // initial icon
999 entity wp = new(sprite_waypoint);
1000 wp.teleport_time = time + _lifetime;
1001 wp.fade_time = _lifetime;
1002 wp.exteriormodeltoclient = ref;
1006 setorigin(wp, ref.origin + ofs);
1013 wp.currentammo = hideable;
1017 remove(own.(ownfield));
1018 own.(ownfield) = wp;
1019 wp.owned_by_field = ownfield;
1021 wp.fade_rate = maxdistance;
1022 setthink(wp, WaypointSprite_Think);
1023 wp.nextthink = time;
1024 wp.model1 = spr.netname;
1025 setcefc(wp, WaypointSprite_Customize);
1026 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1027 wp.reset2 = WaypointSprite_Reset;
1029 wp.colormod = spr.m_color;
1030 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1034 entity WaypointSprite_SpawnFixed(
1039 entity icon // initial icon
1042 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1045 entity WaypointSprite_DeployFixed(
1047 float limited_range,
1050 entity icon // initial icon
1060 maxdistance = waypointsprite_limitedrange;
1063 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1066 entity WaypointSprite_DeployPersonal(
1070 entity icon // initial icon
1073 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1076 entity WaypointSprite_Attach(
1079 float limited_range,
1080 entity icon // initial icon
1084 if (player.waypointsprite_attachedforcarrier)
1085 return NULL; // can't attach to FC
1092 maxdistance = waypointsprite_limitedrange;
1095 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1098 entity WaypointSprite_AttachCarrier(
1101 entity icon // initial icon and color
1104 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1105 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1108 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1109 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1114 void WaypointSprite_DetachCarrier(entity carrier)
1116 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1119 void WaypointSprite_ClearPersonal(entity this)
1121 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1124 void WaypointSprite_ClearOwned(entity this)
1126 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1127 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1128 WaypointSprite_Kill(this.waypointsprite_attached);
1131 void WaypointSprite_PlayerDead(entity this)
1133 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1134 WaypointSprite_DetachCarrier(this);
1137 void WaypointSprite_PlayerGone(entity this)
1139 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1140 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1141 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1142 WaypointSprite_DetachCarrier(this);