1 #include "waypointsprites.qh"
5 REGISTER_MUTATOR(waypointsprites, true);
8 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
9 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
11 WriteMutator(MSG_ENTITY, waypointsprites);
13 sendflags = sendflags & 0x7F;
17 else if (self.max_health || (self.pain_finished && (time < self.pain_finished + 0.25)))
20 WriteByte(MSG_ENTITY, sendflags);
21 WriteByte(MSG_ENTITY, self.wp_extra);
26 if(self.exteriormodeltoclient == to)
29 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
35 WriteByte(MSG_ENTITY, (self.health / self.max_health) * 191.0);
39 float dt = self.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, self.origin.x);
49 WriteCoord(MSG_ENTITY, self.origin.y);
50 WriteCoord(MSG_ENTITY, self.origin.z);
55 WriteByte(MSG_ENTITY, self.team);
56 WriteByte(MSG_ENTITY, self.rule);
60 WriteString(MSG_ENTITY, self.model1);
63 WriteString(MSG_ENTITY, self.model2);
66 WriteString(MSG_ENTITY, self.model3);
70 WriteCoord(MSG_ENTITY, self.fade_time);
71 WriteCoord(MSG_ENTITY, self.teleport_time);
72 WriteShort(MSG_ENTITY, self.fade_rate); // maxdist
73 WriteByte(MSG_ENTITY, f);
78 WriteByte(MSG_ENTITY, self.cnt); // icon on radar
79 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
80 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
81 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
83 if (WaypointSprite_isteammate(self.owner, WaypointSprite_getviewentity(to)))
85 float dt = (self.waypointsprite_helpmetime - time) / 0.1;
90 WriteByte(MSG_ENTITY, dt);
93 WriteByte(MSG_ENTITY, 0);
101 void Ent_WaypointSprite();
102 MUTATOR_HOOKFUNCTION(waypointsprites, CSQC_Ent_Update) {
103 if (MUTATOR_RETURNVALUE) return false;
104 if (!ReadMutatorEquals(mutator_argv_int_0, waypointsprites)) return false;
105 Ent_WaypointSprite();
109 void Ent_RemoveWaypointSprite()
111 if (self.netname) strunzone(self.netname);
112 if (self.netname2) strunzone(self.netname2);
113 if (self.netname3) strunzone(self.netname3);
116 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
117 void Ent_WaypointSprite()
119 int sendflags = ReadByte();
120 self.wp_extra = ReadByte();
123 self.spawntime = time;
125 self.draw2d = Draw_WaypointSprite;
127 InterpolateOrigin_Undo();
128 self.iflags |= IFLAG_ORIGIN;
130 if (sendflags & 0x80)
135 self.health = t / 191.0;
136 self.build_finished = 0;
140 t = (t - 192) * 256 + ReadByte();
141 self.build_started = servertime;
142 if (self.build_finished)
143 self.build_starthealth = bound(0, self.health, 1);
145 self.build_starthealth = 0;
146 self.build_finished = servertime + t / 32;
152 self.build_finished = 0;
157 // unfortunately, this needs to be exact (for the 3D display)
158 self.origin_x = ReadCoord();
159 self.origin_y = ReadCoord();
160 self.origin_z = ReadCoord();
161 setorigin(self, self.origin);
166 self.team = ReadByte();
167 self.rule = ReadByte();
173 strunzone(self.netname);
174 self.netname = strzone(ReadString());
180 strunzone(self.netname2);
181 self.netname2 = strzone(ReadString());
187 strunzone(self.netname3);
188 self.netname3 = strzone(ReadString());
193 self.lifetime = ReadCoord();
194 self.fadetime = ReadCoord();
195 self.maxdistance = ReadShort();
196 self.hideflags = ReadByte();
202 self.teamradar_icon = f & BITS(7);
205 self.(teamradar_times[self.teamradar_time_index]) = time;
206 self.teamradar_time_index = (self.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
208 self.teamradar_color_x = ReadByte() / 255.0;
209 self.teamradar_color_y = ReadByte() / 255.0;
210 self.teamradar_color_z = ReadByte() / 255.0;
211 self.helpme = ReadByte() * 0.1;
213 self.helpme += servertime;
216 InterpolateOrigin_Note();
218 self.entremove = Ent_RemoveWaypointSprite;
223 float spritelookupblinkvalue(string s)
225 if (s == WP_Weapon.netname) {
226 if (get_weaponinfo(self.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
229 if (s == WP_Item.netname) return Items[self.wp_extra].m_waypointblink;
234 vector spritelookupcolor(entity this, string s, vector def)
236 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return get_weaponinfo(this.wp_extra).wpcolor;
237 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items[this.wp_extra].m_color;
238 if (s == WP_Buff.netname || s == RADARICON_Buff.netname) return Buffs[this.wp_extra].m_color;
242 string spritelookuptext(string s)
244 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
245 if (s == WP_Weapon.netname) return get_weaponinfo(self.wp_extra).message;
246 if (s == WP_Item.netname) return Items[self.wp_extra].m_waypoint;
247 if (s == WP_Buff.netname) return Buffs[self.wp_extra].m_prettyName;
248 if (s == WP_Monster.netname) return get_monsterinfo(self.wp_extra).monster_name;
250 // need to loop, as our netname could be one of three
251 FOREACH(Waypoints, it.netname == s, LAMBDA(
260 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
262 vector v1, v2, v3, v4;
264 hotspot = -1 * hotspot;
266 // hotspot-relative coordinates of the corners
268 v2 = hotspot + '1 0 0' * sz.x;
269 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
270 v4 = hotspot + '0 1 0' * sz.y;
272 // rotate them, and make them absolute
273 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
274 v1 = rotate(v1, rot) + org;
275 v2 = rotate(v2, rot) + org;
276 v3 = rotate(v3, rot) + org;
277 v4 = rotate(v4, rot) + org;
280 R_BeginPolygon(pic, f);
281 R_PolygonVertex(v1, '0 0 0', rgb, a);
282 R_PolygonVertex(v2, '1 0 0', rgb, a);
283 R_PolygonVertex(v3, '1 1 0', rgb, a);
284 R_PolygonVertex(v4, '0 1 0', rgb, a);
288 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
290 R_BeginPolygon(pic, f);
291 R_PolygonVertex(o, '0 0 0', rgb, a);
292 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
293 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
294 R_PolygonVertex(o + up, '0 1 0', rgb, a);
298 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)
301 float owidth; // outer width
303 hotspot = -1 * hotspot;
305 // hotspot-relative coordinates of the healthbar corners
310 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
311 o = rotate(o, rot) + org;
312 ri = rotate(ri, rot);
313 up = rotate(up, rot);
315 owidth = width + 2 * border;
316 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
318 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
319 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
320 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
321 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
322 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
325 // returns location of sprite text
326 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
328 float size = 9.0 * t;
329 float border = 1.5 * t;
330 float margin = 4.0 * t;
332 float borderDiag = border * 1.414;
333 vector arrowX = eX * size;
334 vector arrowY = eY * (size+borderDiag);
335 vector borderX = eX * (size+borderDiag);
336 vector borderY = eY * (size+borderDiag+border);
338 R_BeginPolygon("", DRAWFLAG_NORMAL);
339 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
340 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
341 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
342 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
343 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
346 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
347 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
348 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
349 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
352 return o + rotate(eY * (borderDiag+size+margin), ang);
355 // returns location of sprite healthbar
356 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
360 float aspect, sa, ca;
362 sw = stringwidth(s, false, fontsize);
369 // how do corners work?
370 aspect = vid_conwidth / vid_conheight;
372 ca = cos(ang) * aspect;
373 if (fabs(sa) > fabs(ca))
376 algny = 0.5 - 0.5 * ca / fabs(sa);
380 algnx = 0.5 - 0.5 * sa / fabs(ca);
388 // we want to be onscreen
393 if (o.x > vid_conwidth - w)
394 o.x = vid_conwidth - w;
395 if (o.y > vid_conheight - h)
396 o.x = vid_conheight - h;
398 o.x += 0.5 * (w - sw);
400 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
408 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
410 vector yvec = '0.299 0.587 0.114';
411 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
414 vector fixrgbexcess(vector rgb)
417 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
419 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
420 if (rgb.z > 1) rgb.z = 1;
421 } else if (rgb.z > 1) {
422 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
423 if (rgb.y > 1) rgb.y = 1;
425 } else if (rgb.y > 1) {
426 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
428 rgb = fixrgbexcess_move(rgb, '1 0 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', '1 0 0');
432 if (rgb.x > 1) rgb.x = 1;
434 } else if (rgb.z > 1) {
435 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
437 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
438 if (rgb.y > 1) rgb.y = 1;
439 } else if (rgb.y > 1) {
440 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
441 if (rgb.x > 1) rgb.x = 1;
447 void Draw_WaypointSprite(entity this)
450 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
454 if (self.hideflags & 2)
455 return; // radar only
457 if (autocvar_cl_hidewaypoints >= 2)
460 if (self.hideflags & 1)
461 if (autocvar_cl_hidewaypoints)
462 return; // fixed waypoint
464 InterpolateOrigin_Do();
466 float t = GetPlayerColor(player_localnum) + 1;
468 string spriteimage = "";
473 case SPRITERULE_SPECTATOR:
475 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
476 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
479 spriteimage = self.netname;
481 case SPRITERULE_DEFAULT:
485 spriteimage = self.netname;
490 spriteimage = self.netname;
492 case SPRITERULE_TEAMPLAY:
493 if (t == NUM_SPECTATOR + 1)
494 spriteimage = self.netname3;
495 else if (self.team == t)
496 spriteimage = self.netname2;
498 spriteimage = self.netname;
501 error("Invalid waypointsprite rule!");
505 if (spriteimage == "")
508 ++waypointsprite_newcount;
511 dist = vlen(self.origin - view_origin);
514 a = self.alpha * autocvar_hud_panel_fg_alpha;
516 if (self.maxdistance > waypointsprite_normdistance)
517 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
518 else if (self.maxdistance > 0)
519 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
521 vector rgb = spritelookupcolor(self, spriteimage, self.teamradar_color);
524 self.teamradar_color = '1 0 1';
525 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
528 if (time - floor(time) > 0.5)
530 if (self.helpme && time < self.helpme)
531 a *= SPRITE_HELPME_BLINK;
532 else if (!self.lifetime) // fading out waypoints don't blink
533 a *= spritelookupblinkvalue(spriteimage);
545 rgb = fixrgbexcess(rgb);
550 o = project_3d_to_2d(self.origin);
552 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
553 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
554 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
555 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
557 // scale it to be just in view
561 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
562 ang = atan2(-d.x, -d.y);
566 f1 = d.x / vid_conwidth;
567 f2 = d.y / vid_conheight;
569 if (max(f1, -f1) > max(f2, -f2)) {
572 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
575 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
580 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
583 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
587 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
595 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
596 ang = atan2(-d.x, -d.y);
601 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
602 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
603 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
604 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
606 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
608 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
610 t = waypointsprite_scale * vidscale;
611 a *= waypointsprite_alpha;
614 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
615 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
617 if (edgedistance_min < waypointsprite_edgefadedistance) {
618 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
619 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
621 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
622 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
623 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
626 if (self.build_finished)
628 if (time < self.build_finished + 0.25)
630 if (time < self.build_started)
631 self.health = self.build_starthealth;
632 else if (time < self.build_finished)
633 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
641 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
644 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
647 txt = spritelookuptext(spriteimage);
648 if (self.helpme && time < self.helpme)
649 txt = sprintf(_("%s needing help!"), txt);
650 if (autocvar_g_waypointsprite_uppercase)
651 txt = strtoupper(txt);
653 draw_beginBoldFont();
654 if (self.health >= 0)
656 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
659 if (self.build_finished)
664 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
666 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
673 SPRITE_HEALTHBAR_WIDTH * t,
674 SPRITE_HEALTHBAR_HEIGHT * t,
676 SPRITE_HEALTHBAR_BORDER * t,
679 a * SPRITE_HEALTHBAR_BORDERALPHA,
681 a * SPRITE_HEALTHBAR_HEALTHALPHA,
687 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
692 void WaypointSprite_Load_Frames(string ext)
694 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
696 int ext_len = strlen(ext);
697 int n = search_getsize(dh);
698 for (int i = 0; i < n; ++i)
700 string s = search_getfilename(dh, i);
701 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
703 int o = strstrofs(s, "_frame", 0);
704 string sname = strcat("/spriteframes/", substring(s, 0, o));
705 string sframes = substring(s, o + 6, strlen(s) - o - 6);
706 int f = stof(sframes) + 1;
707 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
712 void WaypointSprite_Load();
713 STATIC_INIT(WaypointSprite_Load) {
714 WaypointSprite_Load();
715 WaypointSprite_Load_Frames(".tga");
716 WaypointSprite_Load_Frames(".jpg");
718 void WaypointSprite_Load()
720 waypointsprite_fadedistance = vlen(mi_scale);
721 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
722 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
723 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
724 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
725 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
726 waypointsprite_scale = autocvar_g_waypointsprite_scale;
727 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
728 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
729 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
730 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
731 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
732 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
733 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
734 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
735 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
736 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
737 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
738 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
739 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
740 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
741 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
743 waypointsprite_count = waypointsprite_newcount;
744 waypointsprite_newcount = 0;
749 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
751 string m1 = _m1.netname;
752 string m2 = _m2.netname;
753 string m3 = _m3.netname;
771 void WaypointSprite_UpdateHealth(entity e, float f)
773 f = bound(0, f, e.max_health);
774 if (f != e.health || e.pain_finished)
782 void WaypointSprite_UpdateMaxHealth(entity e, float f)
784 if (f != e.max_health || e.pain_finished)
792 void WaypointSprite_UpdateBuildFinished(entity e, float f)
794 if (f != e.pain_finished || e.max_health)
802 void WaypointSprite_UpdateOrigin(entity e, vector o)
811 void WaypointSprite_UpdateRule(entity e, float t, float r)
813 // no check, as this is never called without doing an actual change (usually only once)
819 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
821 // no check, as this is never called without doing an actual change (usually only once)
823 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
828 void WaypointSprite_Ping(entity e)
831 if (time < e.waypointsprite_pingtime) return;
832 e.waypointsprite_pingtime = time + 0.3;
833 // ALWAYS sends (this causes a radar circle), thus no check
838 void WaypointSprite_HelpMePing(entity e)
840 WaypointSprite_Ping(e);
841 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
845 void WaypointSprite_FadeOutIn(entity e, float t)
850 e.teleport_time = time + t;
852 else if (t < (e.teleport_time - time))
854 // accelerate the waypoint's dying
856 // (e.teleport_time - time) / wp.fade_time stays
857 // e.teleport_time = time + fadetime
858 float current_fadetime;
859 current_fadetime = e.teleport_time - time;
860 e.teleport_time = time + t;
861 e.fade_time = e.fade_time * t / current_fadetime;
867 void WaypointSprite_Init()
869 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
870 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
871 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
874 void WaypointSprite_InitClient(entity e)
878 void WaypointSprite_Kill(entity wp)
881 if (wp.owner) wp.owner.(wp.owned_by_field) = world;
885 void WaypointSprite_Disown(entity wp, float fadetime)
888 if (wp.classname != "sprite_waypoint")
890 backtrace("Trying to disown a non-waypointsprite");
895 if (wp.exteriormodeltoclient == wp.owner)
896 wp.exteriormodeltoclient = world;
897 wp.owner.(wp.owned_by_field) = world;
900 WaypointSprite_FadeOutIn(wp, fadetime);
904 void WaypointSprite_Think()
906 bool doremove = false;
908 if (self.fade_time && time >= self.teleport_time)
913 if (self.exteriormodeltoclient)
914 WaypointSprite_UpdateOrigin(self, self.exteriormodeltoclient.origin + self.view_ofs);
917 WaypointSprite_Kill(self);
919 self.nextthink = time; // WHY?!?
922 float WaypointSprite_visible_for_player(entity e)
924 // personal waypoints
925 if (self.enemy && self.enemy != e)
929 if (self.rule == SPRITERULE_SPECTATOR)
931 if (!autocvar_sv_itemstime)
933 if (!warmup_stage && IS_PLAYER(e))
936 else if (self.team && self.rule == SPRITERULE_DEFAULT)
938 if (self.team != e.team)
947 entity WaypointSprite_getviewentity(entity e)
949 if (IS_SPEC(e)) e = e.enemy;
950 /* TODO idea (check this breaks nothing)
951 else if (e.classname == "observer")
957 float WaypointSprite_isteammate(entity e, entity e2)
960 return e2.team == e.team;
964 float WaypointSprite_Customize()
966 // this is not in SendEntity because it shall run every frame, not just every update
968 // make spectators see what the player would see
969 entity e = WaypointSprite_getviewentity(other);
971 if (MUTATOR_CALLHOOK(CustomizeWaypoint, self, other))
974 return self.waypointsprite_visible_for_player(e);
977 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
979 void WaypointSprite_Reset()
981 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
983 if (self.fade_time) // was there before: || g_keyhunt, do we really need this?
984 WaypointSprite_Kill(self);
987 entity WaypointSprite_Spawn(
988 entity spr, // sprite
989 float _lifetime, float maxdistance, // lifetime, max distance
990 entity ref, vector ofs, // position
991 entity showto, float t, // show to whom? Use a flag to indicate a team
992 entity own, .entity ownfield, // remove when own gets killed
993 float hideable, // true when it should be controlled by cl_hidewaypoints
994 entity icon // initial icon
997 entity wp = new(sprite_waypoint);
998 wp.teleport_time = time + _lifetime;
999 wp.fade_time = _lifetime;
1000 wp.exteriormodeltoclient = ref;
1004 setorigin(wp, ref.origin + ofs);
1011 wp.currentammo = hideable;
1015 remove(own.(ownfield));
1016 own.(ownfield) = wp;
1017 wp.owned_by_field = ownfield;
1019 wp.fade_rate = maxdistance;
1020 wp.think = WaypointSprite_Think;
1021 wp.nextthink = time;
1022 wp.model1 = spr.netname;
1023 wp.customizeentityforclient = WaypointSprite_Customize;
1024 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1025 wp.reset2 = WaypointSprite_Reset;
1027 wp.colormod = spr.m_color;
1028 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1032 entity WaypointSprite_SpawnFixed(
1037 entity icon // initial icon
1040 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, own, ownfield, true, icon);
1043 entity WaypointSprite_DeployFixed(
1045 float limited_range,
1047 entity icon // initial icon
1057 maxdistance = waypointsprite_limitedrange;
1060 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, world, ofs, world, t, self, waypointsprite_deployed_fixed, false, icon);
1063 entity WaypointSprite_DeployPersonal(
1066 entity icon // initial icon
1069 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, self, waypointsprite_deployed_personal, false, icon);
1072 entity WaypointSprite_Attach(
1074 float limited_range,
1075 entity icon // initial icon
1079 if (self.waypointsprite_attachedforcarrier)
1080 return world; // can't attach to FC
1087 maxdistance = waypointsprite_limitedrange;
1090 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, self, '0 0 64', world, t, self, waypointsprite_attached, false, icon);
1093 entity WaypointSprite_AttachCarrier(
1096 entity icon // initial icon and color
1099 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1100 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', world, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1103 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1104 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1109 void WaypointSprite_DetachCarrier(entity carrier)
1111 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1114 void WaypointSprite_ClearPersonal()
1116 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1119 void WaypointSprite_ClearOwned()
1121 WaypointSprite_Kill(self.waypointsprite_deployed_fixed);
1122 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1123 WaypointSprite_Kill(self.waypointsprite_attached);
1126 void WaypointSprite_PlayerDead()
1128 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1129 WaypointSprite_DetachCarrier(self);
1132 void WaypointSprite_PlayerGone()
1134 WaypointSprite_Disown(self.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1135 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1136 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1137 WaypointSprite_DetachCarrier(self);