1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
6 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
7 float WaypointSprite_SendEntity(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);
24 WriteByte(MSG_ENTITY, (self.health / self.max_health) * 191.0);
28 float dt = self.pain_finished - time;
29 dt = bound(0, dt * 32, 16383);
30 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
31 WriteByte(MSG_ENTITY, (dt & 0x00FF));
37 WriteCoord(MSG_ENTITY, self.origin.x);
38 WriteCoord(MSG_ENTITY, self.origin.y);
39 WriteCoord(MSG_ENTITY, self.origin.z);
44 WriteByte(MSG_ENTITY, self.team);
45 WriteByte(MSG_ENTITY, self.rule);
49 WriteString(MSG_ENTITY, self.model1);
52 WriteString(MSG_ENTITY, self.model2);
55 WriteString(MSG_ENTITY, self.model3);
59 WriteCoord(MSG_ENTITY, self.fade_time);
60 WriteCoord(MSG_ENTITY, self.teleport_time);
61 WriteShort(MSG_ENTITY, self.fade_rate); // maxdist
65 if (self.exteriormodeltoclient == to)
69 if (self.owner.classname == "onslaught_controlpoint")
71 entity wp_owner = self.owner;
72 entity e = WaypointSprite_getviewentity(to);
73 if (SAME_TEAM(e, wp_owner) && wp_owner.goalentity.health >= wp_owner.goalentity.max_health) { f |= 2; }
74 if (!ons_ControlPoint_Attackable(wp_owner, e.team)) { f |= 2; }
76 if (self.owner.classname == "onslaught_generator")
78 entity wp_owner = self.owner;
79 if (wp_owner.isshielded && wp_owner.health >= wp_owner.max_health) { f |= 2; }
80 if (wp_owner.health <= 0) { f |= 2; }
83 WriteByte(MSG_ENTITY, f);
88 WriteByte(MSG_ENTITY, self.cnt); // icon on radar
89 WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
90 WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
91 WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
93 if (WaypointSprite_isteammate(self.owner, WaypointSprite_getviewentity(to)))
95 float dt = (self.waypointsprite_helpmetime - time) / 0.1;
100 WriteByte(MSG_ENTITY, dt);
103 WriteByte(MSG_ENTITY, 0);
111 void Ent_WaypointSprite();
112 MUTATOR_HOOKFUNCTION(waypointsprites, CSQC_Ent_Update) {
113 if (MUTATOR_RETURNVALUE) return false;
114 if (!ReadMutatorEquals(mutator_argv_int_0, waypointsprites)) return false;
115 Ent_WaypointSprite();
119 void Ent_RemoveWaypointSprite()
121 if (self.netname) strunzone(self.netname);
122 if (self.netname2) strunzone(self.netname2);
123 if (self.netname3) strunzone(self.netname3);
126 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
127 void Ent_WaypointSprite()
129 int sendflags = ReadByte();
132 self.spawntime = time;
134 self.draw2d = Draw_WaypointSprite;
136 InterpolateOrigin_Undo();
137 self.iflags |= IFLAG_ORIGIN;
139 if (sendflags & 0x80)
144 self.health = t / 191.0;
145 self.build_finished = 0;
149 t = (t - 192) * 256 + ReadByte();
150 self.build_started = servertime;
151 if (self.build_finished)
152 self.build_starthealth = bound(0, self.health, 1);
154 self.build_starthealth = 0;
155 self.build_finished = servertime + t / 32;
161 self.build_finished = 0;
166 // unfortunately, this needs to be exact (for the 3D display)
167 self.origin_x = ReadCoord();
168 self.origin_y = ReadCoord();
169 self.origin_z = ReadCoord();
170 setorigin(self, self.origin);
175 self.team = ReadByte();
176 self.rule = ReadByte();
182 strunzone(self.netname);
183 self.netname = strzone(ReadString());
189 strunzone(self.netname2);
190 self.netname2 = strzone(ReadString());
196 strunzone(self.netname3);
197 self.netname3 = strzone(ReadString());
202 self.lifetime = ReadCoord();
203 self.fadetime = ReadCoord();
204 self.maxdistance = ReadShort();
205 self.hideflags = ReadByte();
211 self.teamradar_icon = (f & 0x7F);
214 self.(teamradar_times[self.teamradar_time_index]) = time;
215 self.teamradar_time_index = (self.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
217 self.teamradar_color_x = ReadByte() / 255.0;
218 self.teamradar_color_y = ReadByte() / 255.0;
219 self.teamradar_color_z = ReadByte() / 255.0;
220 self.helpme = ReadByte() * 0.1;
222 self.helpme += servertime;
225 InterpolateOrigin_Note();
227 self.entremove = Ent_RemoveWaypointSprite;
232 float spritelookupblinkvalue(string s)
234 if (substring(s, 0, 4) == "wpn-")
235 if (get_weaponinfo(stof(substring(s, 4, strlen(s)))).spawnflags & WEP_FLAG_SUPERWEAPON)
238 FOREACH(ITEMS, it.m_waypoint == s, LAMBDA(
239 return it.m_waypointblink;
244 case "ons-cp-atck": return 2;
245 case "ons-cp-dfnd": return 0.5;
246 case "item-invis": return 2;
247 case "item-extralife": return 2;
248 case "item-speed": return 2;
249 case "tagged-target": return 2;
254 vector spritelookupcolor(string s, vector def)
256 if (substring(s, 0, 4) == "wpn-")
257 return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).wpcolor);
261 case "keycarrier-friend": return '0 1 0';
266 string spritelookuptext(string s)
268 if (substring(s, 0, 4) == "wpn-") { return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).message); }
269 if (substring(s, 0, 5) == "buff-")
271 entity buff = BUFF_NULL;
272 FOREACH(BUFFS, it.m_sprite == s, LAMBDA(
276 return buff.m_prettyName;
281 case "as-push": return _("Push");
282 case "as-destroy": return _("Destroy");
283 case "as-defend": return _("Defend");
284 case "bluebase": return _("Blue base");
285 case "danger": return _("DANGER");
286 case "enemyflagcarrier": return _("Enemy carrier");
287 case "flagcarrier": return _("Flag carrier");
288 case "flagdropped": return _("Dropped flag");
289 case "helpme": return _("Help me!");
290 case "here": return _("Here");
291 case "key-dropped": return _("Dropped key");
292 case "keycarrier-blue": return _("Key carrier");
293 case "keycarrier-finish": return _("Run here");
294 case "keycarrier-friend": return _("Key carrier");
295 case "keycarrier-pink": return _("Key carrier");
296 case "keycarrier-red": return _("Key carrier");
297 case "keycarrier-yellow": return _("Key carrier");
298 case "redbase": return _("Red base");
299 case "yellowbase": return _("Yellow base");
300 case "neutralbase": return _("White base");
301 case "pinkbase": return _("Pink base");
302 case "waypoint": return _("Waypoint");
303 case "ons-gen": return _("Generator");
304 case "ons-gen-shielded": return _("Generator");
305 case "ons-cp": return _("Control point");
306 case "ons-cp-atck": return _("Control point");
307 case "ons-cp-dfnd": return _("Control point");
308 case "race-checkpoint": return _("Checkpoint");
309 case "race-finish": return _("Finish");
310 case "race-start": return _("Start");
311 case "race-start-finish": return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
312 case "goal": return _("Goal");
313 case "nb-ball": return _("Ball");
314 case "ka-ball": return _("Ball");
315 case "ka-ballcarrier": return _("Ball carrier");
316 case "dom-neut": return _("Control point");
317 case "dom-red": return _("Control point");
318 case "dom-blue": return _("Control point");
319 case "dom-yellow": return _("Control point");
320 case "dom-pink": return _("Control point");
321 case "item-invis": return _("Invisibility");
322 case "item-extralife": return _("Extra life");
323 case "item-speed": return _("Speed");
324 case "frozen": return _("Frozen!");
325 case "tagged-target": return _("Tagged");
326 case "vehicle": return _("Vehicle");
327 case "intruder": return _("Intruder!");
334 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
336 vector v1, v2, v3, v4;
338 hotspot = -1 * hotspot;
340 // hotspot-relative coordinates of the corners
342 v2 = hotspot + '1 0 0' * sz.x;
343 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
344 v4 = hotspot + '0 1 0' * sz.y;
346 // rotate them, and make them absolute
347 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
348 v1 = rotate(v1, rot) + org;
349 v2 = rotate(v2, rot) + org;
350 v3 = rotate(v3, rot) + org;
351 v4 = rotate(v4, rot) + org;
354 R_BeginPolygon(pic, f);
355 R_PolygonVertex(v1, '0 0 0', rgb, a);
356 R_PolygonVertex(v2, '1 0 0', rgb, a);
357 R_PolygonVertex(v3, '1 1 0', rgb, a);
358 R_PolygonVertex(v4, '0 1 0', rgb, a);
362 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
364 R_BeginPolygon(pic, f);
365 R_PolygonVertex(o, '0 0 0', rgb, a);
366 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
367 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
368 R_PolygonVertex(o + up, '0 1 0', rgb, a);
372 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)
375 float owidth; // outer width
377 hotspot = -1 * hotspot;
379 // hotspot-relative coordinates of the healthbar corners
384 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
385 o = rotate(o, rot) + org;
386 ri = rotate(ri, rot);
387 up = rotate(up, rot);
389 owidth = width + 2 * border;
390 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
392 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
393 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
394 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
395 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
396 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
399 // returns location of sprite text
400 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
402 float size = 9.0 * t;
403 float border = 1.5 * t;
404 float margin = 4.0 * t;
406 float borderDiag = border * 1.414;
407 vector arrowX = eX * size;
408 vector arrowY = eY * (size+borderDiag);
409 vector borderX = eX * (size+borderDiag);
410 vector borderY = eY * (size+borderDiag+border);
412 R_BeginPolygon("", DRAWFLAG_NORMAL);
413 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
414 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
415 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
416 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
417 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
420 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
421 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
422 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
423 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
426 return o + rotate(eY * (borderDiag+size+margin), ang);
429 // returns location of sprite healthbar
430 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
434 float aspect, sa, ca;
436 sw = stringwidth(s, false, fontsize);
443 // how do corners work?
444 aspect = vid_conwidth / vid_conheight;
446 ca = cos(ang) * aspect;
447 if (fabs(sa) > fabs(ca))
450 algny = 0.5 - 0.5 * ca / fabs(sa);
454 algnx = 0.5 - 0.5 * sa / fabs(ca);
462 // we want to be onscreen
467 if (o.x > vid_conwidth - w)
468 o.x = vid_conwidth - w;
469 if (o.y > vid_conheight - h)
470 o.x = vid_conheight - h;
472 o.x += 0.5 * (w - sw);
474 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
482 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
484 vector yvec = '0.299 0.587 0.114';
485 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
488 vector fixrgbexcess(vector rgb)
491 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
493 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
494 if (rgb.z > 1) rgb.z = 1;
495 } else if (rgb.z > 1) {
496 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
497 if (rgb.y > 1) rgb.y = 1;
499 } else if (rgb.y > 1) {
500 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
502 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
503 if (rgb.z > 1) rgb.z = 1;
504 } else if (rgb.z > 1) {
505 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
506 if (rgb.x > 1) rgb.x = 1;
508 } else if (rgb.z > 1) {
509 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
511 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
512 if (rgb.y > 1) rgb.y = 1;
513 } else if (rgb.y > 1) {
514 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
515 if (rgb.x > 1) rgb.x = 1;
521 void Draw_WaypointSprite()
524 self.alpha = pow(bound(0, (self.fadetime - time) / self.lifetime, 1), waypointsprite_timealphaexponent);
528 if (self.hideflags & 2)
529 return; // radar only
531 if (autocvar_cl_hidewaypoints >= 2)
534 if (self.hideflags & 1)
535 if (autocvar_cl_hidewaypoints)
536 return; // fixed waypoint
538 InterpolateOrigin_Do();
540 float t = GetPlayerColor(player_localnum) + 1;
542 string spriteimage = "";
547 case SPRITERULE_SPECTATOR:
549 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
550 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage))
553 spriteimage = self.netname;
555 case SPRITERULE_DEFAULT:
559 spriteimage = self.netname;
564 spriteimage = self.netname;
566 case SPRITERULE_TEAMPLAY:
567 if (t == NUM_SPECTATOR + 1)
568 spriteimage = self.netname3;
569 else if (self.team == t)
570 spriteimage = self.netname2;
572 spriteimage = self.netname;
575 error("Invalid waypointsprite rule!");
579 if (spriteimage == "")
582 ++waypointsprite_newcount;
585 dist = vlen(self.origin - view_origin);
588 a = self.alpha * autocvar_hud_panel_fg_alpha;
590 if (self.maxdistance > waypointsprite_normdistance)
591 a *= pow(bound(0, (self.maxdistance - dist) / (self.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
592 else if (self.maxdistance > 0)
593 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
595 vector rgb = spritelookupcolor(spriteimage, self.teamradar_color);
598 self.teamradar_color = '1 0 1';
599 printf("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
602 if (time - floor(time) > 0.5)
604 if (self.helpme && time < self.helpme)
605 a *= SPRITE_HELPME_BLINK;
606 else if (!self.lifetime) // fading out waypoints don't blink
607 a *= spritelookupblinkvalue(spriteimage);
619 rgb = fixrgbexcess(rgb);
624 o = project_3d_to_2d(self.origin);
626 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
627 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
628 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
629 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
631 // scale it to be just in view
635 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
636 ang = atan2(-d.x, -d.y);
640 f1 = d.x / vid_conwidth;
641 f2 = d.y / vid_conheight;
643 if (max(f1, -f1) > max(f2, -f2)) {
646 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
649 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
654 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
657 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
661 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
669 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
670 ang = atan2(-d.x, -d.y);
675 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
676 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
677 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
678 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
680 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
682 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
684 t = waypointsprite_scale * vidscale;
685 a *= waypointsprite_alpha;
688 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
689 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
691 if (edgedistance_min < waypointsprite_edgefadedistance) {
692 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
693 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
695 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
696 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
697 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
700 if (self.build_finished)
702 if (time < self.build_finished + 0.25)
704 if (time < self.build_started)
705 self.health = self.build_starthealth;
706 else if (time < self.build_finished)
707 self.health = (time - self.build_started) / (self.build_finished - self.build_started) * (1 - self.build_starthealth) + self.build_starthealth;
715 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
718 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
721 txt = spritelookuptext(spriteimage);
722 if (self.helpme && time < self.helpme)
723 txt = sprintf(_("%s needing help!"), txt);
724 if (autocvar_g_waypointsprite_uppercase)
725 txt = strtoupper(txt);
727 draw_beginBoldFont();
728 if (self.health >= 0)
730 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
733 if (self.build_finished)
738 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
740 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
747 SPRITE_HEALTHBAR_WIDTH * t,
748 SPRITE_HEALTHBAR_HEIGHT * t,
750 SPRITE_HEALTHBAR_BORDER * t,
753 a * SPRITE_HEALTHBAR_BORDERALPHA,
755 a * SPRITE_HEALTHBAR_HEALTHALPHA,
761 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
766 void WaypointSprite_Load_Frames(string ext)
768 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
770 int ext_len = strlen(ext);
771 int n = search_getsize(dh);
772 for (int i = 0; i < n; ++i)
774 string s = search_getfilename(dh, i);
775 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
777 int o = strstrofs(s, "_frame", 0);
778 string sname = strcat("/spriteframes/", substring(s, 0, o));
779 string sframes = substring(s, o + 6, strlen(s) - o - 6);
780 int f = stof(sframes) + 1;
781 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
786 void WaypointSprite_Load();
787 STATIC_INIT(WaypointSprite_Load) {
788 WaypointSprite_Load();
789 WaypointSprite_Load_Frames(".tga");
790 WaypointSprite_Load_Frames(".jpg");
792 void WaypointSprite_Load()
794 waypointsprite_fadedistance = vlen(mi_scale);
795 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
796 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
797 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
798 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
799 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
800 waypointsprite_scale = autocvar_g_waypointsprite_scale;
801 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
802 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
803 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
804 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
805 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
806 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
807 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
808 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
809 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
810 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
811 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
812 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
813 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
814 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
815 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
817 waypointsprite_count = waypointsprite_newcount;
818 waypointsprite_newcount = 0;
823 void WaypointSprite_UpdateSprites(entity e, string m1, string m2, string m3)
842 void WaypointSprite_UpdateHealth(entity e, float f)
844 f = bound(0, f, e.max_health);
845 if (f != e.health || e.pain_finished)
853 void WaypointSprite_UpdateMaxHealth(entity e, float f)
855 if (f != e.max_health || e.pain_finished)
863 void WaypointSprite_UpdateBuildFinished(entity e, float f)
865 if (f != e.pain_finished || e.max_health)
873 void WaypointSprite_UpdateOrigin(entity e, vector o)
882 void WaypointSprite_UpdateRule(entity e, float t, float r)
884 // no check, as this is never called without doing an actual change (usually only once)
890 void WaypointSprite_UpdateTeamRadar(entity e, float icon, vector col)
892 // no check, as this is never called without doing an actual change (usually only once)
893 e.cnt = (icon & 0x7F) | (e.cnt & 0x80);
898 void WaypointSprite_Ping(entity e)
901 if (time < e.waypointsprite_pingtime) return;
902 e.waypointsprite_pingtime = time + 0.3;
903 // ALWAYS sends (this causes a radar circle), thus no check
908 void WaypointSprite_HelpMePing(entity e)
910 WaypointSprite_Ping(e);
911 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
915 void WaypointSprite_FadeOutIn(entity e, float t)
920 e.teleport_time = time + t;
922 else if (t < (e.teleport_time - time))
924 // accelerate the waypoint's dying
926 // (e.teleport_time - time) / wp.fade_time stays
927 // e.teleport_time = time + fadetime
928 float current_fadetime;
929 current_fadetime = e.teleport_time - time;
930 e.teleport_time = time + t;
931 e.fade_time = e.fade_time * t / current_fadetime;
937 void WaypointSprite_Init()
939 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
940 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
941 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
944 void WaypointSprite_InitClient(entity e)
948 void WaypointSprite_Kill(entity wp)
951 if (wp.owner) wp.owner.(wp.owned_by_field) = world;
955 void WaypointSprite_Disown(entity wp, float fadetime)
958 if (wp.classname != "sprite_waypoint")
960 backtrace("Trying to disown a non-waypointsprite");
965 if (wp.exteriormodeltoclient == wp.owner)
966 wp.exteriormodeltoclient = world;
967 wp.owner.(wp.owned_by_field) = world;
970 WaypointSprite_FadeOutIn(wp, fadetime);
974 void WaypointSprite_Think()
976 bool doremove = false;
978 if (self.fade_time && time >= self.teleport_time)
983 if (self.exteriormodeltoclient)
984 WaypointSprite_UpdateOrigin(self, self.exteriormodeltoclient.origin + self.view_ofs);
987 WaypointSprite_Kill(self);
989 self.nextthink = time; // WHY?!?
992 float WaypointSprite_visible_for_player(entity e)
994 // personal waypoints
995 if (self.enemy && self.enemy != e)
999 if (self.rule == SPRITERULE_SPECTATOR)
1001 if (!autocvar_sv_itemstime)
1003 if (!warmup_stage && IS_PLAYER(e))
1006 else if (self.team && self.rule == SPRITERULE_DEFAULT)
1008 if (self.team != e.team)
1017 entity WaypointSprite_getviewentity(entity e)
1019 if (IS_SPEC(e)) e = e.enemy;
1020 /* TODO idea (check this breaks nothing)
1021 else if (e.classname == "observer")
1027 float WaypointSprite_isteammate(entity e, entity e2)
1030 return e2.team == e.team;
1034 float WaypointSprite_Customize()
1036 // this is not in SendEntity because it shall run every frame, not just every update
1038 // make spectators see what the player would see
1039 entity e = WaypointSprite_getviewentity(other);
1041 if (MUTATOR_CALLHOOK(CustomizeWaypoint, self, other))
1044 return self.waypointsprite_visible_for_player(e);
1047 float WaypointSprite_SendEntity(entity to, float sendflags);
1049 void WaypointSprite_Reset()
1051 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1053 if (self.fade_time) // there was there before: || g_keyhunt, do we really need this?
1054 WaypointSprite_Kill(self);
1057 entity WaypointSprite_Spawn(
1058 string spr, // sprite
1059 float _lifetime, float maxdistance, // lifetime, max distance
1060 entity ref, vector ofs, // position
1061 entity showto, float t, // show to whom? Use a flag to indicate a team
1062 entity own, .entity ownfield, // remove when own gets killed
1063 float hideable, // true when it should be controlled by cl_hidewaypoints
1064 float icon, vector rgb // initial icon and color
1067 entity wp = new(sprite_waypoint);
1068 wp.teleport_time = time + _lifetime;
1069 wp.fade_time = _lifetime;
1070 wp.exteriormodeltoclient = ref;
1074 setorigin(wp, ref.origin + ofs);
1081 wp.currentammo = hideable;
1085 remove(own.(ownfield));
1086 own.(ownfield) = wp;
1087 wp.owned_by_field = ownfield;
1089 wp.fade_rate = maxdistance;
1090 wp.think = WaypointSprite_Think;
1091 wp.nextthink = time;
1093 wp.customizeentityforclient = WaypointSprite_Customize;
1094 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1095 wp.reset2 = WaypointSprite_Reset;
1098 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1102 entity WaypointSprite_SpawnFixed(
1107 float icon, vector rgb // initial icon and color
1110 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, own, ownfield, true, icon, rgb);
1113 entity WaypointSprite_DeployFixed(
1115 float limited_range,
1117 float icon, vector rgb // initial icon and color
1127 maxdistance = waypointsprite_limitedrange;
1130 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, world, ofs, world, t, self, waypointsprite_deployed_fixed, false, icon, rgb);
1133 entity WaypointSprite_DeployPersonal(
1136 float icon, vector rgb // initial icon and color
1139 return WaypointSprite_Spawn(spr, 0, 0, world, ofs, world, 0, self, waypointsprite_deployed_personal, false, icon, rgb);
1142 entity WaypointSprite_Attach(
1144 float limited_range,
1145 float icon, vector rgb // initial icon and color
1149 if (self.waypointsprite_attachedforcarrier)
1150 return world; // can't attach to FC
1157 maxdistance = waypointsprite_limitedrange;
1160 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, self, '0 0 64', world, t, self, waypointsprite_attached, false, icon, rgb);
1163 entity WaypointSprite_AttachCarrier(
1166 float icon, vector rgb // initial icon and color
1169 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1170 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', world, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon, rgb);
1173 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
1174 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1179 void WaypointSprite_DetachCarrier(entity carrier)
1181 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1184 void WaypointSprite_ClearPersonal()
1186 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1189 void WaypointSprite_ClearOwned()
1191 WaypointSprite_Kill(self.waypointsprite_deployed_fixed);
1192 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1193 WaypointSprite_Kill(self.waypointsprite_attached);
1196 void WaypointSprite_PlayerDead()
1198 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1199 WaypointSprite_DetachCarrier(self);
1202 void WaypointSprite_PlayerGone()
1204 WaypointSprite_Disown(self.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1205 WaypointSprite_Kill(self.waypointsprite_deployed_personal);
1206 WaypointSprite_Disown(self.waypointsprite_attached, waypointsprite_deadlifetime);
1207 WaypointSprite_DetachCarrier(self);