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, 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, LAMBDA(
271 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
273 vector v1, v2, v3, v4;
275 hotspot = -1 * hotspot;
277 // hotspot-relative coordinates of the corners
279 v2 = hotspot + '1 0 0' * sz.x;
280 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
281 v4 = hotspot + '0 1 0' * sz.y;
283 // rotate them, and make them absolute
284 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
285 v1 = rotate(v1, rot) + org;
286 v2 = rotate(v2, rot) + org;
287 v3 = rotate(v3, rot) + org;
288 v4 = rotate(v4, rot) + org;
291 R_BeginPolygon(pic, f);
292 R_PolygonVertex(v1, '0 0 0', rgb, a);
293 R_PolygonVertex(v2, '1 0 0', rgb, a);
294 R_PolygonVertex(v3, '1 1 0', rgb, a);
295 R_PolygonVertex(v4, '0 1 0', rgb, a);
299 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
301 R_BeginPolygon(pic, f);
302 R_PolygonVertex(o, '0 0 0', rgb, a);
303 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
304 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
305 R_PolygonVertex(o + up, '0 1 0', rgb, a);
309 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)
312 float owidth; // outer width
314 hotspot = -1 * hotspot;
316 // hotspot-relative coordinates of the healthbar corners
321 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
322 o = rotate(o, rot) + org;
323 ri = rotate(ri, rot);
324 up = rotate(up, rot);
326 owidth = width + 2 * border;
327 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
329 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
330 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
331 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
332 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
333 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
336 // returns location of sprite text
337 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
339 float size = 9.0 * t;
340 float border = 1.5 * t;
341 float margin = 4.0 * t;
343 float borderDiag = border * 1.414;
344 vector arrowX = eX * size;
345 vector arrowY = eY * (size+borderDiag);
346 vector borderX = eX * (size+borderDiag);
347 vector borderY = eY * (size+borderDiag+border);
349 R_BeginPolygon("", DRAWFLAG_NORMAL);
350 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
351 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
352 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
353 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
354 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
357 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
358 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
359 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
360 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
363 return o + rotate(eY * (borderDiag+size+margin), ang);
366 // returns location of sprite healthbar
367 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
371 float aspect, sa, ca;
373 sw = stringwidth(s, false, fontsize);
380 // how do corners work?
381 aspect = vid_conwidth / vid_conheight;
383 ca = cos(ang) * aspect;
384 if (fabs(sa) > fabs(ca))
388 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
393 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
401 // we want to be onscreen
406 if (o.x > vid_conwidth - w)
407 o.x = vid_conwidth - w;
408 if (o.y > vid_conheight - h)
409 o.x = vid_conheight - h;
411 o.x += 0.5 * (w - sw);
413 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
421 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
423 vector yvec = '0.299 0.587 0.114';
424 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
427 vector fixrgbexcess(vector rgb)
430 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
432 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
433 if (rgb.z > 1) rgb.z = 1;
434 } else if (rgb.z > 1) {
435 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
436 if (rgb.y > 1) rgb.y = 1;
438 } else if (rgb.y > 1) {
439 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
441 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
442 if (rgb.z > 1) rgb.z = 1;
443 } else if (rgb.z > 1) {
444 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
445 if (rgb.x > 1) rgb.x = 1;
447 } else if (rgb.z > 1) {
448 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
450 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
451 if (rgb.y > 1) rgb.y = 1;
452 } else if (rgb.y > 1) {
453 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
454 if (rgb.x > 1) rgb.x = 1;
460 void Draw_WaypointSprite(entity this)
463 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
467 if (this.hideflags & 2)
468 return; // radar only
470 if (autocvar_cl_hidewaypoints >= 2)
473 if (this.hideflags & 1)
474 if (autocvar_cl_hidewaypoints)
475 return; // fixed waypoint
477 InterpolateOrigin_Do(this);
479 float t = entcs_GetTeam(player_localnum) + 1;
481 string spriteimage = "";
486 case SPRITERULE_SPECTATOR:
488 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
489 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
492 spriteimage = this.netname;
494 case SPRITERULE_DEFAULT:
498 spriteimage = this.netname;
503 spriteimage = this.netname;
505 case SPRITERULE_TEAMPLAY:
506 if (t == NUM_SPECTATOR + 1)
507 spriteimage = this.netname3;
508 else if (this.team == t)
509 spriteimage = this.netname2;
511 spriteimage = this.netname;
514 error("Invalid waypointsprite rule!");
518 if (spriteimage == "")
521 ++waypointsprite_newcount;
524 dist = vlen(this.origin - view_origin);
527 a = this.alpha * autocvar_hud_panel_fg_alpha;
529 if (this.maxdistance > waypointsprite_normdistance)
530 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
531 else if (this.maxdistance > 0)
532 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
534 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
537 this.teamradar_color = '1 0 1';
538 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
541 if (time - floor(time) > 0.5)
543 if (this.helpme && time < this.helpme)
544 a *= SPRITE_HELPME_BLINK;
545 else if (!this.lifetime) // fading out waypoints don't blink
546 a *= spritelookupblinkvalue(this, spriteimage);
558 rgb = fixrgbexcess(rgb);
563 o = project_3d_to_2d(this.origin);
565 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
566 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
567 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
568 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
570 // scale it to be just in view
574 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
575 ang = atan2(-d.x, -d.y);
579 f1 = d.x / vid_conwidth;
580 f2 = d.y / vid_conheight;
582 if (max(f1, -f1) > max(f2, -f2)) {
585 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
588 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
593 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
596 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
600 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
608 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
609 ang = atan2(-d.x, -d.y);
614 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
615 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
616 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
617 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
619 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
621 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
623 t = waypointsprite_scale * vidscale;
624 a *= waypointsprite_alpha;
627 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
628 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
630 if (edgedistance_min < waypointsprite_edgefadedistance) {
631 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
632 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
634 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
635 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
636 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
639 if (this.build_finished)
641 if (time < this.build_finished + 0.25)
643 if (time < this.build_started)
644 this.health = this.build_starthealth;
645 else if (time < this.build_finished)
646 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
654 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
657 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
660 txt = spritelookuptext(this, spriteimage);
661 if (this.helpme && time < this.helpme)
662 txt = sprintf(_("%s needing help!"), txt);
663 if (autocvar_g_waypointsprite_uppercase)
664 txt = strtoupper(txt);
666 draw_beginBoldFont();
667 if (this.health >= 0)
669 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
672 if (this.build_finished)
677 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
679 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
686 SPRITE_HEALTHBAR_WIDTH * t,
687 SPRITE_HEALTHBAR_HEIGHT * t,
689 SPRITE_HEALTHBAR_BORDER * t,
692 a * SPRITE_HEALTHBAR_BORDERALPHA,
694 a * SPRITE_HEALTHBAR_HEALTHALPHA,
700 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
705 void WaypointSprite_Load_Frames(string ext)
707 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
709 int ext_len = strlen(ext);
710 int n = search_getsize(dh);
711 for (int i = 0; i < n; ++i)
713 string s = search_getfilename(dh, i);
714 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
716 int o = strstrofs(s, "_frame", 0);
717 string sname = strcat("/spriteframes/", substring(s, 0, o));
718 string sframes = substring(s, o + 6, strlen(s) - o - 6);
719 int f = stof(sframes) + 1;
720 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
725 void WaypointSprite_Load();
726 STATIC_INIT(WaypointSprite_Load) {
727 WaypointSprite_Load();
728 WaypointSprite_Load_Frames(".tga");
729 WaypointSprite_Load_Frames(".jpg");
731 void WaypointSprite_Load()
733 waypointsprite_fadedistance = vlen(mi_scale);
734 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
735 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
736 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
737 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
738 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
739 waypointsprite_scale = autocvar_g_waypointsprite_scale;
740 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
741 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
742 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
743 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
744 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
745 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
746 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
747 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
748 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
749 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
750 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
751 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
752 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
753 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
754 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
756 waypointsprite_count = waypointsprite_newcount;
757 waypointsprite_newcount = 0;
762 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
764 string m1 = _m1.netname;
765 string m2 = _m2.netname;
766 string m3 = _m3.netname;
784 void WaypointSprite_UpdateHealth(entity e, float f)
786 f = bound(0, f, e.max_health);
787 if (f != e.health || e.pain_finished)
795 void WaypointSprite_UpdateMaxHealth(entity e, float f)
797 if (f != e.max_health || e.pain_finished)
805 void WaypointSprite_UpdateBuildFinished(entity e, float f)
807 if (f != e.pain_finished || e.max_health)
815 void WaypointSprite_UpdateOrigin(entity e, vector o)
824 void WaypointSprite_UpdateRule(entity e, float t, float r)
826 // no check, as this is never called without doing an actual change (usually only once)
832 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
834 // no check, as this is never called without doing an actual change (usually only once)
836 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
841 void WaypointSprite_Ping(entity e)
844 if (time < e.waypointsprite_pingtime) return;
845 e.waypointsprite_pingtime = time + 0.3;
846 // ALWAYS sends (this causes a radar circle), thus no check
851 void WaypointSprite_HelpMePing(entity e)
853 WaypointSprite_Ping(e);
854 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
858 void WaypointSprite_FadeOutIn(entity e, float t)
863 e.teleport_time = time + t;
865 else if (t < (e.teleport_time - time))
867 // accelerate the waypoint's dying
869 // (e.teleport_time - time) / wp.fade_time stays
870 // e.teleport_time = time + fadetime
871 float current_fadetime;
872 current_fadetime = e.teleport_time - time;
873 e.teleport_time = time + t;
874 e.fade_time = e.fade_time * t / current_fadetime;
880 void WaypointSprite_Init()
882 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
883 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
884 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
887 void WaypointSprite_Kill(entity wp)
890 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
894 void WaypointSprite_Disown(entity wp, float fadetime)
897 if (wp.classname != "sprite_waypoint")
899 backtrace("Trying to disown a non-waypointsprite");
904 if (wp.exteriormodeltoclient == wp.owner)
905 wp.exteriormodeltoclient = NULL;
906 wp.owner.(wp.owned_by_field) = NULL;
909 WaypointSprite_FadeOutIn(wp, fadetime);
913 void WaypointSprite_Think(entity this)
915 bool doremove = false;
917 if (this.fade_time && time >= this.teleport_time)
922 if (this.exteriormodeltoclient)
923 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
926 WaypointSprite_Kill(this);
928 this.nextthink = time; // WHY?!?
931 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
933 // personal waypoints
934 if (this.enemy && this.enemy != view)
938 if (this.rule == SPRITERULE_SPECTATOR)
940 if (!autocvar_sv_itemstime)
942 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
945 else if (this.team && this.rule == SPRITERULE_DEFAULT)
947 if (this.team != view.team)
949 if (!IS_PLAYER(view))
956 entity WaypointSprite_getviewentity(entity e)
958 if (IS_SPEC(e)) e = e.enemy;
959 /* TODO idea (check this breaks nothing)
960 else if (e.classname == "observer")
966 float WaypointSprite_isteammate(entity e, entity e2)
969 return e2.team == e.team;
973 bool WaypointSprite_Customize(entity this, entity client)
975 // this is not in SendEntity because it shall run every frame, not just every update
977 // make spectators see what the player would see
978 entity e = WaypointSprite_getviewentity(client);
980 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
983 return this.waypointsprite_visible_for_player(this, client, e);
986 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
988 void WaypointSprite_Reset(entity this)
990 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
993 WaypointSprite_Kill(this);
996 entity WaypointSprite_Spawn(
997 entity spr, // sprite
998 float _lifetime, float maxdistance, // lifetime, max distance
999 entity ref, vector ofs, // position
1000 entity showto, float t, // show to whom? Use a flag to indicate a team
1001 entity own, .entity ownfield, // remove when own gets killed
1002 float hideable, // true when it should be controlled by cl_hidewaypoints
1003 entity icon // initial icon
1006 entity wp = new(sprite_waypoint);
1007 wp.teleport_time = time + _lifetime;
1008 wp.fade_time = _lifetime;
1009 wp.exteriormodeltoclient = ref;
1013 setorigin(wp, ref.origin + ofs);
1020 wp.currentammo = hideable;
1024 remove(own.(ownfield));
1025 own.(ownfield) = wp;
1026 wp.owned_by_field = ownfield;
1028 wp.fade_rate = maxdistance;
1029 setthink(wp, WaypointSprite_Think);
1030 wp.nextthink = time;
1031 wp.model1 = spr.netname;
1032 setcefc(wp, WaypointSprite_Customize);
1033 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1034 wp.reset2 = WaypointSprite_Reset;
1036 wp.colormod = spr.m_color;
1037 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1041 entity WaypointSprite_SpawnFixed(
1046 entity icon // initial icon
1049 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1052 entity WaypointSprite_DeployFixed(
1054 float limited_range,
1057 entity icon // initial icon
1067 maxdistance = waypointsprite_limitedrange;
1070 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1073 entity WaypointSprite_DeployPersonal(
1077 entity icon // initial icon
1080 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1083 entity WaypointSprite_Attach(
1086 float limited_range,
1087 entity icon // initial icon
1091 if (player.waypointsprite_attachedforcarrier)
1092 return NULL; // can't attach to FC
1099 maxdistance = waypointsprite_limitedrange;
1102 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1105 entity WaypointSprite_AttachCarrier(
1108 entity icon // initial icon and color
1111 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1112 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1115 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1116 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1121 void WaypointSprite_DetachCarrier(entity carrier)
1123 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1126 void WaypointSprite_ClearPersonal(entity this)
1128 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1131 void WaypointSprite_ClearOwned(entity this)
1133 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1134 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1135 WaypointSprite_Kill(this.waypointsprite_attached);
1138 void WaypointSprite_PlayerDead(entity this)
1140 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1141 WaypointSprite_DetachCarrier(this);
1144 void WaypointSprite_PlayerGone(entity this)
1146 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1147 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1148 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1149 WaypointSprite_DetachCarrier(this);