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;
230 if(s == WP_FlagReturn.netname) return 2;
235 vector spritelookupcolor(entity this, string s, vector def)
237 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
238 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
239 if (MUTATOR_CALLHOOK(WP_Format, this, s))
241 return M_ARGV(2, vector);
246 string spritelookuptext(entity this, string s)
248 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
249 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
250 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
251 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
252 if (MUTATOR_CALLHOOK(WP_Format, this, s))
254 return M_ARGV(3, string);
257 // need to loop, as our netname could be one of three
258 FOREACH(Waypoints, it.netname == s, LAMBDA(
267 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
269 vector v1, v2, v3, v4;
271 hotspot = -1 * hotspot;
273 // hotspot-relative coordinates of the corners
275 v2 = hotspot + '1 0 0' * sz.x;
276 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
277 v4 = hotspot + '0 1 0' * sz.y;
279 // rotate them, and make them absolute
280 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
281 v1 = rotate(v1, rot) + org;
282 v2 = rotate(v2, rot) + org;
283 v3 = rotate(v3, rot) + org;
284 v4 = rotate(v4, rot) + org;
287 R_BeginPolygon(pic, f);
288 R_PolygonVertex(v1, '0 0 0', rgb, a);
289 R_PolygonVertex(v2, '1 0 0', rgb, a);
290 R_PolygonVertex(v3, '1 1 0', rgb, a);
291 R_PolygonVertex(v4, '0 1 0', rgb, a);
295 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
297 R_BeginPolygon(pic, f);
298 R_PolygonVertex(o, '0 0 0', rgb, a);
299 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
300 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
301 R_PolygonVertex(o + up, '0 1 0', rgb, a);
305 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)
308 float owidth; // outer width
310 hotspot = -1 * hotspot;
312 // hotspot-relative coordinates of the healthbar corners
317 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
318 o = rotate(o, rot) + org;
319 ri = rotate(ri, rot);
320 up = rotate(up, rot);
322 owidth = width + 2 * border;
323 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
325 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
326 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
327 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
328 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
329 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
332 // returns location of sprite text
333 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
335 float size = 9.0 * t;
336 float border = 1.5 * t;
337 float margin = 4.0 * t;
339 float borderDiag = border * 1.414;
340 vector arrowX = eX * size;
341 vector arrowY = eY * (size+borderDiag);
342 vector borderX = eX * (size+borderDiag);
343 vector borderY = eY * (size+borderDiag+border);
345 R_BeginPolygon("", DRAWFLAG_NORMAL);
346 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
347 R_PolygonVertex(o + rotate(arrowY - 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(borderY + borderX, ang), '0 0 0', '0 0 0', a);
350 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
353 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
354 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
355 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
356 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
359 return o + rotate(eY * (borderDiag+size+margin), ang);
362 // returns location of sprite healthbar
363 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
367 float aspect, sa, ca;
369 sw = stringwidth(s, false, fontsize);
376 // how do corners work?
377 aspect = vid_conwidth / vid_conheight;
379 ca = cos(ang) * aspect;
380 if (fabs(sa) > fabs(ca))
384 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
389 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
397 // we want to be onscreen
402 if (o.x > vid_conwidth - w)
403 o.x = vid_conwidth - w;
404 if (o.y > vid_conheight - h)
405 o.x = vid_conheight - h;
407 o.x += 0.5 * (w - sw);
409 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
417 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
419 vector yvec = '0.299 0.587 0.114';
420 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
423 vector fixrgbexcess(vector rgb)
426 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
428 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
429 if (rgb.z > 1) rgb.z = 1;
430 } else if (rgb.z > 1) {
431 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
432 if (rgb.y > 1) rgb.y = 1;
434 } else if (rgb.y > 1) {
435 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
437 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
438 if (rgb.z > 1) rgb.z = 1;
439 } else if (rgb.z > 1) {
440 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
441 if (rgb.x > 1) rgb.x = 1;
443 } else if (rgb.z > 1) {
444 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
446 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
447 if (rgb.y > 1) rgb.y = 1;
448 } else if (rgb.y > 1) {
449 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
450 if (rgb.x > 1) rgb.x = 1;
456 void Draw_WaypointSprite(entity this)
459 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
463 if (this.hideflags & 2)
464 return; // radar only
466 if (autocvar_cl_hidewaypoints >= 2)
469 if (this.hideflags & 1)
470 if (autocvar_cl_hidewaypoints)
471 return; // fixed waypoint
473 InterpolateOrigin_Do(this);
475 float t = entcs_GetTeam(player_localnum) + 1;
477 string spriteimage = "";
482 case SPRITERULE_SPECTATOR:
484 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
485 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
488 spriteimage = this.netname;
490 case SPRITERULE_DEFAULT:
494 spriteimage = this.netname;
499 spriteimage = this.netname;
501 case SPRITERULE_TEAMPLAY:
502 if (t == NUM_SPECTATOR + 1)
503 spriteimage = this.netname3;
504 else if (this.team == t)
505 spriteimage = this.netname2;
507 spriteimage = this.netname;
510 error("Invalid waypointsprite rule!");
514 if (spriteimage == "")
517 ++waypointsprite_newcount;
520 dist = vlen(this.origin - view_origin);
523 a = this.alpha * autocvar_hud_panel_fg_alpha;
525 if (this.maxdistance > waypointsprite_normdistance)
526 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
527 else if (this.maxdistance > 0)
528 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
530 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
533 this.teamradar_color = '1 0 1';
534 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
537 if (time - floor(time) > 0.5)
539 if (this.helpme && time < this.helpme)
540 a *= SPRITE_HELPME_BLINK;
541 else if (!this.lifetime) // fading out waypoints don't blink
542 a *= spritelookupblinkvalue(this, spriteimage);
554 rgb = fixrgbexcess(rgb);
559 o = project_3d_to_2d(this.origin);
561 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
562 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
563 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
564 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
566 // scale it to be just in view
570 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
571 ang = atan2(-d.x, -d.y);
575 f1 = d.x / vid_conwidth;
576 f2 = d.y / vid_conheight;
578 if (max(f1, -f1) > max(f2, -f2)) {
581 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
584 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
589 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
592 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
596 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
604 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
605 ang = atan2(-d.x, -d.y);
610 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
611 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
612 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
613 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
615 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
617 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
619 t = waypointsprite_scale * vidscale;
620 a *= waypointsprite_alpha;
623 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
624 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
626 if (edgedistance_min < waypointsprite_edgefadedistance) {
627 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
628 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
630 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
631 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
632 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
635 if (this.build_finished)
637 if (time < this.build_finished + 0.25)
639 if (time < this.build_started)
640 this.health = this.build_starthealth;
641 else if (time < this.build_finished)
642 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
650 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
653 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
656 txt = spritelookuptext(this, spriteimage);
657 if (this.helpme && time < this.helpme)
658 txt = sprintf(_("%s needing help!"), txt);
659 if (autocvar_g_waypointsprite_uppercase)
660 txt = strtoupper(txt);
662 draw_beginBoldFont();
663 if (this.health >= 0)
665 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
668 if (this.build_finished)
673 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
675 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
682 SPRITE_HEALTHBAR_WIDTH * t,
683 SPRITE_HEALTHBAR_HEIGHT * t,
685 SPRITE_HEALTHBAR_BORDER * t,
688 a * SPRITE_HEALTHBAR_BORDERALPHA,
690 a * SPRITE_HEALTHBAR_HEALTHALPHA,
696 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
701 void WaypointSprite_Load_Frames(string ext)
703 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
705 int ext_len = strlen(ext);
706 int n = search_getsize(dh);
707 for (int i = 0; i < n; ++i)
709 string s = search_getfilename(dh, i);
710 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
712 int o = strstrofs(s, "_frame", 0);
713 string sname = strcat("/spriteframes/", substring(s, 0, o));
714 string sframes = substring(s, o + 6, strlen(s) - o - 6);
715 int f = stof(sframes) + 1;
716 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
721 void WaypointSprite_Load();
722 STATIC_INIT(WaypointSprite_Load) {
723 WaypointSprite_Load();
724 WaypointSprite_Load_Frames(".tga");
725 WaypointSprite_Load_Frames(".jpg");
727 void WaypointSprite_Load()
729 waypointsprite_fadedistance = vlen(mi_scale);
730 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
731 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
732 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
733 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
734 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
735 waypointsprite_scale = autocvar_g_waypointsprite_scale;
736 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
737 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
738 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
739 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
740 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
741 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
742 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
743 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
744 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
745 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
746 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
747 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
748 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
749 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
750 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
752 waypointsprite_count = waypointsprite_newcount;
753 waypointsprite_newcount = 0;
758 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
760 string m1 = _m1.netname;
761 string m2 = _m2.netname;
762 string m3 = _m3.netname;
780 void WaypointSprite_UpdateHealth(entity e, float f)
782 f = bound(0, f, e.max_health);
783 if (f != e.health || e.pain_finished)
791 void WaypointSprite_UpdateMaxHealth(entity e, float f)
793 if (f != e.max_health || e.pain_finished)
801 void WaypointSprite_UpdateBuildFinished(entity e, float f)
803 if (f != e.pain_finished || e.max_health)
811 void WaypointSprite_UpdateOrigin(entity e, vector o)
820 void WaypointSprite_UpdateRule(entity e, float t, float r)
822 // no check, as this is never called without doing an actual change (usually only once)
828 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
830 // no check, as this is never called without doing an actual change (usually only once)
832 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
837 void WaypointSprite_Ping(entity e)
840 if (time < e.waypointsprite_pingtime) return;
841 e.waypointsprite_pingtime = time + 0.3;
842 // ALWAYS sends (this causes a radar circle), thus no check
847 void WaypointSprite_HelpMePing(entity e)
849 WaypointSprite_Ping(e);
850 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
854 void WaypointSprite_FadeOutIn(entity e, float t)
859 e.teleport_time = time + t;
861 else if (t < (e.teleport_time - time))
863 // accelerate the waypoint's dying
865 // (e.teleport_time - time) / wp.fade_time stays
866 // e.teleport_time = time + fadetime
867 float current_fadetime;
868 current_fadetime = e.teleport_time - time;
869 e.teleport_time = time + t;
870 e.fade_time = e.fade_time * t / current_fadetime;
876 void WaypointSprite_Init()
878 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
879 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
880 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
883 void WaypointSprite_Kill(entity wp)
886 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
890 void WaypointSprite_Disown(entity wp, float fadetime)
893 if (wp.classname != "sprite_waypoint")
895 backtrace("Trying to disown a non-waypointsprite");
900 if (wp.exteriormodeltoclient == wp.owner)
901 wp.exteriormodeltoclient = NULL;
902 wp.owner.(wp.owned_by_field) = NULL;
905 WaypointSprite_FadeOutIn(wp, fadetime);
909 void WaypointSprite_Think(entity this)
911 bool doremove = false;
913 if (this.fade_time && time >= this.teleport_time)
918 if (this.exteriormodeltoclient)
919 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
922 WaypointSprite_Kill(this);
924 this.nextthink = time; // WHY?!?
927 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
929 // personal waypoints
930 if (this.enemy && this.enemy != view)
934 if (this.rule == SPRITERULE_SPECTATOR)
936 if (!autocvar_sv_itemstime)
938 if (!warmup_stage && IS_PLAYER(view))
941 else if (this.team && this.rule == SPRITERULE_DEFAULT)
943 if (this.team != view.team)
945 if (!IS_PLAYER(view))
952 entity WaypointSprite_getviewentity(entity e)
954 if (IS_SPEC(e)) e = e.enemy;
955 /* TODO idea (check this breaks nothing)
956 else if (e.classname == "observer")
962 float WaypointSprite_isteammate(entity e, entity e2)
965 return e2.team == e.team;
969 bool WaypointSprite_Customize(entity this, entity client)
971 // this is not in SendEntity because it shall run every frame, not just every update
973 // make spectators see what the player would see
974 entity e = WaypointSprite_getviewentity(client);
976 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
979 return this.waypointsprite_visible_for_player(this, client, e);
982 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
984 void WaypointSprite_Reset(entity this)
986 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
989 WaypointSprite_Kill(this);
992 entity WaypointSprite_Spawn(
993 entity spr, // sprite
994 float _lifetime, float maxdistance, // lifetime, max distance
995 entity ref, vector ofs, // position
996 entity showto, float t, // show to whom? Use a flag to indicate a team
997 entity own, .entity ownfield, // remove when own gets killed
998 float hideable, // true when it should be controlled by cl_hidewaypoints
999 entity icon // initial icon
1002 entity wp = new(sprite_waypoint);
1003 wp.teleport_time = time + _lifetime;
1004 wp.fade_time = _lifetime;
1005 wp.exteriormodeltoclient = ref;
1009 setorigin(wp, ref.origin + ofs);
1016 wp.currentammo = hideable;
1020 remove(own.(ownfield));
1021 own.(ownfield) = wp;
1022 wp.owned_by_field = ownfield;
1024 wp.fade_rate = maxdistance;
1025 setthink(wp, WaypointSprite_Think);
1026 wp.nextthink = time;
1027 wp.model1 = spr.netname;
1028 setcefc(wp, WaypointSprite_Customize);
1029 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1030 wp.reset2 = WaypointSprite_Reset;
1032 wp.colormod = spr.m_color;
1033 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1037 entity WaypointSprite_SpawnFixed(
1042 entity icon // initial icon
1045 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1048 entity WaypointSprite_DeployFixed(
1050 float limited_range,
1053 entity icon // initial icon
1063 maxdistance = waypointsprite_limitedrange;
1066 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1069 entity WaypointSprite_DeployPersonal(
1073 entity icon // initial icon
1076 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1079 entity WaypointSprite_Attach(
1082 float limited_range,
1083 entity icon // initial icon
1087 if (player.waypointsprite_attachedforcarrier)
1088 return NULL; // can't attach to FC
1095 maxdistance = waypointsprite_limitedrange;
1098 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1101 entity WaypointSprite_AttachCarrier(
1104 entity icon // initial icon and color
1107 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1108 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1111 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1112 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1117 void WaypointSprite_DetachCarrier(entity carrier)
1119 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1122 void WaypointSprite_ClearPersonal(entity this)
1124 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1127 void WaypointSprite_ClearOwned(entity this)
1129 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1130 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1131 WaypointSprite_Kill(this.waypointsprite_attached);
1134 void WaypointSprite_PlayerDead(entity this)
1136 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1137 WaypointSprite_DetachCarrier(this);
1140 void WaypointSprite_PlayerGone(entity this)
1142 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1143 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1144 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1145 WaypointSprite_DetachCarrier(this);