1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
6 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
7 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
9 WriteMutator(MSG_ENTITY, waypointsprites);
11 sendflags = sendflags & 0x7F;
15 else if (self.max_health || (self.pain_finished && (time < self.pain_finished + 0.25)))
18 WriteByte(MSG_ENTITY, sendflags);
19 WriteByte(MSG_ENTITY, self.wp_extra);
25 WriteByte(MSG_ENTITY, (self.health / self.max_health) * 191.0);
29 float dt = self.pain_finished - time;
30 dt = bound(0, dt * 32, 16383);
31 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
32 WriteByte(MSG_ENTITY, (dt & 0x00FF));
38 WriteCoord(MSG_ENTITY, self.origin.x);
39 WriteCoord(MSG_ENTITY, self.origin.y);
40 WriteCoord(MSG_ENTITY, self.origin.z);
45 WriteByte(MSG_ENTITY, self.team);
46 WriteByte(MSG_ENTITY, self.rule);
50 WriteString(MSG_ENTITY, self.model1);
53 WriteString(MSG_ENTITY, self.model2);
56 WriteString(MSG_ENTITY, self.model3);
60 WriteCoord(MSG_ENTITY, self.fade_time);
61 WriteCoord(MSG_ENTITY, self.teleport_time);
62 WriteShort(MSG_ENTITY, self.fade_rate); // maxdist
66 if (self.exteriormodeltoclient == to)
70 if (self.owner.classname == "onslaught_controlpoint")
72 entity wp_owner = self.owner;
73 entity e = WaypointSprite_getviewentity(to);
74 if (SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { f |= 2; }
75 if (!ons_ControlPoint_Attackable(wp_owner, e.team)) { f |= 2; }
77 if (self.owner.classname == "onslaught_generator")
79 entity wp_owner = self.owner;
80 if (wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { f |= 2; }
81 if (wp_owner.health <= 0) { f |= 2; }
84 WriteByte(MSG_ENTITY, f);
89 WriteByte(MSG_ENTITY, self.cnt); // icon on radar
90 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
91 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
92 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
94 if (WaypointSprite_isteammate(self.owner, WaypointSprite_getviewentity(to)))
96 float dt = (self.waypointsprite_helpmetime - time) / 0.1;
101 WriteByte(MSG_ENTITY, dt);
104 WriteByte(MSG_ENTITY, 0);
112 void Ent_WaypointSprite();
113 MUTATOR_HOOKFUNCTION(waypointsprites, CSQC_Ent_Update) {
114 if (MUTATOR_RETURNVALUE) return false;
115 if (!ReadMutatorEquals(mutator_argv_int_0, waypointsprites)) return false;
116 Ent_WaypointSprite();
120 void Ent_RemoveWaypointSprite()
122 if (self.netname) strunzone(self.netname);
123 if (self.netname2) strunzone(self.netname2);
124 if (self.netname3) strunzone(self.netname3);
127 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
128 void Ent_WaypointSprite()
130 int sendflags = ReadByte();
131 self.wp_extra = ReadByte();
134 self.spawntime = time;
136 self.draw2d = Draw_WaypointSprite;
138 InterpolateOrigin_Undo();
139 self.iflags |= IFLAG_ORIGIN;
141 if (sendflags & 0x80)
146 self.health = t / 191.0;
147 self.build_finished = 0;
151 t = (t - 192) * 256 + ReadByte();
152 self.build_started = servertime;
153 if (self.build_finished)
154 self.build_starthealth = bound(0, self.health, 1);
156 self.build_starthealth = 0;
157 self.build_finished = servertime + t / 32;
163 self.build_finished = 0;
168 // unfortunately, this needs to be exact (for the 3D display)
169 self.origin_x = ReadCoord();
170 self.origin_y = ReadCoord();
171 self.origin_z = ReadCoord();
172 setorigin(self, self.origin);
177 self.team = ReadByte();
178 self.rule = ReadByte();
184 strunzone(self.netname);
185 self.netname = strzone(ReadString());
191 strunzone(self.netname2);
192 self.netname2 = strzone(ReadString());
198 strunzone(self.netname3);
199 self.netname3 = strzone(ReadString());
204 self.lifetime = ReadCoord();
205 self.fadetime = ReadCoord();
206 self.maxdistance = ReadShort();
207 self.hideflags = ReadByte();
213 self.teamradar_icon = f & BITS(7);
216 self.(teamradar_times[self.teamradar_time_index]) = time;
217 self.teamradar_time_index = (self.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
219 self.teamradar_color_x = ReadByte() / 255.0;
220 self.teamradar_color_y = ReadByte() / 255.0;
221 self.teamradar_color_z = ReadByte() / 255.0;
222 self.helpme = ReadByte() * 0.1;
224 self.helpme += servertime;
227 InterpolateOrigin_Note();
229 self.entremove = Ent_RemoveWaypointSprite;
234 float spritelookupblinkvalue(string s)
236 if (s == WP_Weapon.netname) {
237 if (get_weaponinfo(self.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
240 if (s == WP_Item.netname) return Items[self.wp_extra].m_waypointblink;
245 vector spritelookupcolor(entity this, string s, vector def)
247 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return get_weaponinfo(this.wp_extra).wpcolor;
248 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items[this.wp_extra].m_color;
249 if (s == WP_Buff.netname || s == RADARICON_Buff.netname) return Buffs[this.wp_extra].m_color;
253 string spritelookuptext(string s)
255 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
256 if (s == WP_Weapon.netname) return get_weaponinfo(self.wp_extra).message;
257 if (s == WP_Item.netname) return Items[self.wp_extra].m_waypoint;
258 if (s == WP_Buff.netname) return Buffs[self.wp_extra].m_prettyName;
259 if (s == WP_Monster.netname) return get_monsterinfo(self.wp_extra).monster_name;
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))
387 algny = 0.5 - 0.5 * ca / fabs(sa);
391 algnx = 0.5 - 0.5 * sa / fabs(ca);
399 // we want to be onscreen
404 if (o.x > vid_conwidth - w)
405 o.x = vid_conwidth - w;
406 if (o.y > vid_conheight - h)
407 o.x = vid_conheight - h;
409 o.x += 0.5 * (w - sw);
411 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
419 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
421 vector yvec = '0.299 0.587 0.114';
422 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
425 vector fixrgbexcess(vector rgb)
428 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
430 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
431 if (rgb.z > 1) rgb.z = 1;
432 } else if (rgb.z > 1) {
433 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
434 if (rgb.y > 1) rgb.y = 1;
436 } else if (rgb.y > 1) {
437 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
439 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
440 if (rgb.z > 1) rgb.z = 1;
441 } else if (rgb.z > 1) {
442 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
443 if (rgb.x > 1) rgb.x = 1;
445 } else if (rgb.z > 1) {
446 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
448 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
449 if (rgb.y > 1) rgb.y = 1;
450 } else if (rgb.y > 1) {
451 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
452 if (rgb.x > 1) rgb.x = 1;
458 void Draw_WaypointSprite(entity this)
461 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
465 if (self.hideflags & 2)
466 return; // radar only
468 if (autocvar_cl_hidewaypoints >= 2)
471 if (self.hideflags & 1)
472 if (autocvar_cl_hidewaypoints)
473 return; // fixed waypoint
475 InterpolateOrigin_Do();
477 float t = GetPlayerColor(player_localnum) + 1;
479 string spriteimage = "";
484 case SPRITERULE_SPECTATOR:
486 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
487 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
490 spriteimage = self.netname;
492 case SPRITERULE_DEFAULT:
496 spriteimage = self.netname;
501 spriteimage = self.netname;
503 case SPRITERULE_TEAMPLAY:
504 if (t == NUM_SPECTATOR + 1)
505 spriteimage = self.netname3;
506 else if (self.team == t)
507 spriteimage = self.netname2;
509 spriteimage = self.netname;
512 error("Invalid waypointsprite rule!");
516 if (spriteimage == "")
519 ++waypointsprite_newcount;
522 dist = vlen(self.origin - view_origin);
525 a = self.alpha * autocvar_hud_panel_fg_alpha;
527 if (self.maxdistance > waypointsprite_normdistance)
528 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
529 else if (self.maxdistance > 0)
530 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
532 vector rgb = spritelookupcolor(self, spriteimage, self.teamradar_color);
535 self.teamradar_color = '1 0 1';
536 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
539 if (time - floor(time) > 0.5)
541 if (self.helpme && time < self.helpme)
542 a *= SPRITE_HELPME_BLINK;
543 else if (!self.lifetime) // fading out waypoints don't blink
544 a *= spritelookupblinkvalue(spriteimage);
556 rgb = fixrgbexcess(rgb);
561 o = project_3d_to_2d(self.origin);
563 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
564 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
565 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
566 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
568 // scale it to be just in view
572 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
573 ang = atan2(-d.x, -d.y);
577 f1 = d.x / vid_conwidth;
578 f2 = d.y / vid_conheight;
580 if (max(f1, -f1) > max(f2, -f2)) {
583 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
586 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
591 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
594 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
598 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
606 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
607 ang = atan2(-d.x, -d.y);
612 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
613 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
614 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
615 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
617 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
619 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
621 t = waypointsprite_scale * vidscale;
622 a *= waypointsprite_alpha;
625 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
626 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
628 if (edgedistance_min < waypointsprite_edgefadedistance) {
629 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
630 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
632 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
633 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
634 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
637 if (self.build_finished)
639 if (time < self.build_finished + 0.25)
641 if (time < self.build_started)
642 self.health = self.build_starthealth;
643 else if (time < self.build_finished)
644 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
652 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
655 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
658 txt = spritelookuptext(spriteimage);
659 if (self.helpme && time < self.helpme)
660 txt = sprintf(_("%s needing help!"), txt);
661 if (autocvar_g_waypointsprite_uppercase)
662 txt = strtoupper(txt);
664 draw_beginBoldFont();
665 if (self.health >= 0)
667 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
670 if (self.build_finished)
675 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
677 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
684 SPRITE_HEALTHBAR_WIDTH * t,
685 SPRITE_HEALTHBAR_HEIGHT * t,
687 SPRITE_HEALTHBAR_BORDER * t,
690 a * SPRITE_HEALTHBAR_BORDERALPHA,
692 a * SPRITE_HEALTHBAR_HEALTHALPHA,
698 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
703 void WaypointSprite_Load_Frames(string ext)
705 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
707 int ext_len = strlen(ext);
708 int n = search_getsize(dh);
709 for (int i = 0; i < n; ++i)
711 string s = search_getfilename(dh, i);
712 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
714 int o = strstrofs(s, "_frame", 0);
715 string sname = strcat("/spriteframes/", substring(s, 0, o));
716 string sframes = substring(s, o + 6, strlen(s) - o - 6);
717 int f = stof(sframes) + 1;
718 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
723 void WaypointSprite_Load();
724 STATIC_INIT(WaypointSprite_Load) {
725 WaypointSprite_Load();
726 WaypointSprite_Load_Frames(".tga");
727 WaypointSprite_Load_Frames(".jpg");
729 void WaypointSprite_Load()
731 waypointsprite_fadedistance = vlen(mi_scale);
732 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
733 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
734 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
735 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
736 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
737 waypointsprite_scale = autocvar_g_waypointsprite_scale;
738 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
739 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
740 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
741 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
742 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
743 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
744 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
745 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
746 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
747 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
748 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
749 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
750 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
751 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
752 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
754 waypointsprite_count = waypointsprite_newcount;
755 waypointsprite_newcount = 0;
760 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
762 string m1 = _m1.netname;
763 string m2 = _m2.netname;
764 string m3 = _m3.netname;
782 void WaypointSprite_UpdateHealth(entity e, float f)
784 f = bound(0, f, e.max_health);
785 if (f != e.health || e.pain_finished)
793 void WaypointSprite_UpdateMaxHealth(entity e, float f)
795 if (f != e.max_health || e.pain_finished)
803 void WaypointSprite_UpdateBuildFinished(entity e, float f)
805 if (f != e.pain_finished || e.max_health)
813 void WaypointSprite_UpdateOrigin(entity e, vector o)
822 void WaypointSprite_UpdateRule(entity e, float t, float r)
824 // no check, as this is never called without doing an actual change (usually only once)
830 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
832 // no check, as this is never called without doing an actual change (usually only once)
834 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
839 void WaypointSprite_Ping(entity e)
842 if (time < e.waypointsprite_pingtime) return;
843 e.waypointsprite_pingtime = time + 0.3;
844 // ALWAYS sends (this causes a radar circle), thus no check
849 void WaypointSprite_HelpMePing(entity e)
851 WaypointSprite_Ping(e);
852 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
856 void WaypointSprite_FadeOutIn(entity e, float t)
861 e.teleport_time = time + t;
863 else if (t < (e.teleport_time - time))
865 // accelerate the waypoint's dying
867 // (e.teleport_time - time) / wp.fade_time stays
868 // e.teleport_time = time + fadetime
869 float current_fadetime;
870 current_fadetime = e.teleport_time - time;
871 e.teleport_time = time + t;
872 e.fade_time = e.fade_time * t / current_fadetime;
878 void WaypointSprite_Init()
880 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
881 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
882 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
885 void WaypointSprite_InitClient(entity e)
889 void WaypointSprite_Kill(entity wp)
892 if (wp.owner) wp.owner.(wp.owned_by_field) = world;
896 void WaypointSprite_Disown(entity wp, float fadetime)
899 if (wp.classname != "sprite_waypoint")
901 backtrace("Trying to disown a non-waypointsprite");
906 if (wp.exteriormodeltoclient == wp.owner)
907 wp.exteriormodeltoclient = world;
908 wp.owner.(wp.owned_by_field) = world;
911 WaypointSprite_FadeOutIn(wp, fadetime);
915 void WaypointSprite_Think()
917 bool doremove = false;
919 if (self.fade_time && time >= self.teleport_time)
924 if (self.exteriormodeltoclient)
925 WaypointSprite_UpdateOrigin(self, self.exteriormodeltoclient.origin + self.view_ofs);
928 WaypointSprite_Kill(self);
930 self.nextthink = time; // WHY?!?
933 float WaypointSprite_visible_for_player(entity e)
935 // personal waypoints
936 if (self.enemy && self.enemy != e)
940 if (self.rule == SPRITERULE_SPECTATOR)
942 if (!autocvar_sv_itemstime)
944 if (!warmup_stage && IS_PLAYER(e))
947 else if (self.team && self.rule == SPRITERULE_DEFAULT)
949 if (self.team != e.team)
958 entity WaypointSprite_getviewentity(entity e)
960 if (IS_SPEC(e)) e = e.enemy;
961 /* TODO idea (check this breaks nothing)
962 else if (e.classname == "observer")
968 float WaypointSprite_isteammate(entity e, entity e2)
971 return e2.team == e.team;
975 float WaypointSprite_Customize()
977 // this is not in SendEntity because it shall run every frame, not just every update
979 // make spectators see what the player would see
980 entity e = WaypointSprite_getviewentity(other);
982 if (MUTATOR_CALLHOOK(CustomizeWaypoint, self, other))
985 return self.waypointsprite_visible_for_player(e);
988 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
990 void WaypointSprite_Reset()
992 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
994 if (self.fade_time) // there was there before: || g_keyhunt, do we really need this?
995 WaypointSprite_Kill(self);
998 entity WaypointSprite_Spawn(
999 entity spr, // sprite
1000 float _lifetime, float maxdistance, // lifetime, max distance
1001 entity ref, vector ofs, // position
1002 entity showto, float t, // show to whom? Use a flag to indicate a team
1003 entity own, .entity ownfield, // remove when own gets killed
1004 float hideable, // true when it should be controlled by cl_hidewaypoints
1005 entity icon // initial icon
1008 entity wp = new(sprite_waypoint);
1009 wp.teleport_time = time + _lifetime;
1010 wp.fade_time = _lifetime;
1011 wp.exteriormodeltoclient = ref;
1015 setorigin(wp, ref.origin + ofs);
1022 wp.currentammo = hideable;
1026 remove(own.(ownfield));
1027 own.(ownfield) = wp;
1028 wp.owned_by_field = ownfield;
1030 wp.fade_rate = maxdistance;
1031 wp.think = WaypointSprite_Think;
1032 wp.nextthink = time;
1033 wp.model1 = spr.netname;
1034 wp.customizeentityforclient = WaypointSprite_Customize;
1035 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1036 wp.reset2 = WaypointSprite_Reset;
1038 wp.colormod = spr.m_color;
1039 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1043 entity WaypointSprite_SpawnFixed(
1048 entity icon // initial icon
1051 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, own, ownfield, true, icon);
1054 entity WaypointSprite_DeployFixed(
1056 float limited_range,
1058 entity icon // initial icon
1068 maxdistance = waypointsprite_limitedrange;
1071 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, world, ofs, world, t, self, waypointsprite_deployed_fixed, false, icon);
1074 entity WaypointSprite_DeployPersonal(
1077 entity icon // initial icon
1080 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, self, waypointsprite_deployed_personal, false, icon);
1083 entity WaypointSprite_Attach(
1085 float limited_range,
1086 entity icon // initial icon
1090 if (self.waypointsprite_attachedforcarrier)
1091 return world; // can't attach to FC
1098 maxdistance = waypointsprite_limitedrange;
1101 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, self, '0 0 64', world, t, self, waypointsprite_attached, false, icon);
1104 entity WaypointSprite_AttachCarrier(
1107 entity icon // initial icon and color
1110 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1111 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', world, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1114 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
1115 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1120 void WaypointSprite_DetachCarrier(entity carrier)
1122 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1125 void WaypointSprite_ClearPersonal()
1127 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1130 void WaypointSprite_ClearOwned()
1132 WaypointSprite_Kill(self.waypointsprite_deployed_fixed);
1133 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1134 WaypointSprite_Kill(self.waypointsprite_attached);
1137 void WaypointSprite_PlayerDead()
1139 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1140 WaypointSprite_DetachCarrier(self);
1143 void WaypointSprite_PlayerGone()
1145 WaypointSprite_Disown(self.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1146 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1147 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1148 WaypointSprite_DetachCarrier(self);