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, 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);
467 if (this.hideflags & 2)
468 return; // radar only
470 if (autocvar_cl_hidewaypoints >= 2)
473 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
474 return; // fixed waypoint
476 InterpolateOrigin_Do(this);
478 float t = entcs_GetTeam(player_localnum) + 1;
479 string spriteimage = "";
484 case SPRITERULE_SPECTATOR:
486 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
487 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
490 spriteimage = this.netname;
492 case SPRITERULE_DEFAULT:
496 spriteimage = this.netname;
501 spriteimage = this.netname;
503 case SPRITERULE_TEAMPLAY:
504 if (t == NUM_SPECTATOR + 1)
505 spriteimage = this.netname3;
506 else if (this.team == t)
507 spriteimage = this.netname2;
509 spriteimage = this.netname;
512 error("Invalid waypointsprite rule!");
516 if (spriteimage == "")
519 ++waypointsprite_newcount;
521 float dist = vlen(this.origin - view_origin);
522 float a = this.alpha * autocvar_hud_panel_fg_alpha;
524 if (this.maxdistance > waypointsprite_normdistance)
525 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
526 else if (this.maxdistance > 0)
527 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
529 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
532 this.teamradar_color = '1 0 1';
533 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
536 if (time - floor(time) > 0.5)
538 if (this.helpme && time < this.helpme)
539 a *= SPRITE_HELPME_BLINK;
540 else if (this.lifetime > 0) // fading out waypoints don't blink
541 a *= spritelookupblinkvalue(this, spriteimage);
553 rgb = fixrgbexcess(rgb);
558 o = project_3d_to_2d(this.origin);
560 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
561 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
562 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
563 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
565 // scale it to be just in view
569 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
570 ang = atan2(-d.x, -d.y);
574 f1 = d.x / vid_conwidth;
575 f2 = d.y / vid_conheight;
577 if (max(f1, -f1) > max(f2, -f2)) {
580 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
583 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
588 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
591 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
595 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
603 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
604 ang = atan2(-d.x, -d.y);
609 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
610 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
611 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
612 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
614 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
616 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
618 t = waypointsprite_scale * vidscale;
619 a *= waypointsprite_alpha;
622 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
623 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
625 if (edgedistance_min < waypointsprite_edgefadedistance) {
626 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
627 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
629 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
630 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
631 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
634 if (this.build_finished)
636 if (time < this.build_finished + 0.25)
638 if (time < this.build_started)
639 this.health = this.build_starthealth;
640 else if (time < this.build_finished)
641 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
649 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
652 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
655 txt = spritelookuptext(this, spriteimage);
656 if (this.helpme && time < this.helpme)
657 txt = sprintf(_("%s needing help!"), txt);
658 if (autocvar_g_waypointsprite_uppercase)
659 txt = strtoupper(txt);
661 draw_beginBoldFont();
662 if (this.health >= 0)
664 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
667 if (this.build_finished)
672 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
674 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
681 SPRITE_HEALTHBAR_WIDTH * t,
682 SPRITE_HEALTHBAR_HEIGHT * t,
684 SPRITE_HEALTHBAR_BORDER * t,
687 a * SPRITE_HEALTHBAR_BORDERALPHA,
689 a * SPRITE_HEALTHBAR_HEALTHALPHA,
695 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
700 void WaypointSprite_Load_Frames(string ext)
702 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
704 int ext_len = strlen(ext);
705 int n = search_getsize(dh);
706 for (int i = 0; i < n; ++i)
708 string s = search_getfilename(dh, i);
709 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
711 int o = strstrofs(s, "_frame", 0);
712 string sname = strcat("/spriteframes/", substring(s, 0, o));
713 string sframes = substring(s, o + 6, strlen(s) - o - 6);
714 int f = stof(sframes) + 1;
715 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
720 void WaypointSprite_Load();
721 STATIC_INIT(WaypointSprite_Load) {
722 WaypointSprite_Load();
723 WaypointSprite_Load_Frames(".tga");
724 WaypointSprite_Load_Frames(".jpg");
726 void WaypointSprite_Load()
728 waypointsprite_fadedistance = vlen(mi_scale);
729 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
730 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
731 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
732 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
733 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
734 waypointsprite_scale = autocvar_g_waypointsprite_scale;
735 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
736 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
737 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
738 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
739 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
740 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
741 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
742 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
743 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
744 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
745 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
746 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
747 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
748 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
749 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
751 waypointsprite_count = waypointsprite_newcount;
752 waypointsprite_newcount = 0;
757 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
759 string m1 = _m1.netname;
760 string m2 = _m2.netname;
761 string m3 = _m3.netname;
779 void WaypointSprite_UpdateHealth(entity e, float f)
781 f = bound(0, f, e.max_health);
782 if (f != e.health || e.pain_finished)
790 void WaypointSprite_UpdateMaxHealth(entity e, float f)
792 if (f != e.max_health || e.pain_finished)
800 void WaypointSprite_UpdateBuildFinished(entity e, float f)
802 if (f != e.pain_finished || e.max_health)
810 void WaypointSprite_UpdateOrigin(entity e, vector o)
819 void WaypointSprite_UpdateRule(entity e, float t, float r)
821 // no check, as this is never called without doing an actual change (usually only once)
827 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
829 // no check, as this is never called without doing an actual change (usually only once)
831 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
836 void WaypointSprite_Ping(entity e)
839 if (time < e.waypointsprite_pingtime) return;
840 e.waypointsprite_pingtime = time + 0.3;
841 // ALWAYS sends (this causes a radar circle), thus no check
846 void WaypointSprite_HelpMePing(entity e)
848 WaypointSprite_Ping(e);
849 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
853 void WaypointSprite_FadeOutIn(entity e, float t)
858 e.teleport_time = time + t;
860 else if (t < (e.teleport_time - time))
862 // accelerate the waypoint's dying
864 // (e.teleport_time - time) / wp.fade_time stays
865 // e.teleport_time = time + fadetime
866 float current_fadetime = e.teleport_time - time;
867 e.teleport_time = time + t;
869 e.fade_time = -e.fade_time;
870 e.fade_time = e.fade_time * t / current_fadetime;
876 void WaypointSprite_Init()
878 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
879 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
880 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
883 void WaypointSprite_Kill(entity wp)
886 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
890 void WaypointSprite_Disown(entity wp, float fadetime)
893 if (wp.classname != "sprite_waypoint")
895 backtrace("Trying to disown a non-waypointsprite");
900 if (wp.exteriormodeltoclient == wp.owner)
901 wp.exteriormodeltoclient = NULL;
902 wp.owner.(wp.owned_by_field) = NULL;
905 WaypointSprite_FadeOutIn(wp, fadetime);
909 void WaypointSprite_Think(entity this)
911 bool doremove = false;
913 if (this.fade_time && time >= this.teleport_time)
918 if (this.exteriormodeltoclient)
919 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
922 WaypointSprite_Kill(this);
924 this.nextthink = time; // WHY?!?
927 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
929 // personal waypoints
930 if (this.enemy && this.enemy != view)
934 if (this.rule == SPRITERULE_SPECTATOR)
936 if (!autocvar_sv_itemstime)
938 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
941 else if (this.team && this.rule == SPRITERULE_DEFAULT)
943 if (this.team != view.team)
945 if (!IS_PLAYER(view))
952 entity WaypointSprite_getviewentity(entity e)
954 if (IS_SPEC(e)) e = e.enemy;
955 /* TODO idea (check this breaks nothing)
956 else if (e.classname == "observer")
962 float WaypointSprite_isteammate(entity e, entity e2)
965 return e2.team == e.team;
969 bool WaypointSprite_Customize(entity this, entity client)
971 // this is not in SendEntity because it shall run every frame, not just every update
973 // make spectators see what the player would see
974 entity e = WaypointSprite_getviewentity(client);
976 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
979 return this.waypointsprite_visible_for_player(this, client, e);
982 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
984 void WaypointSprite_Reset(entity this)
986 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
989 WaypointSprite_Kill(this);
992 entity WaypointSprite_Spawn(
993 entity spr, // sprite
994 float _lifetime, float maxdistance, // lifetime, max distance
995 entity ref, vector ofs, // position
996 entity showto, float t, // show to whom? Use a flag to indicate a team
997 entity own, .entity ownfield, // remove when own gets killed
998 float hideable, // true when it should be controlled by cl_hidewaypoints
999 entity icon // initial icon
1002 entity wp = new(sprite_waypoint);
1003 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1005 _lifetime = -_lifetime;
1006 wp.teleport_time = time + _lifetime;
1007 wp.exteriormodeltoclient = ref;
1011 setorigin(wp, ref.origin + ofs);
1018 wp.currentammo = hideable;
1022 delete(own.(ownfield));
1023 own.(ownfield) = wp;
1024 wp.owned_by_field = ownfield;
1026 wp.fade_rate = maxdistance;
1027 setthink(wp, WaypointSprite_Think);
1028 wp.nextthink = time;
1029 wp.model1 = spr.netname;
1030 setcefc(wp, WaypointSprite_Customize);
1031 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1032 wp.reset2 = WaypointSprite_Reset;
1034 wp.colormod = spr.m_color;
1035 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1039 entity WaypointSprite_SpawnFixed(
1044 entity icon // initial icon
1047 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1050 entity WaypointSprite_DeployFixed(
1052 float limited_range,
1055 entity icon // initial icon
1065 maxdistance = waypointsprite_limitedrange;
1068 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1071 entity WaypointSprite_DeployPersonal(
1075 entity icon // initial icon
1078 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1081 entity WaypointSprite_Attach(
1084 float limited_range,
1085 entity icon // initial icon
1089 if (player.waypointsprite_attachedforcarrier)
1090 return NULL; // can't attach to FC
1097 maxdistance = waypointsprite_limitedrange;
1100 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1103 entity WaypointSprite_AttachCarrier(
1106 entity icon // initial icon and color
1109 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1110 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1113 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1114 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1119 void WaypointSprite_DetachCarrier(entity carrier)
1121 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1124 void WaypointSprite_ClearPersonal(entity this)
1126 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1129 void WaypointSprite_ClearOwned(entity this)
1131 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1132 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1133 WaypointSprite_Kill(this.waypointsprite_attached);
1136 void WaypointSprite_PlayerDead(entity this)
1138 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1139 WaypointSprite_DetachCarrier(this);
1142 void WaypointSprite_PlayerGone(entity this)
1144 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1145 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1146 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1147 WaypointSprite_DetachCarrier(this);