1 #include "waypointsprites.qh"
3 REGISTER_MUTATOR(waypointsprites, true);
5 REGISTER_NET_LINKED(waypointsprites)
8 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
9 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
11 WriteHeader(MSG_ENTITY, waypointsprites);
13 sendflags = sendflags & 0x7F;
15 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
19 if(this.currentammo == 1)
21 if(this.exteriormodeltoclient == to)
23 if(this.currentammo == 2)
26 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
27 sendflags = M_ARGV(2, int);
30 WriteByte(MSG_ENTITY, sendflags);
31 WriteByte(MSG_ENTITY, this.wp_extra);
37 WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
41 float dt = this.pain_finished - time;
42 dt = bound(0, dt * 32, 16383);
43 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
44 WriteByte(MSG_ENTITY, (dt & 0x00FF));
50 WriteCoord(MSG_ENTITY, this.origin.x);
51 WriteCoord(MSG_ENTITY, this.origin.y);
52 WriteCoord(MSG_ENTITY, this.origin.z);
57 WriteByte(MSG_ENTITY, this.team);
58 WriteByte(MSG_ENTITY, this.rule);
62 WriteString(MSG_ENTITY, this.model1);
65 WriteString(MSG_ENTITY, this.model2);
68 WriteString(MSG_ENTITY, this.model3);
72 WriteCoord(MSG_ENTITY, this.fade_time);
73 WriteCoord(MSG_ENTITY, this.teleport_time);
74 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
75 WriteByte(MSG_ENTITY, f);
80 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
81 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
82 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
83 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
85 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
87 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
92 WriteByte(MSG_ENTITY, dt);
95 WriteByte(MSG_ENTITY, 0);
103 void Ent_WaypointSprite(entity this, bool isnew);
104 NET_HANDLE(waypointsprites, bool isnew) {
105 Ent_WaypointSprite(this, isnew);
109 void Ent_RemoveWaypointSprite(entity this)
111 if (this.netname) strunzone(this.netname);
112 if (this.netname2) strunzone(this.netname2);
113 if (this.netname3) strunzone(this.netname3);
116 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
117 void Ent_WaypointSprite(entity this, bool isnew)
119 int sendflags = ReadByte();
120 this.wp_extra = ReadByte();
123 this.spawntime = time;
125 this.draw2d = Draw_WaypointSprite;
127 IL_PUSH(g_drawables_2d, this);
128 IL_PUSH(g_radaricons, this);
131 InterpolateOrigin_Undo(this);
132 this.iflags |= IFLAG_ORIGIN;
134 if (sendflags & 0x80)
139 this.health = t / 191.0;
140 this.build_finished = 0;
144 t = (t - 192) * 256 + ReadByte();
145 this.build_started = servertime;
146 if (this.build_finished)
147 this.build_starthealth = bound(0, this.health, 1);
149 this.build_starthealth = 0;
150 this.build_finished = servertime + t / 32;
156 this.build_finished = 0;
161 // unfortunately, this needs to be exact (for the 3D display)
162 this.origin_x = ReadCoord();
163 this.origin_y = ReadCoord();
164 this.origin_z = ReadCoord();
165 setorigin(this, this.origin);
170 this.team = ReadByte();
171 this.rule = ReadByte();
177 strunzone(this.netname);
178 this.netname = strzone(ReadString());
184 strunzone(this.netname2);
185 this.netname2 = strzone(ReadString());
191 strunzone(this.netname3);
192 this.netname3 = strzone(ReadString());
197 this.lifetime = ReadCoord();
198 this.fadetime = ReadCoord();
199 this.maxdistance = ReadShort();
200 this.hideflags = ReadByte();
206 this.teamradar_icon = f & BITS(7);
209 this.(teamradar_times[this.teamradar_time_index]) = time;
210 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
212 this.teamradar_color_x = ReadByte() / 255.0;
213 this.teamradar_color_y = ReadByte() / 255.0;
214 this.teamradar_color_z = ReadByte() / 255.0;
215 this.helpme = ReadByte() * 0.1;
217 this.helpme += servertime;
220 InterpolateOrigin_Note(this);
222 this.entremove = Ent_RemoveWaypointSprite;
227 float spritelookupblinkvalue(entity this, string s)
229 if (s == WP_Weapon.netname) {
230 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
233 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
234 if(s == WP_FlagReturn.netname) return 2;
239 vector spritelookupcolor(entity this, string s, vector def)
241 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
242 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
243 if (MUTATOR_CALLHOOK(WP_Format, this, s))
245 return M_ARGV(2, vector);
250 string spritelookuptext(entity this, string s)
252 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
253 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
254 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
255 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
256 if (MUTATOR_CALLHOOK(WP_Format, this, s))
258 return M_ARGV(3, string);
261 // need to loop, as our netname could be one of three
262 FOREACH(Waypoints, it.netname == s, {
269 string spritelookupicon(entity this, string s)
271 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
272 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
273 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
274 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
275 if (MUTATOR_CALLHOOK(WP_Format, this, s))
277 return M_ARGV(4, string);
280 // need to loop, as our netname could be one of three
281 FOREACH(Waypoints, it.netname == s, {
290 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
292 vector v1, v2, v3, v4;
294 hotspot = -1 * hotspot;
296 // hotspot-relative coordinates of the corners
298 v2 = hotspot + '1 0 0' * sz.x;
299 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
300 v4 = hotspot + '0 1 0' * sz.y;
302 // rotate them, and make them absolute
303 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
304 v1 = Rotate(v1, rot) + org;
305 v2 = Rotate(v2, rot) + org;
306 v3 = Rotate(v3, rot) + org;
307 v4 = Rotate(v4, rot) + org;
310 R_BeginPolygon(pic, f);
311 R_PolygonVertex(v1, '0 0 0', rgb, a);
312 R_PolygonVertex(v2, '1 0 0', rgb, a);
313 R_PolygonVertex(v3, '1 1 0', rgb, a);
314 R_PolygonVertex(v4, '0 1 0', rgb, a);
318 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
320 R_BeginPolygon(pic, f);
321 R_PolygonVertex(o, '0 0 0', rgb, a);
322 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
323 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
324 R_PolygonVertex(o + up, '0 1 0', rgb, a);
328 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)
331 float owidth; // outer width
333 hotspot = -1 * hotspot;
335 // hotspot-relative coordinates of the healthbar corners
340 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
341 o = Rotate(o, rot) + org;
342 ri = Rotate(ri, rot);
343 up = Rotate(up, rot);
345 owidth = width + 2 * border;
346 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
348 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
349 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
350 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
351 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
352 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
355 // returns location of sprite text
356 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
358 float size = 9.0 * t;
359 float border = 1.5 * t;
360 float margin = 4.0 * t;
362 float borderDiag = border * 1.414;
363 vector arrowX = eX * size;
364 vector arrowY = eY * (size+borderDiag);
365 vector borderX = eX * (size+borderDiag);
366 vector borderY = eY * (size+borderDiag+border);
368 R_BeginPolygon("", DRAWFLAG_NORMAL);
369 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
370 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
371 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
372 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
373 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
376 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
377 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
378 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
379 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
382 return o + Rotate(eY * (borderDiag+size+margin), ang);
385 // returns location of sprite healthbar
386 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
390 float aspect, sa, ca;
392 sw = stringwidth(s, false, fontsize);
399 // how do corners work?
400 aspect = vid_conwidth / vid_conheight;
402 ca = cos(ang) * aspect;
403 if (fabs(sa) > fabs(ca))
407 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
412 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
420 // we want to be onscreen
425 if (o.x > vid_conwidth - w)
426 o.x = vid_conwidth - w;
427 if (o.y > vid_conheight - h)
428 o.x = vid_conheight - h;
430 o.x += 0.5 * (w - sw);
432 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
440 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
442 vector yvec = '0.299 0.587 0.114';
443 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
446 vector fixrgbexcess(vector rgb)
449 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
451 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
452 if (rgb.z > 1) rgb.z = 1;
453 } else if (rgb.z > 1) {
454 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
455 if (rgb.y > 1) rgb.y = 1;
457 } else if (rgb.y > 1) {
458 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
460 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
461 if (rgb.z > 1) rgb.z = 1;
462 } else if (rgb.z > 1) {
463 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
464 if (rgb.x > 1) rgb.x = 1;
466 } else if (rgb.z > 1) {
467 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
469 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
470 if (rgb.y > 1) rgb.y = 1;
471 } else if (rgb.y > 1) {
472 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
473 if (rgb.x > 1) rgb.x = 1;
479 void Draw_WaypointSprite(entity this)
481 if (this.lifetime > 0)
482 this.alpha = (bound(0, (this.fadetime - time) / this.lifetime, 1) ** waypointsprite_timealphaexponent);
486 if (this.hideflags & 2)
487 return; // radar only
489 if (autocvar_cl_hidewaypoints >= 2)
492 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
493 return; // fixed waypoint
495 InterpolateOrigin_Do(this);
497 float t = entcs_GetTeam(player_localnum) + 1;
498 string spriteimage = "";
503 case SPRITERULE_SPECTATOR:
505 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
506 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
509 spriteimage = this.netname;
511 case SPRITERULE_DEFAULT:
515 spriteimage = this.netname;
520 spriteimage = this.netname;
522 case SPRITERULE_TEAMPLAY:
523 if (t == NUM_SPECTATOR + 1)
524 spriteimage = this.netname3;
525 else if (this.team == t)
526 spriteimage = this.netname2;
528 spriteimage = this.netname;
531 error("Invalid waypointsprite rule!");
535 if (spriteimage == "")
538 ++waypointsprite_newcount;
540 float dist = vlen(this.origin - view_origin);
541 float a = this.alpha * autocvar_hud_panel_fg_alpha;
543 if (this.maxdistance > waypointsprite_normdistance)
544 a *= (bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent);
545 else if (this.maxdistance > 0)
546 a *= (bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1) ** waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
548 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
551 this.teamradar_color = '1 0 1';
552 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it", spriteimage);
555 if (time - floor(time) > 0.5)
557 if (this.helpme && time < this.helpme)
558 a *= SPRITE_HELPME_BLINK;
559 else if (this.lifetime > 0) // fading out waypoints don't blink
560 a *= spritelookupblinkvalue(this, spriteimage);
572 rgb = fixrgbexcess(rgb);
577 o = project_3d_to_2d(this.origin);
579 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
580 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
581 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
582 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
584 // scale it to be just in view
588 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
589 ang = atan2(-d.x, -d.y);
593 f1 = d.x / vid_conwidth;
594 f2 = d.y / vid_conheight;
596 if (max(f1, -f1) > max(f2, -f2)) {
599 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
602 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
607 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
610 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
614 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
622 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
623 ang = atan2(-d.x, -d.y);
628 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
629 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
630 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
631 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
633 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
635 t = waypointsprite_scale;
636 a *= waypointsprite_alpha;
639 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
640 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
642 if (edgedistance_min < waypointsprite_edgefadedistance) {
643 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
644 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
646 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
647 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
648 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
651 if (this.build_finished)
653 if (time < this.build_finished + 0.25)
655 if (time < this.build_started)
656 this.health = this.build_starthealth;
657 else if (time < this.build_finished)
658 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
666 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
668 string spr_icon = spritelookupicon(this, spriteimage);
669 string pic = spr_icon;
670 bool icon_found = !(!spr_icon || spr_icon == "");
671 if (icon_found) // it's valid, but let's make sure it exists!
673 pic = strcat(hud_skin_path, "/", spr_icon);
674 if(precache_pic(pic) == "")
676 pic = strcat("gfx/hud/default/", spr_icon);
677 if(!precache_pic(pic))
682 string txt = string_null;
683 if (autocvar_g_waypointsprite_text || !icon_found)
685 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
688 txt = spritelookuptext(this, spriteimage);
689 if (this.helpme && time < this.helpme)
690 txt = sprintf(_("%s needing help!"), txt);
691 if (autocvar_g_waypointsprite_uppercase)
692 txt = strtoupper(txt);
695 draw_beginBoldFont();
696 if (this.health >= 0)
699 if (this.build_finished)
704 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
706 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
713 SPRITE_HEALTHBAR_WIDTH * t,
714 SPRITE_HEALTHBAR_HEIGHT * t,
716 SPRITE_HEALTHBAR_BORDER * t,
719 a * SPRITE_HEALTHBAR_BORDERALPHA,
721 a * SPRITE_HEALTHBAR_HEALTHALPHA,
725 if(autocvar_g_waypointsprite_text || !icon_found)
726 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
728 drawpic(o - vec2(iconsize/2, autocvar_g_waypointsprite_iconsize*t + 2*marg + SPRITE_HEALTHBAR_HEIGHT*t), pic, '1 1 0'*iconsize, rgb, a, DRAWFLAG_NORMAL);
732 if (autocvar_g_waypointsprite_text || !icon_found)
733 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
735 drawpic_aspect(o, pic, vec2(SPRITE_HEALTHBAR_WIDTH * t, SPRITE_HEALTHBAR_HEIGHT * t), rgb, a, DRAWFLAG_NORMAL);
740 void WaypointSprite_Load_Frames(string ext)
742 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
744 int ext_len = strlen(ext);
745 int n = search_getsize(dh);
746 for (int i = 0; i < n; ++i)
748 string s = search_getfilename(dh, i);
749 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
751 int o = strstrofs(s, "_frame", 0);
752 string sname = strcat("/spriteframes/", substring(s, 0, o));
753 string sframes = substring(s, o + 6, strlen(s) - o - 6);
754 int f = stof(sframes) + 1;
755 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
760 void WaypointSprite_Load();
761 STATIC_INIT(WaypointSprite_Load) {
762 WaypointSprite_Load();
763 WaypointSprite_Load_Frames(".tga");
764 WaypointSprite_Load_Frames(".jpg");
766 void WaypointSprite_Load()
768 waypointsprite_fadedistance = vlen(mi_scale);
769 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
770 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
771 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
772 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
773 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
774 waypointsprite_scale = autocvar_g_waypointsprite_scale;
775 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
776 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
777 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
778 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
779 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
780 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
781 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
782 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
783 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
784 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
785 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
786 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
787 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
788 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
789 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
791 waypointsprite_count = waypointsprite_newcount;
792 waypointsprite_newcount = 0;
797 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
799 string m1 = _m1.netname;
800 string m2 = _m2.netname;
801 string m3 = _m3.netname;
819 void WaypointSprite_UpdateHealth(entity e, float f)
821 f = bound(0, f, e.max_health);
822 if (f != e.health || e.pain_finished)
830 void WaypointSprite_UpdateMaxHealth(entity e, float f)
832 if (f != e.max_health || e.pain_finished)
840 void WaypointSprite_UpdateBuildFinished(entity e, float f)
842 if (f != e.pain_finished || e.max_health)
850 void WaypointSprite_UpdateOrigin(entity e, vector o)
859 void WaypointSprite_UpdateRule(entity e, float t, float r)
861 // no check, as this is never called without doing an actual change (usually only once)
867 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
869 // no check, as this is never called without doing an actual change (usually only once)
871 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
876 void WaypointSprite_Ping(entity e)
879 if (time < e.waypointsprite_pingtime) return;
880 e.waypointsprite_pingtime = time + 0.3;
881 // ALWAYS sends (this causes a radar circle), thus no check
886 void WaypointSprite_HelpMePing(entity e)
888 WaypointSprite_Ping(e);
889 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
893 void WaypointSprite_FadeOutIn(entity e, float t)
898 e.teleport_time = time + t;
900 else if (t < (e.teleport_time - time))
902 // accelerate the waypoint's dying
904 // (e.teleport_time - time) / wp.fade_time stays
905 // e.teleport_time = time + fadetime
906 float current_fadetime = e.teleport_time - time;
907 e.teleport_time = time + t;
909 e.fade_time = -e.fade_time;
910 e.fade_time = e.fade_time * t / current_fadetime;
916 void WaypointSprite_Init()
918 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
919 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
920 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
923 void WaypointSprite_Kill(entity wp)
926 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
930 void WaypointSprite_Disown(entity wp, float fadetime)
933 if (wp.classname != "sprite_waypoint")
935 backtrace("Trying to disown a non-waypointsprite");
940 if (wp.exteriormodeltoclient == wp.owner)
941 wp.exteriormodeltoclient = NULL;
942 wp.owner.(wp.owned_by_field) = NULL;
945 WaypointSprite_FadeOutIn(wp, fadetime);
949 void WaypointSprite_Think(entity this)
951 bool doremove = false;
953 if (this.fade_time && time >= this.teleport_time)
958 if (this.exteriormodeltoclient)
959 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
962 WaypointSprite_Kill(this);
964 this.nextthink = time; // WHY?!?
967 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
969 // personal waypoints
970 if (this.enemy && this.enemy != view)
974 if (this.rule == SPRITERULE_SPECTATOR)
976 if (!autocvar_sv_itemstime)
978 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
981 else if (this.team && this.rule == SPRITERULE_DEFAULT)
983 if (this.team != view.team)
985 if (!IS_PLAYER(view))
992 entity WaypointSprite_getviewentity(entity e)
994 if (IS_SPEC(e)) e = e.enemy;
995 /* TODO idea (check this breaks nothing)
996 else if (e.classname == "observer")
1002 float WaypointSprite_isteammate(entity e, entity e2)
1005 return e2.team == e.team;
1009 bool WaypointSprite_Customize(entity this, entity client)
1011 // this is not in SendEntity because it shall run every frame, not just every update
1013 // make spectators see what the player would see
1014 entity e = WaypointSprite_getviewentity(client);
1016 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1019 return this.waypointsprite_visible_for_player(this, client, e);
1022 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1024 void WaypointSprite_Reset(entity this)
1026 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1029 WaypointSprite_Kill(this);
1032 entity WaypointSprite_Spawn(
1033 entity spr, // sprite
1034 float _lifetime, float maxdistance, // lifetime, max distance
1035 entity ref, vector ofs, // position
1036 entity showto, float t, // show to whom? Use a flag to indicate a team
1037 entity own, .entity ownfield, // remove when own gets killed
1038 float hideable, // true when it should be controlled by cl_hidewaypoints
1039 entity icon // initial icon
1042 entity wp = new(sprite_waypoint);
1043 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1045 _lifetime = -_lifetime;
1046 wp.teleport_time = time + _lifetime;
1047 wp.exteriormodeltoclient = ref;
1051 setorigin(wp, ref.origin + ofs);
1058 wp.currentammo = hideable;
1062 delete(own.(ownfield));
1063 own.(ownfield) = wp;
1064 wp.owned_by_field = ownfield;
1066 wp.fade_rate = maxdistance;
1067 setthink(wp, WaypointSprite_Think);
1068 wp.nextthink = time;
1069 wp.model1 = spr.netname;
1070 setcefc(wp, WaypointSprite_Customize);
1071 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1072 wp.reset2 = WaypointSprite_Reset;
1074 wp.colormod = spr.m_color;
1075 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1079 entity WaypointSprite_SpawnFixed(
1084 entity icon // initial icon
1087 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1090 entity WaypointSprite_DeployFixed(
1092 float limited_range,
1095 entity icon // initial icon
1105 maxdistance = waypointsprite_limitedrange;
1108 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1111 entity WaypointSprite_DeployPersonal(
1115 entity icon // initial icon
1118 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1121 entity WaypointSprite_Attach(
1124 float limited_range,
1125 entity icon // initial icon
1129 if (player.waypointsprite_attachedforcarrier)
1130 return NULL; // can't attach to FC
1137 maxdistance = waypointsprite_limitedrange;
1140 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1143 entity WaypointSprite_AttachCarrier(
1146 entity icon // initial icon and color
1149 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1150 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1153 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1154 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1159 void WaypointSprite_DetachCarrier(entity carrier)
1161 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1164 void WaypointSprite_ClearPersonal(entity this)
1166 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1169 void WaypointSprite_ClearOwned(entity this)
1171 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1172 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1173 WaypointSprite_Kill(this.waypointsprite_attached);
1176 void WaypointSprite_PlayerDead(entity this)
1178 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1179 WaypointSprite_DetachCarrier(this);
1182 void WaypointSprite_PlayerGone(entity this)
1184 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1185 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1186 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1187 WaypointSprite_DetachCarrier(this);