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);
27 WriteByte(MSG_ENTITY, (self.health / self.max_health) * 191.0);
31 float dt = self.pain_finished - time;
32 dt = bound(0, dt * 32, 16383);
33 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
34 WriteByte(MSG_ENTITY, (dt & 0x00FF));
40 WriteCoord(MSG_ENTITY, self.origin.x);
41 WriteCoord(MSG_ENTITY, self.origin.y);
42 WriteCoord(MSG_ENTITY, self.origin.z);
47 WriteByte(MSG_ENTITY, self.team);
48 WriteByte(MSG_ENTITY, self.rule);
52 WriteString(MSG_ENTITY, self.model1);
55 WriteString(MSG_ENTITY, self.model2);
58 WriteString(MSG_ENTITY, self.model3);
62 WriteCoord(MSG_ENTITY, self.fade_time);
63 WriteCoord(MSG_ENTITY, self.teleport_time);
64 WriteShort(MSG_ENTITY, self.fade_rate); // maxdist
68 if (self.exteriormodeltoclient == to)
72 if (self.owner.classname == "onslaught_controlpoint")
74 entity wp_owner = self.owner;
75 entity e = WaypointSprite_getviewentity(to);
76 if (SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { f |= 2; }
77 if (!ons_ControlPoint_Attackable(wp_owner, e.team)) { f |= 2; }
79 if (self.owner.classname == "onslaught_generator")
81 entity wp_owner = self.owner;
82 if (wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { f |= 2; }
83 if (wp_owner.health <= 0) { f |= 2; }
86 WriteByte(MSG_ENTITY, f);
91 WriteByte(MSG_ENTITY, self.cnt); // icon on radar
92 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
93 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
94 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
96 if (WaypointSprite_isteammate(self.owner, WaypointSprite_getviewentity(to)))
98 float dt = (self.waypointsprite_helpmetime - time) / 0.1;
103 WriteByte(MSG_ENTITY, dt);
106 WriteByte(MSG_ENTITY, 0);
114 void Ent_WaypointSprite();
115 MUTATOR_HOOKFUNCTION(waypointsprites, CSQC_Ent_Update) {
116 if (MUTATOR_RETURNVALUE) return false;
117 if (!ReadMutatorEquals(mutator_argv_int_0, waypointsprites)) return false;
118 Ent_WaypointSprite();
122 void Ent_RemoveWaypointSprite()
124 if (self.netname) strunzone(self.netname);
125 if (self.netname2) strunzone(self.netname2);
126 if (self.netname3) strunzone(self.netname3);
129 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
130 void Ent_WaypointSprite()
132 int sendflags = ReadByte();
133 self.wp_extra = ReadByte();
136 self.spawntime = time;
138 self.draw2d = Draw_WaypointSprite;
140 InterpolateOrigin_Undo();
141 self.iflags |= IFLAG_ORIGIN;
143 if (sendflags & 0x80)
148 self.health = t / 191.0;
149 self.build_finished = 0;
153 t = (t - 192) * 256 + ReadByte();
154 self.build_started = servertime;
155 if (self.build_finished)
156 self.build_starthealth = bound(0, self.health, 1);
158 self.build_starthealth = 0;
159 self.build_finished = servertime + t / 32;
165 self.build_finished = 0;
170 // unfortunately, this needs to be exact (for the 3D display)
171 self.origin_x = ReadCoord();
172 self.origin_y = ReadCoord();
173 self.origin_z = ReadCoord();
174 setorigin(self, self.origin);
179 self.team = ReadByte();
180 self.rule = ReadByte();
186 strunzone(self.netname);
187 self.netname = strzone(ReadString());
193 strunzone(self.netname2);
194 self.netname2 = strzone(ReadString());
200 strunzone(self.netname3);
201 self.netname3 = strzone(ReadString());
206 self.lifetime = ReadCoord();
207 self.fadetime = ReadCoord();
208 self.maxdistance = ReadShort();
209 self.hideflags = ReadByte();
215 self.teamradar_icon = f & BITS(7);
218 self.(teamradar_times[self.teamradar_time_index]) = time;
219 self.teamradar_time_index = (self.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
221 self.teamradar_color_x = ReadByte() / 255.0;
222 self.teamradar_color_y = ReadByte() / 255.0;
223 self.teamradar_color_z = ReadByte() / 255.0;
224 self.helpme = ReadByte() * 0.1;
226 self.helpme += servertime;
229 InterpolateOrigin_Note();
231 self.entremove = Ent_RemoveWaypointSprite;
236 float spritelookupblinkvalue(string s)
238 if (s == WP_Weapon.netname) {
239 if (get_weaponinfo(self.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
242 if (s == WP_Item.netname) return Items[self.wp_extra].m_waypointblink;
247 vector spritelookupcolor(entity this, string s, vector def)
249 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return get_weaponinfo(this.wp_extra).wpcolor;
250 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items[this.wp_extra].m_color;
251 if (s == WP_Buff.netname || s == RADARICON_Buff.netname) return Buffs[this.wp_extra].m_color;
255 string spritelookuptext(string s)
257 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
258 if (s == WP_Weapon.netname) return get_weaponinfo(self.wp_extra).message;
259 if (s == WP_Item.netname) return Items[self.wp_extra].m_waypoint;
260 if (s == WP_Buff.netname) return Buffs[self.wp_extra].m_prettyName;
261 if (s == WP_Monster.netname) return get_monsterinfo(self.wp_extra).monster_name;
263 // need to loop, as our netname could be one of three
264 FOREACH(Waypoints, it.netname == s, LAMBDA(
273 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
275 vector v1, v2, v3, v4;
277 hotspot = -1 * hotspot;
279 // hotspot-relative coordinates of the corners
281 v2 = hotspot + '1 0 0' * sz.x;
282 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
283 v4 = hotspot + '0 1 0' * sz.y;
285 // rotate them, and make them absolute
286 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
287 v1 = rotate(v1, rot) + org;
288 v2 = rotate(v2, rot) + org;
289 v3 = rotate(v3, rot) + org;
290 v4 = rotate(v4, rot) + org;
293 R_BeginPolygon(pic, f);
294 R_PolygonVertex(v1, '0 0 0', rgb, a);
295 R_PolygonVertex(v2, '1 0 0', rgb, a);
296 R_PolygonVertex(v3, '1 1 0', rgb, a);
297 R_PolygonVertex(v4, '0 1 0', rgb, a);
301 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
303 R_BeginPolygon(pic, f);
304 R_PolygonVertex(o, '0 0 0', rgb, a);
305 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
306 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
307 R_PolygonVertex(o + up, '0 1 0', rgb, a);
311 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)
314 float owidth; // outer width
316 hotspot = -1 * hotspot;
318 // hotspot-relative coordinates of the healthbar corners
323 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
324 o = rotate(o, rot) + org;
325 ri = rotate(ri, rot);
326 up = rotate(up, rot);
328 owidth = width + 2 * border;
329 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
331 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
332 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
333 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
334 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
335 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
338 // returns location of sprite text
339 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
341 float size = 9.0 * t;
342 float border = 1.5 * t;
343 float margin = 4.0 * t;
345 float borderDiag = border * 1.414;
346 vector arrowX = eX * size;
347 vector arrowY = eY * (size+borderDiag);
348 vector borderX = eX * (size+borderDiag);
349 vector borderY = eY * (size+borderDiag+border);
351 R_BeginPolygon("", DRAWFLAG_NORMAL);
352 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
353 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
354 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
355 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
356 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
359 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
360 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
361 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
362 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
365 return o + rotate(eY * (borderDiag+size+margin), ang);
368 // returns location of sprite healthbar
369 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
373 float aspect, sa, ca;
375 sw = stringwidth(s, false, fontsize);
382 // how do corners work?
383 aspect = vid_conwidth / vid_conheight;
385 ca = cos(ang) * aspect;
386 if (fabs(sa) > fabs(ca))
389 algny = 0.5 - 0.5 * ca / fabs(sa);
393 algnx = 0.5 - 0.5 * sa / fabs(ca);
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 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
467 if (self.hideflags & 2)
468 return; // radar only
470 if (autocvar_cl_hidewaypoints >= 2)
473 if (self.hideflags & 1)
474 if (autocvar_cl_hidewaypoints)
475 return; // fixed waypoint
477 InterpolateOrigin_Do();
479 float t = GetPlayerColor(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))
492 spriteimage = self.netname;
494 case SPRITERULE_DEFAULT:
498 spriteimage = self.netname;
503 spriteimage = self.netname;
505 case SPRITERULE_TEAMPLAY:
506 if (t == NUM_SPECTATOR + 1)
507 spriteimage = self.netname3;
508 else if (self.team == t)
509 spriteimage = self.netname2;
511 spriteimage = self.netname;
514 error("Invalid waypointsprite rule!");
518 if (spriteimage == "")
521 ++waypointsprite_newcount;
524 dist = vlen(self.origin - view_origin);
527 a = self.alpha * autocvar_hud_panel_fg_alpha;
529 if (self.maxdistance > waypointsprite_normdistance)
530 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
531 else if (self.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(self, spriteimage, self.teamradar_color);
537 self.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 (self.helpme && time < self.helpme)
544 a *= SPRITE_HELPME_BLINK;
545 else if (!self.lifetime) // fading out waypoints don't blink
546 a *= spritelookupblinkvalue(spriteimage);
558 rgb = fixrgbexcess(rgb);
563 o = project_3d_to_2d(self.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 (self.build_finished)
641 if (time < self.build_finished + 0.25)
643 if (time < self.build_started)
644 self.health = self.build_starthealth;
645 else if (time < self.build_finished)
646 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.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(spriteimage);
661 if (self.helpme && time < self.helpme)
662 txt = sprintf(_("%s needing help!"), txt);
663 if (autocvar_g_waypointsprite_uppercase)
664 txt = strtoupper(txt);
666 draw_beginBoldFont();
667 if (self.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 (self.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_InitClient(entity e)
891 void WaypointSprite_Kill(entity wp)
894 if (wp.owner) wp.owner.(wp.owned_by_field) = world;
898 void WaypointSprite_Disown(entity wp, float fadetime)
901 if (wp.classname != "sprite_waypoint")
903 backtrace("Trying to disown a non-waypointsprite");
908 if (wp.exteriormodeltoclient == wp.owner)
909 wp.exteriormodeltoclient = world;
910 wp.owner.(wp.owned_by_field) = world;
913 WaypointSprite_FadeOutIn(wp, fadetime);
917 void WaypointSprite_Think()
919 bool doremove = false;
921 if (self.fade_time && time >= self.teleport_time)
926 if (self.exteriormodeltoclient)
927 WaypointSprite_UpdateOrigin(self, self.exteriormodeltoclient.origin + self.view_ofs);
930 WaypointSprite_Kill(self);
932 self.nextthink = time; // WHY?!?
935 float WaypointSprite_visible_for_player(entity e)
937 // personal waypoints
938 if (self.enemy && self.enemy != e)
942 if (self.rule == SPRITERULE_SPECTATOR)
944 if (!autocvar_sv_itemstime)
946 if (!warmup_stage && IS_PLAYER(e))
949 else if (self.team && self.rule == SPRITERULE_DEFAULT)
951 if (self.team != e.team)
960 entity WaypointSprite_getviewentity(entity e)
962 if (IS_SPEC(e)) e = e.enemy;
963 /* TODO idea (check this breaks nothing)
964 else if (e.classname == "observer")
970 float WaypointSprite_isteammate(entity e, entity e2)
973 return e2.team == e.team;
977 float WaypointSprite_Customize()
979 // this is not in SendEntity because it shall run every frame, not just every update
981 // make spectators see what the player would see
982 entity e = WaypointSprite_getviewentity(other);
984 if (MUTATOR_CALLHOOK(CustomizeWaypoint, self, other))
987 return self.waypointsprite_visible_for_player(e);
990 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
992 void WaypointSprite_Reset()
994 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
996 if (self.fade_time) // there was there before: || g_keyhunt, do we really need this?
997 WaypointSprite_Kill(self);
1000 entity WaypointSprite_Spawn(
1001 entity spr, // sprite
1002 float _lifetime, float maxdistance, // lifetime, max distance
1003 entity ref, vector ofs, // position
1004 entity showto, float t, // show to whom? Use a flag to indicate a team
1005 entity own, .entity ownfield, // remove when own gets killed
1006 float hideable, // true when it should be controlled by cl_hidewaypoints
1007 entity icon // initial icon
1010 entity wp = new(sprite_waypoint);
1011 wp.teleport_time = time + _lifetime;
1012 wp.fade_time = _lifetime;
1013 wp.exteriormodeltoclient = ref;
1017 setorigin(wp, ref.origin + ofs);
1024 wp.currentammo = hideable;
1028 remove(own.(ownfield));
1029 own.(ownfield) = wp;
1030 wp.owned_by_field = ownfield;
1032 wp.fade_rate = maxdistance;
1033 wp.think = WaypointSprite_Think;
1034 wp.nextthink = time;
1035 wp.model1 = spr.netname;
1036 wp.customizeentityforclient = WaypointSprite_Customize;
1037 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1038 wp.reset2 = WaypointSprite_Reset;
1040 wp.colormod = spr.m_color;
1041 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1045 entity WaypointSprite_SpawnFixed(
1050 entity icon // initial icon
1053 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, own, ownfield, true, icon);
1056 entity WaypointSprite_DeployFixed(
1058 float limited_range,
1060 entity icon // initial icon
1070 maxdistance = waypointsprite_limitedrange;
1073 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, world, ofs, world, t, self, waypointsprite_deployed_fixed, false, icon);
1076 entity WaypointSprite_DeployPersonal(
1079 entity icon // initial icon
1082 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, self, waypointsprite_deployed_personal, false, icon);
1085 entity WaypointSprite_Attach(
1087 float limited_range,
1088 entity icon // initial icon
1092 if (self.waypointsprite_attachedforcarrier)
1093 return world; // can't attach to FC
1100 maxdistance = waypointsprite_limitedrange;
1103 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, self, '0 0 64', world, t, self, waypointsprite_attached, false, icon);
1106 entity WaypointSprite_AttachCarrier(
1109 entity icon // initial icon and color
1112 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1113 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', world, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1116 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1117 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1122 void WaypointSprite_DetachCarrier(entity carrier)
1124 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1127 void WaypointSprite_ClearPersonal()
1129 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1132 void WaypointSprite_ClearOwned()
1134 WaypointSprite_Kill(self.waypointsprite_deployed_fixed);
1135 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1136 WaypointSprite_Kill(self.waypointsprite_attached);
1139 void WaypointSprite_PlayerDead()
1141 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1142 WaypointSprite_DetachCarrier(self);
1145 void WaypointSprite_PlayerGone()
1147 WaypointSprite_Disown(self.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1148 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1149 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1150 WaypointSprite_DetachCarrier(self);