1 #include "waypointsprites.qh"
5 REGISTER_MUTATOR(waypointsprites, true);
7 REGISTER_NET_LINKED(waypointsprites)
10 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
11 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags)
13 WriteHeader(MSG_ENTITY, waypointsprites);
15 sendflags = sendflags & 0x7F;
17 if (this.max_health || (this.pain_finished && (time < this.pain_finished + 0.25)))
23 if(this.exteriormodeltoclient == to)
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, 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))
388 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
393 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
401 // we want to be onscreen
406 if (o.x > vid_conwidth - w)
407 o.x = vid_conwidth - w;
408 if (o.y > vid_conheight - h)
409 o.x = vid_conheight - h;
411 o.x += 0.5 * (w - sw);
413 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
421 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
423 vector yvec = '0.299 0.587 0.114';
424 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
427 vector fixrgbexcess(vector rgb)
430 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
432 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
433 if (rgb.z > 1) rgb.z = 1;
434 } else if (rgb.z > 1) {
435 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
436 if (rgb.y > 1) rgb.y = 1;
438 } else if (rgb.y > 1) {
439 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
441 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
442 if (rgb.z > 1) rgb.z = 1;
443 } else if (rgb.z > 1) {
444 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
445 if (rgb.x > 1) rgb.x = 1;
447 } else if (rgb.z > 1) {
448 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
450 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
451 if (rgb.y > 1) rgb.y = 1;
452 } else if (rgb.y > 1) {
453 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
454 if (rgb.x > 1) rgb.x = 1;
460 void Draw_WaypointSprite(entity this)
462 if (this.lifetime > 0)
463 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
464 else if (this.lifetime < 0)
465 this.alpha = (time < this.fadetime) ? 1 : 0; // no fading out effect
469 if (this.hideflags & 2)
470 return; // radar only
472 if (autocvar_cl_hidewaypoints >= 2)
475 if (this.hideflags & 1)
476 if (autocvar_cl_hidewaypoints)
477 return; // fixed waypoint
479 InterpolateOrigin_Do(this);
481 float t = entcs_GetTeam(player_localnum) + 1;
483 string spriteimage = "";
488 case SPRITERULE_SPECTATOR:
490 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
491 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
494 spriteimage = this.netname;
496 case SPRITERULE_DEFAULT:
500 spriteimage = this.netname;
505 spriteimage = this.netname;
507 case SPRITERULE_TEAMPLAY:
508 if (t == NUM_SPECTATOR + 1)
509 spriteimage = this.netname3;
510 else if (this.team == t)
511 spriteimage = this.netname2;
513 spriteimage = this.netname;
516 error("Invalid waypointsprite rule!");
520 if (spriteimage == "")
523 ++waypointsprite_newcount;
526 dist = vlen(this.origin - view_origin);
529 a = this.alpha * autocvar_hud_panel_fg_alpha;
531 if (this.maxdistance > waypointsprite_normdistance)
532 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
533 else if (this.maxdistance > 0)
534 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
536 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
539 this.teamradar_color = '1 0 1';
540 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
543 if (time - floor(time) > 0.5)
545 if (this.helpme && time < this.helpme)
546 a *= SPRITE_HELPME_BLINK;
547 else if (this.lifetime > 0) // fading out waypoints don't blink
548 a *= spritelookupblinkvalue(this, spriteimage);
560 rgb = fixrgbexcess(rgb);
565 o = project_3d_to_2d(this.origin);
567 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
568 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
569 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
570 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
572 // scale it to be just in view
576 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
577 ang = atan2(-d.x, -d.y);
581 f1 = d.x / vid_conwidth;
582 f2 = d.y / vid_conheight;
584 if (max(f1, -f1) > max(f2, -f2)) {
587 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
590 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
595 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
598 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
602 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
610 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
611 ang = atan2(-d.x, -d.y);
616 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
617 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
618 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
619 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
621 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
623 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
625 t = waypointsprite_scale * vidscale;
626 a *= waypointsprite_alpha;
629 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
630 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
632 if (edgedistance_min < waypointsprite_edgefadedistance) {
633 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
634 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
636 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
637 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
638 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
641 if (this.build_finished)
643 if (time < this.build_finished + 0.25)
645 if (time < this.build_started)
646 this.health = this.build_starthealth;
647 else if (time < this.build_finished)
648 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
656 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
659 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
662 txt = spritelookuptext(this, spriteimage);
663 if (this.helpme && time < this.helpme)
664 txt = sprintf(_("%s needing help!"), txt);
665 if (autocvar_g_waypointsprite_uppercase)
666 txt = strtoupper(txt);
668 draw_beginBoldFont();
669 if (this.health >= 0)
671 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
674 if (this.build_finished)
679 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
681 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
688 SPRITE_HEALTHBAR_WIDTH * t,
689 SPRITE_HEALTHBAR_HEIGHT * t,
691 SPRITE_HEALTHBAR_BORDER * t,
694 a * SPRITE_HEALTHBAR_BORDERALPHA,
696 a * SPRITE_HEALTHBAR_HEALTHALPHA,
702 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
707 void WaypointSprite_Load_Frames(string ext)
709 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
711 int ext_len = strlen(ext);
712 int n = search_getsize(dh);
713 for (int i = 0; i < n; ++i)
715 string s = search_getfilename(dh, i);
716 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
718 int o = strstrofs(s, "_frame", 0);
719 string sname = strcat("/spriteframes/", substring(s, 0, o));
720 string sframes = substring(s, o + 6, strlen(s) - o - 6);
721 int f = stof(sframes) + 1;
722 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
727 void WaypointSprite_Load();
728 STATIC_INIT(WaypointSprite_Load) {
729 WaypointSprite_Load();
730 WaypointSprite_Load_Frames(".tga");
731 WaypointSprite_Load_Frames(".jpg");
733 void WaypointSprite_Load()
735 waypointsprite_fadedistance = vlen(mi_scale);
736 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
737 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
738 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
739 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
740 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
741 waypointsprite_scale = autocvar_g_waypointsprite_scale;
742 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
743 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
744 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
745 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
746 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
747 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
748 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
749 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
750 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
751 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
752 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
753 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
754 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
755 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
756 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
758 waypointsprite_count = waypointsprite_newcount;
759 waypointsprite_newcount = 0;
764 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
766 string m1 = _m1.netname;
767 string m2 = _m2.netname;
768 string m3 = _m3.netname;
786 void WaypointSprite_UpdateHealth(entity e, float f)
788 f = bound(0, f, e.max_health);
789 if (f != e.health || e.pain_finished)
797 void WaypointSprite_UpdateMaxHealth(entity e, float f)
799 if (f != e.max_health || e.pain_finished)
807 void WaypointSprite_UpdateBuildFinished(entity e, float f)
809 if (f != e.pain_finished || e.max_health)
817 void WaypointSprite_UpdateOrigin(entity e, vector o)
826 void WaypointSprite_UpdateRule(entity e, float t, float r)
828 // no check, as this is never called without doing an actual change (usually only once)
834 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
836 // no check, as this is never called without doing an actual change (usually only once)
838 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
843 void WaypointSprite_Ping(entity e)
846 if (time < e.waypointsprite_pingtime) return;
847 e.waypointsprite_pingtime = time + 0.3;
848 // ALWAYS sends (this causes a radar circle), thus no check
853 void WaypointSprite_HelpMePing(entity e)
855 WaypointSprite_Ping(e);
856 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
860 void WaypointSprite_FadeOutIn(entity e, float t)
865 e.teleport_time = time + t;
867 else if (t < (e.teleport_time - time))
869 // accelerate the waypoint's dying
871 // (e.teleport_time - time) / wp.fade_time stays
872 // e.teleport_time = time + fadetime
873 float current_fadetime;
874 current_fadetime = e.teleport_time - time;
875 e.teleport_time = time + t;
877 e.fade_time = -e.fade_time;
878 e.fade_time = e.fade_time * t / current_fadetime;
884 void WaypointSprite_Init()
886 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
887 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
888 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
891 void WaypointSprite_Kill(entity wp)
894 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
898 void WaypointSprite_Disown(entity wp, float fadetime)
901 if (wp.classname != "sprite_waypoint")
903 backtrace("Trying to disown a non-waypointsprite");
908 if (wp.exteriormodeltoclient == wp.owner)
909 wp.exteriormodeltoclient = NULL;
910 wp.owner.(wp.owned_by_field) = NULL;
913 WaypointSprite_FadeOutIn(wp, fadetime);
917 void WaypointSprite_Think(entity this)
919 bool doremove = false;
921 if (this.fade_time && time >= this.teleport_time)
926 if (this.exteriormodeltoclient)
927 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
930 WaypointSprite_Kill(this);
932 this.nextthink = time; // WHY?!?
935 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
937 // personal waypoints
938 if (this.enemy && this.enemy != view)
942 if (this.rule == SPRITERULE_SPECTATOR)
944 if (!autocvar_sv_itemstime)
946 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
949 else if (this.team && this.rule == SPRITERULE_DEFAULT)
951 if (this.team != view.team)
953 if (!IS_PLAYER(view))
960 entity WaypointSprite_getviewentity(entity e)
962 if (IS_SPEC(e)) e = e.enemy;
963 /* TODO idea (check this breaks nothing)
964 else if (e.classname == "observer")
970 float WaypointSprite_isteammate(entity e, entity e2)
973 return e2.team == e.team;
977 bool WaypointSprite_Customize(entity this, entity client)
979 // this is not in SendEntity because it shall run every frame, not just every update
981 // make spectators see what the player would see
982 entity e = WaypointSprite_getviewentity(client);
984 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
987 return this.waypointsprite_visible_for_player(this, client, e);
990 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
992 void WaypointSprite_Reset(entity this)
994 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
997 WaypointSprite_Kill(this);
1000 entity WaypointSprite_Spawn(
1001 entity spr, // sprite
1002 float _lifetime, float maxdistance, // lifetime, max distance
1003 entity ref, vector ofs, // position
1004 entity showto, float t, // show to whom? Use a flag to indicate a team
1005 entity own, .entity ownfield, // remove when own gets killed
1006 float hideable, // true when it should be controlled by cl_hidewaypoints
1007 entity icon // initial icon
1010 entity wp = new(sprite_waypoint);
1011 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1013 _lifetime = -_lifetime;
1014 wp.teleport_time = time + _lifetime;
1015 wp.exteriormodeltoclient = ref;
1019 setorigin(wp, ref.origin + ofs);
1026 wp.currentammo = hideable;
1030 delete(own.(ownfield));
1031 own.(ownfield) = wp;
1032 wp.owned_by_field = ownfield;
1034 wp.fade_rate = maxdistance;
1035 setthink(wp, WaypointSprite_Think);
1036 wp.nextthink = time;
1037 wp.model1 = spr.netname;
1038 setcefc(wp, WaypointSprite_Customize);
1039 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1040 wp.reset2 = WaypointSprite_Reset;
1042 wp.colormod = spr.m_color;
1043 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1047 entity WaypointSprite_SpawnFixed(
1052 entity icon // initial icon
1055 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1058 entity WaypointSprite_DeployFixed(
1060 float limited_range,
1063 entity icon // initial icon
1073 maxdistance = waypointsprite_limitedrange;
1076 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1079 entity WaypointSprite_DeployPersonal(
1083 entity icon // initial icon
1086 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1089 entity WaypointSprite_Attach(
1092 float limited_range,
1093 entity icon // initial icon
1097 if (player.waypointsprite_attachedforcarrier)
1098 return NULL; // can't attach to FC
1105 maxdistance = waypointsprite_limitedrange;
1108 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1111 entity WaypointSprite_AttachCarrier(
1114 entity icon // initial icon and color
1117 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1118 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1121 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1122 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1127 void WaypointSprite_DetachCarrier(entity carrier)
1129 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1132 void WaypointSprite_ClearPersonal(entity this)
1134 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1137 void WaypointSprite_ClearOwned(entity this)
1139 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1140 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1141 WaypointSprite_Kill(this.waypointsprite_attached);
1144 void WaypointSprite_PlayerDead(entity this)
1146 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1147 WaypointSprite_DetachCarrier(this);
1150 void WaypointSprite_PlayerGone(entity this)
1152 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1153 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1154 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1155 WaypointSprite_DetachCarrier(this);