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, (GetResourceAmount(this, RESOURCE_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 WriteVector(MSG_ENTITY, this.origin);
55 WriteByte(MSG_ENTITY, this.team);
56 WriteByte(MSG_ENTITY, this.rule);
60 WriteString(MSG_ENTITY, this.model1);
63 WriteString(MSG_ENTITY, this.model2);
66 WriteString(MSG_ENTITY, this.model3);
70 WriteCoord(MSG_ENTITY, this.fade_time);
71 WriteCoord(MSG_ENTITY, this.teleport_time);
72 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
73 WriteByte(MSG_ENTITY, f);
78 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
79 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
80 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
81 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
83 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
85 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
90 WriteByte(MSG_ENTITY, dt);
93 WriteByte(MSG_ENTITY, 0);
101 void Ent_WaypointSprite(entity this, bool isnew);
102 NET_HANDLE(waypointsprites, bool isnew) {
103 Ent_WaypointSprite(this, isnew);
107 void Ent_RemoveWaypointSprite(entity this)
109 strfree(this.netname);
110 strfree(this.netname2);
111 strfree(this.netname3);
114 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
115 void Ent_WaypointSprite(entity this, bool isnew)
117 int sendflags = ReadByte();
118 this.wp_extra = ReadByte();
121 this.spawntime = time;
123 this.draw2d = Draw_WaypointSprite;
125 IL_PUSH(g_drawables_2d, this);
126 IL_PUSH(g_radaricons, this);
129 InterpolateOrigin_Undo(this);
130 this.iflags |= IFLAG_ORIGIN;
132 if (sendflags & 0x80)
137 SetResourceAmountExplicit(this, RESOURCE_HEALTH, t / 191.0);
138 this.build_finished = 0;
142 t = (t - 192) * 256 + ReadByte();
143 this.build_started = servertime;
144 if (this.build_finished)
145 this.build_starthealth = bound(0, GetResourceAmount(this, RESOURCE_HEALTH), 1);
147 this.build_starthealth = 0;
148 this.build_finished = servertime + t / 32;
153 SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
154 this.build_finished = 0;
159 // unfortunately, this needs to be exact (for the 3D display)
160 this.origin = ReadVector();
161 setorigin(this, this.origin);
166 this.team = ReadByte();
167 this.rule = ReadByte();
172 strcpy(this.netname, ReadString());
177 strcpy(this.netname2, ReadString());
182 strcpy(this.netname3, ReadString());
187 this.lifetime = ReadCoord();
188 this.fadetime = ReadCoord();
189 this.maxdistance = ReadShort();
190 this.hideflags = ReadByte();
196 this.teamradar_icon = f & BITS(7);
199 this.(teamradar_times[this.teamradar_time_index]) = time;
200 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
202 this.teamradar_color_x = ReadByte() / 255.0;
203 this.teamradar_color_y = ReadByte() / 255.0;
204 this.teamradar_color_z = ReadByte() / 255.0;
205 this.helpme = ReadByte() * 0.1;
207 this.helpme += servertime;
210 InterpolateOrigin_Note(this);
212 this.entremove = Ent_RemoveWaypointSprite;
217 float spritelookupblinkvalue(entity this, string s)
219 if (s == WP_Weapon.netname) {
220 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
223 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
224 if(s == WP_FlagReturn.netname) return 2;
229 vector spritelookupcolor(entity this, string s, vector def)
231 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
232 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
233 if (MUTATOR_CALLHOOK(WP_Format, this, s))
235 return M_ARGV(2, vector);
240 string spritelookuptext(entity this, string s)
242 if(autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
243 return "Spam"; // no need to translate this debug string
244 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
245 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
246 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
247 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
248 if (MUTATOR_CALLHOOK(WP_Format, this, s))
250 return M_ARGV(3, string);
253 // need to loop, as our netname could be one of three
254 FOREACH(Waypoints, it.netname == s, {
261 string spritelookupicon(entity this, string s)
263 // TODO: needs icons! //if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
264 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).model2;
265 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_icon;
266 if (s == WP_Vehicle.netname) return Vehicles_from(this.wp_extra).m_icon;
267 //if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).m_icon;
268 if (MUTATOR_CALLHOOK(WP_Format, this, s))
270 return M_ARGV(4, string);
273 // need to loop, as our netname could be one of three
274 FOREACH(Waypoints, it.netname == s, {
283 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
285 vector v1, v2, v3, v4;
287 hotspot = -1 * hotspot;
289 // hotspot-relative coordinates of the corners
291 v2 = hotspot + '1 0 0' * sz.x;
292 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
293 v4 = hotspot + '0 1 0' * sz.y;
295 // rotate them, and make them absolute
296 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
297 v1 = Rotate(v1, rot) + org;
298 v2 = Rotate(v2, rot) + org;
299 v3 = Rotate(v3, rot) + org;
300 v4 = Rotate(v4, rot) + org;
303 R_BeginPolygon(pic, f);
304 R_PolygonVertex(v1, '0 0 0', rgb, a);
305 R_PolygonVertex(v2, '1 0 0', rgb, a);
306 R_PolygonVertex(v3, '1 1 0', rgb, a);
307 R_PolygonVertex(v4, '0 1 0', rgb, a);
311 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
313 R_BeginPolygon(pic, f);
314 R_PolygonVertex(o, '0 0 0', rgb, a);
315 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
316 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
317 R_PolygonVertex(o + up, '0 1 0', rgb, a);
321 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)
324 float owidth; // outer width
326 hotspot = -1 * hotspot;
328 // hotspot-relative coordinates of the healthbar corners
333 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
334 o = Rotate(o, rot) + org;
335 ri = Rotate(ri, rot);
336 up = Rotate(up, rot);
338 owidth = width + 2 * border;
339 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
341 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
342 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
343 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
344 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
345 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
348 // returns location of sprite text
349 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
351 float size = 9.0 * t;
352 float border = 1.5 * t;
353 float margin = 4.0 * t;
355 float borderDiag = border * 1.414;
356 vector arrowX = eX * size;
357 vector arrowY = eY * (size+borderDiag);
358 vector borderX = eX * (size+borderDiag);
359 vector borderY = eY * (size+borderDiag+border);
361 R_BeginPolygon("", DRAWFLAG_NORMAL);
362 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
363 R_PolygonVertex(o + Rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
364 R_PolygonVertex(o + Rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
365 R_PolygonVertex(o + Rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
366 R_PolygonVertex(o + Rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
369 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
370 R_PolygonVertex(o + Rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
371 R_PolygonVertex(o + Rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
372 R_PolygonVertex(o + Rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
375 return o + Rotate(eY * (borderDiag+size+margin), ang);
378 // returns location of sprite healthbar
379 vector drawsprite_TextOrIcon(bool is_text, vector o, float ang, float minwidth, vector rgb, float a, vector sz, string str)
383 float aspect, sa, ca;
386 sw = stringwidth(str, false, sz);
396 // how do corners work?
397 aspect = vid_conwidth / vid_conheight;
399 ca = cos(ang) * aspect;
400 if (fabs(sa) > fabs(ca))
404 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
409 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
417 // we want to be onscreen
422 if (o.x > vid_conwidth - w)
423 o.x = vid_conwidth - w;
424 if (o.y > vid_conheight - h)
425 o.y = vid_conheight - h;
427 o.x += 0.5 * (w - sw);
430 drawstring(o, str, sz, rgb, a, DRAWFLAG_NORMAL);
432 drawpic(o, str, sz, 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 float health_val = GetResourceAmount(this, RESOURCE_HEALTH);
556 float blink_time = (health_val >= 0) ? (health_val * 10) : time;
557 if (blink_time - floor(blink_time) > 0.5)
559 if (this.helpme && time < this.helpme)
560 a *= SPRITE_HELPME_BLINK;
561 else if (!this.lifetime) // fading out waypoints don't blink
562 a *= spritelookupblinkvalue(this, spriteimage);
574 rgb = fixrgbexcess(rgb);
579 o = project_3d_to_2d(this.origin);
581 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
582 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
583 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
584 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
586 // scale it to be just in view
589 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
590 ang = atan2(-d.x, -d.y);
594 float f1 = d.x / vid_conwidth;
595 float f2 = d.y / vid_conheight;
596 if (f1 == 0) { f1 = 0.000001; }
597 if (f2 == 0) { f2 = 0.000001; }
599 if (max(f1, -f1) > max(f2, -f2)) {
602 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
605 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
610 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
613 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
617 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
625 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
626 ang = atan2(-d.x, -d.y);
631 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
632 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
633 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
634 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
636 float crosshairdistance = sqrt( ((o.x - vid_conwidth/2) ** 2) + ((o.y - vid_conheight/2) ** 2) );
638 t = waypointsprite_scale;
639 a *= waypointsprite_alpha;
642 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
643 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
645 if (edgedistance_min < waypointsprite_edgefadedistance) {
646 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
647 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
649 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
650 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
651 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
654 if (this.build_finished)
656 if (time < this.build_finished + 0.25)
658 if (time < this.build_started)
659 SetResourceAmountExplicit(this, RESOURCE_HEALTH, this.build_starthealth);
660 else if (time < this.build_finished)
661 SetResourceAmountExplicit(this, RESOURCE_HEALTH, (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth);
663 SetResourceAmountExplicit(this, RESOURCE_HEALTH, 1);
666 SetResourceAmountExplicit(this, RESOURCE_HEALTH, -1);
669 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
673 if (!autocvar_g_waypointsprite_text)
675 string spr_icon = spritelookupicon(this, spriteimage);
677 bool icon_found = !(!spr_icon || spr_icon == "");
678 if (icon_found) // it's valid, but let's make sure it exists!
680 pic = strcat(hud_skin_path, "/", spr_icon);
681 if(precache_pic(pic) == "")
683 pic = strcat("gfx/hud/default/", spr_icon);
684 if(!precache_pic(pic))
694 string txt = string_null;
697 txt = spritelookuptext(this, spriteimage);
698 if (this.helpme && time < this.helpme)
699 txt = sprintf(_("%s needing help!"), txt);
700 if (autocvar_g_waypointsprite_uppercase)
701 txt = strtoupper(txt);
703 sz = waypointsprite_fontsize * '1 1 0';
707 // for convenience icon path and color are saved to txt and txt_color
709 txt_color = ((autocvar_g_waypointsprite_iconcolor) ? '1 1 1' : rgb);
710 sz = autocvar_g_waypointsprite_iconsize * '1 1 0';
713 draw_beginBoldFont();
714 if (GetResourceAmount(this, RESOURCE_HEALTH) >= 0)
716 float align = 0, marg;
717 if (this.build_finished)
722 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * sz.y;
724 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * sz.y;
726 float minwidth = (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t;
727 o = drawsprite_TextOrIcon(is_text, o, ang, minwidth, txt_color, a, sz, txt);
731 GetResourceAmount(this, RESOURCE_HEALTH),
734 SPRITE_HEALTHBAR_WIDTH * t,
735 SPRITE_HEALTHBAR_HEIGHT * t,
737 SPRITE_HEALTHBAR_BORDER * t,
740 a * SPRITE_HEALTHBAR_BORDERALPHA,
742 a * SPRITE_HEALTHBAR_HEALTHALPHA,
748 drawsprite_TextOrIcon(is_text, o, ang, 0, txt_color, a, sz, txt);
754 void WaypointSprite_Load_Frames(string ext)
756 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
758 int ext_len = strlen(ext);
759 int n = search_getsize(dh);
760 for (int i = 0; i < n; ++i)
762 string s = search_getfilename(dh, i);
763 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
765 int o = strstrofs(s, "_frame", 0);
766 string sname = strcat("/spriteframes/", substring(s, 0, o));
767 string sframes = substring(s, o + 6, strlen(s) - o - 6);
768 int f = stof(sframes) + 1;
769 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
774 void WaypointSprite_Load();
775 STATIC_INIT(WaypointSprite_Load) {
776 WaypointSprite_Load();
777 WaypointSprite_Load_Frames(".tga");
778 WaypointSprite_Load_Frames(".jpg");
780 void WaypointSprite_Load()
782 waypointsprite_fadedistance = vlen(mi_scale);
783 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
784 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
785 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
786 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
787 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
788 waypointsprite_scale = autocvar_g_waypointsprite_scale;
789 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
790 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
791 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
792 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
793 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
794 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
795 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
796 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
797 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
798 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
799 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
800 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
801 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
802 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
803 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
805 waypointsprite_count = waypointsprite_newcount;
806 waypointsprite_newcount = 0;
811 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
813 string m1 = _m1.netname;
814 string m2 = _m2.netname;
815 string m3 = _m3.netname;
833 void WaypointSprite_UpdateHealth(entity e, float f)
835 f = bound(0, f, e.max_health);
836 if (f != GetResourceAmount(e, RESOURCE_HEALTH) || e.pain_finished)
838 SetResourceAmountExplicit(e, RESOURCE_HEALTH, f);
844 void WaypointSprite_UpdateMaxHealth(entity e, float f)
846 if (f != e.max_health || e.pain_finished)
854 void WaypointSprite_UpdateBuildFinished(entity e, float f)
856 if (f != e.pain_finished || e.max_health)
864 void WaypointSprite_UpdateOrigin(entity e, vector o)
873 void WaypointSprite_UpdateRule(entity e, float t, float r)
875 // no check, as this is never called without doing an actual change (usually only once)
881 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
883 // no check, as this is never called without doing an actual change (usually only once)
885 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
890 void WaypointSprite_Ping(entity e)
893 if (time < e.waypointsprite_pingtime) return;
894 e.waypointsprite_pingtime = time + 0.3;
895 // ALWAYS sends (this causes a radar circle), thus no check
900 void WaypointSprite_HelpMePing(entity e)
902 WaypointSprite_Ping(e);
903 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
907 void WaypointSprite_FadeOutIn(entity e, float t)
912 e.teleport_time = time + t;
914 else if (t < (e.teleport_time - time))
916 // accelerate the waypoint's dying
918 // (e.teleport_time - time) / wp.fade_time stays
919 // e.teleport_time = time + fadetime
920 float current_fadetime = e.teleport_time - time;
921 e.teleport_time = time + t;
923 e.fade_time = -e.fade_time;
924 e.fade_time = e.fade_time * t / current_fadetime;
930 void WaypointSprite_Init()
932 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
933 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
934 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
937 void WaypointSprite_Kill(entity wp)
940 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
944 void WaypointSprite_Disown(entity wp, float fadetime)
947 if (wp.classname != "sprite_waypoint")
949 backtrace("Trying to disown a non-waypointsprite");
954 if (wp.exteriormodeltoclient == wp.owner)
955 wp.exteriormodeltoclient = NULL;
956 wp.owner.(wp.owned_by_field) = NULL;
959 WaypointSprite_FadeOutIn(wp, fadetime);
963 void WaypointSprite_Think(entity this)
965 bool doremove = false;
967 if (this.fade_time && time >= this.teleport_time)
972 if (this.exteriormodeltoclient)
973 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
976 WaypointSprite_Kill(this);
978 this.nextthink = time; // WHY?!?
981 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
983 // personal waypoints
984 if (this.enemy && this.enemy != view)
988 if (this.rule == SPRITERULE_SPECTATOR)
990 if (!autocvar_sv_itemstime)
992 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
995 else if (this.team && this.rule == SPRITERULE_DEFAULT)
997 if (this.team != view.team)
999 if (!IS_PLAYER(view))
1006 entity WaypointSprite_getviewentity(entity e)
1008 if (IS_SPEC(e)) e = e.enemy;
1009 /* TODO idea (check this breaks nothing)
1010 else if (e.classname == "observer")
1016 float WaypointSprite_isteammate(entity e, entity e2)
1019 return e2.team == e.team;
1023 bool WaypointSprite_Customize(entity this, entity client)
1025 // this is not in SendEntity because it shall run every frame, not just every update
1027 // make spectators see what the player would see
1028 entity e = WaypointSprite_getviewentity(client);
1030 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
1033 return this.waypointsprite_visible_for_player(this, client, e);
1036 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
1038 void WaypointSprite_Reset(entity this)
1040 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
1043 WaypointSprite_Kill(this);
1046 entity WaypointSprite_Spawn(
1047 entity spr, // sprite
1048 float _lifetime, float maxdistance, // lifetime, max distance
1049 entity ref, vector ofs, // position
1050 entity showto, float t, // show to whom? Use a flag to indicate a team
1051 entity own, .entity ownfield, // remove when own gets killed
1052 float hideable, // true when it should be controlled by cl_hidewaypoints
1053 entity icon // initial icon
1056 entity wp = new(sprite_waypoint);
1057 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1059 _lifetime = -_lifetime;
1060 wp.teleport_time = time + _lifetime;
1061 wp.exteriormodeltoclient = ref;
1065 setorigin(wp, ref.origin + ofs);
1072 wp.currentammo = hideable;
1076 delete(own.(ownfield));
1077 own.(ownfield) = wp;
1078 wp.owned_by_field = ownfield;
1080 wp.fade_rate = maxdistance;
1081 setthink(wp, WaypointSprite_Think);
1082 wp.nextthink = time;
1083 wp.model1 = spr.netname;
1084 setcefc(wp, WaypointSprite_Customize);
1085 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1086 wp.reset2 = WaypointSprite_Reset;
1088 wp.colormod = spr.m_color;
1089 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1093 entity WaypointSprite_SpawnFixed(
1098 entity icon // initial icon
1101 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1104 entity WaypointSprite_DeployFixed(
1109 entity icon // initial icon
1119 maxdistance = waypointsprite_limitedrange;
1122 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1125 entity WaypointSprite_DeployPersonal(
1129 entity icon // initial icon
1132 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1135 entity WaypointSprite_Attach(
1139 entity icon // initial icon
1143 if (player.waypointsprite_attachedforcarrier)
1144 return NULL; // can't attach to FC
1151 maxdistance = waypointsprite_limitedrange;
1154 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1157 entity WaypointSprite_AttachCarrier(
1160 entity icon // initial icon and color
1163 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1164 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1165 if (GetResourceAmount(carrier, RESOURCE_HEALTH))
1167 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1168 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(carrier, RESOURCE_HEALTH), GetResourceAmount(carrier, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1173 void WaypointSprite_DetachCarrier(entity carrier)
1175 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1178 void WaypointSprite_ClearPersonal(entity this)
1180 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1183 void WaypointSprite_ClearOwned(entity this)
1185 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1186 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1187 WaypointSprite_Kill(this.waypointsprite_attached);
1190 void WaypointSprite_PlayerDead(entity this)
1192 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1193 WaypointSprite_DetachCarrier(this);
1196 void WaypointSprite_PlayerGone(entity this)
1198 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1199 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1200 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1201 WaypointSprite_DetachCarrier(this);