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)))
21 if(this.currentammo == 1)
23 if(this.exteriormodeltoclient == to)
25 if(this.currentammo == 2)
28 MUTATOR_CALLHOOK(SendWaypoint, this, to, sendflags, f);
29 sendflags = M_ARGV(2, int);
32 WriteByte(MSG_ENTITY, sendflags);
33 WriteByte(MSG_ENTITY, this.wp_extra);
39 WriteByte(MSG_ENTITY, (this.health / this.max_health) * 191.0);
43 float dt = this.pain_finished - time;
44 dt = bound(0, dt * 32, 16383);
45 WriteByte(MSG_ENTITY, (dt & 0xFF00) / 256 + 192);
46 WriteByte(MSG_ENTITY, (dt & 0x00FF));
52 WriteCoord(MSG_ENTITY, this.origin.x);
53 WriteCoord(MSG_ENTITY, this.origin.y);
54 WriteCoord(MSG_ENTITY, this.origin.z);
59 WriteByte(MSG_ENTITY, this.team);
60 WriteByte(MSG_ENTITY, this.rule);
64 WriteString(MSG_ENTITY, this.model1);
67 WriteString(MSG_ENTITY, this.model2);
70 WriteString(MSG_ENTITY, this.model3);
74 WriteCoord(MSG_ENTITY, this.fade_time);
75 WriteCoord(MSG_ENTITY, this.teleport_time);
76 WriteShort(MSG_ENTITY, this.fade_rate); // maxdist
77 WriteByte(MSG_ENTITY, f);
82 WriteByte(MSG_ENTITY, this.cnt); // icon on radar
83 WriteByte(MSG_ENTITY, this.colormod.x * 255.0);
84 WriteByte(MSG_ENTITY, this.colormod.y * 255.0);
85 WriteByte(MSG_ENTITY, this.colormod.z * 255.0);
87 if (WaypointSprite_isteammate(this.owner, WaypointSprite_getviewentity(to)))
89 float dt = (this.waypointsprite_helpmetime - time) / 0.1;
94 WriteByte(MSG_ENTITY, dt);
97 WriteByte(MSG_ENTITY, 0);
105 void Ent_WaypointSprite(entity this, bool isnew);
106 NET_HANDLE(waypointsprites, bool isnew) {
107 Ent_WaypointSprite(this, isnew);
111 void Ent_RemoveWaypointSprite(entity this)
113 if (this.netname) strunzone(this.netname);
114 if (this.netname2) strunzone(this.netname2);
115 if (this.netname3) strunzone(this.netname3);
118 /** flags origin [team displayrule] [spritename] [spritename2] [spritename3] [lifetime maxdistance hideable] */
119 void Ent_WaypointSprite(entity this, bool isnew)
121 int sendflags = ReadByte();
122 this.wp_extra = ReadByte();
125 this.spawntime = time;
127 this.draw2d = Draw_WaypointSprite;
129 IL_PUSH(g_drawables_2d, this);
130 IL_PUSH(g_radaricons, this);
133 InterpolateOrigin_Undo(this);
134 this.iflags |= IFLAG_ORIGIN;
136 if (sendflags & 0x80)
141 this.health = t / 191.0;
142 this.build_finished = 0;
146 t = (t - 192) * 256 + ReadByte();
147 this.build_started = servertime;
148 if (this.build_finished)
149 this.build_starthealth = bound(0, this.health, 1);
151 this.build_starthealth = 0;
152 this.build_finished = servertime + t / 32;
158 this.build_finished = 0;
163 // unfortunately, this needs to be exact (for the 3D display)
164 this.origin_x = ReadCoord();
165 this.origin_y = ReadCoord();
166 this.origin_z = ReadCoord();
167 setorigin(this, this.origin);
172 this.team = ReadByte();
173 this.rule = ReadByte();
179 strunzone(this.netname);
180 this.netname = strzone(ReadString());
186 strunzone(this.netname2);
187 this.netname2 = strzone(ReadString());
193 strunzone(this.netname3);
194 this.netname3 = strzone(ReadString());
199 this.lifetime = ReadCoord();
200 this.fadetime = ReadCoord();
201 this.maxdistance = ReadShort();
202 this.hideflags = ReadByte();
208 this.teamradar_icon = f & BITS(7);
211 this.(teamradar_times[this.teamradar_time_index]) = time;
212 this.teamradar_time_index = (this.teamradar_time_index + 1) % MAX_TEAMRADAR_TIMES;
214 this.teamradar_color_x = ReadByte() / 255.0;
215 this.teamradar_color_y = ReadByte() / 255.0;
216 this.teamradar_color_z = ReadByte() / 255.0;
217 this.helpme = ReadByte() * 0.1;
219 this.helpme += servertime;
222 InterpolateOrigin_Note(this);
224 this.entremove = Ent_RemoveWaypointSprite;
229 float spritelookupblinkvalue(entity this, string s)
231 if (s == WP_Weapon.netname) {
232 if (Weapons_from(this.wp_extra).spawnflags & WEP_FLAG_SUPERWEAPON)
235 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypointblink;
236 if(s == WP_FlagReturn.netname) return 2;
241 vector spritelookupcolor(entity this, string s, vector def)
243 if (s == WP_Weapon.netname || s == RADARICON_Weapon.netname) return Weapons_from(this.wp_extra).wpcolor;
244 if (s == WP_Item.netname || s == RADARICON_Item.netname) return Items_from(this.wp_extra).m_color;
245 if (MUTATOR_CALLHOOK(WP_Format, this, s))
247 return M_ARGV(2, vector);
252 string spritelookuptext(entity this, string s)
254 if (s == WP_RaceStartFinish.netname) return (race_checkpointtime || race_mycheckpointtime) ? _("Finish") : _("Start");
255 if (s == WP_Weapon.netname) return Weapons_from(this.wp_extra).m_name;
256 if (s == WP_Item.netname) return Items_from(this.wp_extra).m_waypoint;
257 if (s == WP_Monster.netname) return get_monsterinfo(this.wp_extra).monster_name;
258 if (MUTATOR_CALLHOOK(WP_Format, this, s))
260 return M_ARGV(3, string);
263 // need to loop, as our netname could be one of three
264 FOREACH(Waypoints, it.netname == s, LAMBDA(
273 void drawrotpic(vector org, float rot, string pic, vector sz, vector hotspot, vector rgb, float a, float f)
275 vector v1, v2, v3, v4;
277 hotspot = -1 * hotspot;
279 // hotspot-relative coordinates of the corners
281 v2 = hotspot + '1 0 0' * sz.x;
282 v3 = hotspot + '1 0 0' * sz.x + '0 1 0' * sz.y;
283 v4 = hotspot + '0 1 0' * sz.y;
285 // rotate them, and make them absolute
286 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
287 v1 = rotate(v1, rot) + org;
288 v2 = rotate(v2, rot) + org;
289 v3 = rotate(v3, rot) + org;
290 v4 = rotate(v4, rot) + org;
293 R_BeginPolygon(pic, f);
294 R_PolygonVertex(v1, '0 0 0', rgb, a);
295 R_PolygonVertex(v2, '1 0 0', rgb, a);
296 R_PolygonVertex(v3, '1 1 0', rgb, a);
297 R_PolygonVertex(v4, '0 1 0', rgb, a);
301 void drawquad(vector o, vector ri, vector up, string pic, vector rgb, float a, float f)
303 R_BeginPolygon(pic, f);
304 R_PolygonVertex(o, '0 0 0', rgb, a);
305 R_PolygonVertex(o + ri, '1 0 0', rgb, a);
306 R_PolygonVertex(o + up + ri, '1 1 0', rgb, a);
307 R_PolygonVertex(o + up, '0 1 0', rgb, a);
311 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)
314 float owidth; // outer width
316 hotspot = -1 * hotspot;
318 // hotspot-relative coordinates of the healthbar corners
323 rot = -rot; // rotate by the opposite angle, as our coordinate system is reversed
324 o = rotate(o, rot) + org;
325 ri = rotate(ri, rot);
326 up = rotate(up, rot);
328 owidth = width + 2 * border;
329 o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
331 drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
332 drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
333 drawquad(o, ri * border, up * theheight, "", rgb, a, f);
334 drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
335 drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
338 // returns location of sprite text
339 vector drawspritearrow(vector o, float ang, vector rgb, float a, float t)
341 float size = 9.0 * t;
342 float border = 1.5 * t;
343 float margin = 4.0 * t;
345 float borderDiag = border * 1.414;
346 vector arrowX = eX * size;
347 vector arrowY = eY * (size+borderDiag);
348 vector borderX = eX * (size+borderDiag);
349 vector borderY = eY * (size+borderDiag+border);
351 R_BeginPolygon("", DRAWFLAG_NORMAL);
352 R_PolygonVertex(o, '0 0 0', '0 0 0', a);
353 R_PolygonVertex(o + rotate(arrowY - borderX, ang), '0 0 0', '0 0 0', a);
354 R_PolygonVertex(o + rotate(borderY - borderX, ang), '0 0 0', '0 0 0', a);
355 R_PolygonVertex(o + rotate(borderY + borderX, ang), '0 0 0', '0 0 0', a);
356 R_PolygonVertex(o + rotate(arrowY + borderX, ang), '0 0 0', '0 0 0', a);
359 R_BeginPolygon("", DRAWFLAG_ADDITIVE);
360 R_PolygonVertex(o + rotate(eY * borderDiag, ang), '0 0 0', rgb, a);
361 R_PolygonVertex(o + rotate(arrowY - arrowX, ang), '0 0 0', rgb, a);
362 R_PolygonVertex(o + rotate(arrowY + arrowX, ang), '0 0 0', rgb, a);
365 return o + rotate(eY * (borderDiag+size+margin), ang);
368 // returns location of sprite healthbar
369 vector drawspritetext(vector o, float ang, float minwidth, vector rgb, float a, vector fontsize, string s)
373 float aspect, sa, ca;
375 sw = stringwidth(s, false, fontsize);
382 // how do corners work?
383 aspect = vid_conwidth / vid_conheight;
385 ca = cos(ang) * aspect;
386 if (fabs(sa) > fabs(ca))
390 algny = 0.5 - 0.5 * (f ? (ca / f) : 0);
395 algnx = 0.5 - 0.5 * (f ? (sa / f) : 0);
403 // we want to be onscreen
408 if (o.x > vid_conwidth - w)
409 o.x = vid_conwidth - w;
410 if (o.y > vid_conheight - h)
411 o.x = vid_conheight - h;
413 o.x += 0.5 * (w - sw);
415 drawstring(o, s, fontsize, rgb, a, DRAWFLAG_NORMAL);
423 vector fixrgbexcess_move(vector rgb, vector src, vector dst)
425 vector yvec = '0.299 0.587 0.114';
426 return rgb + dst * ((src * yvec) / (dst * yvec)) * ((rgb - '1 1 1') * src);
429 vector fixrgbexcess(vector rgb)
432 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 1');
434 rgb = fixrgbexcess_move(rgb, '0 1 0', '0 0 1');
435 if (rgb.z > 1) rgb.z = 1;
436 } else if (rgb.z > 1) {
437 rgb = fixrgbexcess_move(rgb, '0 0 1', '0 1 0');
438 if (rgb.y > 1) rgb.y = 1;
440 } else if (rgb.y > 1) {
441 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 1');
443 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 0 1');
444 if (rgb.z > 1) rgb.z = 1;
445 } else if (rgb.z > 1) {
446 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 0 0');
447 if (rgb.x > 1) rgb.x = 1;
449 } else if (rgb.z > 1) {
450 rgb = fixrgbexcess_move(rgb, '0 0 1', '1 1 0');
452 rgb = fixrgbexcess_move(rgb, '1 0 0', '0 1 0');
453 if (rgb.y > 1) rgb.y = 1;
454 } else if (rgb.y > 1) {
455 rgb = fixrgbexcess_move(rgb, '0 1 0', '1 0 0');
456 if (rgb.x > 1) rgb.x = 1;
462 void Draw_WaypointSprite(entity this)
464 if (this.lifetime > 0)
465 this.alpha = pow(bound(0, (this.fadetime - time) / this.lifetime, 1), waypointsprite_timealphaexponent);
469 if (this.hideflags & 2)
470 return; // radar only
472 if (autocvar_cl_hidewaypoints >= 2)
475 if (this.hideflags & 1 && autocvar_cl_hidewaypoints)
476 return; // fixed waypoint
478 InterpolateOrigin_Do(this);
480 float t = entcs_GetTeam(player_localnum) + 1;
481 string spriteimage = "";
486 case SPRITERULE_SPECTATOR:
488 (autocvar_g_waypointsprite_itemstime == 1 && t == NUM_SPECTATOR + 1)
489 || (autocvar_g_waypointsprite_itemstime == 2 && (t == NUM_SPECTATOR + 1 || warmup_stage || STAT(ITEMSTIME) == 2))
492 spriteimage = this.netname;
494 case SPRITERULE_DEFAULT:
498 spriteimage = this.netname;
503 spriteimage = this.netname;
505 case SPRITERULE_TEAMPLAY:
506 if (t == NUM_SPECTATOR + 1)
507 spriteimage = this.netname3;
508 else if (this.team == t)
509 spriteimage = this.netname2;
511 spriteimage = this.netname;
514 error("Invalid waypointsprite rule!");
518 if (spriteimage == "")
521 ++waypointsprite_newcount;
523 float dist = vlen(this.origin - view_origin);
524 float a = this.alpha * autocvar_hud_panel_fg_alpha;
526 if (this.maxdistance > waypointsprite_normdistance)
527 a *= pow(bound(0, (this.maxdistance - dist) / (this.maxdistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent);
528 else if (this.maxdistance > 0)
529 a *= pow(bound(0, (waypointsprite_fadedistance - dist) / (waypointsprite_fadedistance - waypointsprite_normdistance), 1), waypointsprite_distancealphaexponent) * (1 - waypointsprite_minalpha) + waypointsprite_minalpha;
531 vector rgb = spritelookupcolor(this, spriteimage, this.teamradar_color);
534 this.teamradar_color = '1 0 1';
535 LOG_INFOF("WARNING: sprite of name %s has no color, using pink so you notice it\n", spriteimage);
538 if (time - floor(time) > 0.5)
540 if (this.helpme && time < this.helpme)
541 a *= SPRITE_HELPME_BLINK;
542 else if (this.lifetime > 0) // fading out waypoints don't blink
543 a *= spritelookupblinkvalue(this, spriteimage);
555 rgb = fixrgbexcess(rgb);
560 o = project_3d_to_2d(this.origin);
562 || o.x < (vid_conwidth * waypointsprite_edgeoffset_left)
563 || o.y < (vid_conheight * waypointsprite_edgeoffset_top)
564 || o.x > (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right))
565 || o.y > (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)))
567 // scale it to be just in view
571 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
572 ang = atan2(-d.x, -d.y);
576 f1 = d.x / vid_conwidth;
577 f2 = d.y / vid_conheight;
579 if (max(f1, -f1) > max(f2, -f2)) {
582 d = d * ((0.5 - waypointsprite_edgeoffset_right) / f1);
585 d = d * (-(0.5 - waypointsprite_edgeoffset_left) / f1);
590 d = d * ((0.5 - waypointsprite_edgeoffset_bottom) / f2);
593 d = d * (-(0.5 - waypointsprite_edgeoffset_top) / f2);
597 o = d + '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight;
605 d = o - '0.5 0 0' * vid_conwidth - '0 0.5 0' * vid_conheight;
606 ang = atan2(-d.x, -d.y);
611 float edgedistance_min = min((o.y - (vid_conheight * waypointsprite_edgeoffset_top)),
612 (o.x - (vid_conwidth * waypointsprite_edgeoffset_left)),
613 (vid_conwidth - (vid_conwidth * waypointsprite_edgeoffset_right)) - o.x,
614 (vid_conheight - (vid_conheight * waypointsprite_edgeoffset_bottom)) - o.y);
616 float vidscale = max(vid_conwidth / vid_width, vid_conheight / vid_height);
618 float crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
620 t = waypointsprite_scale * vidscale;
621 a *= waypointsprite_alpha;
624 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
625 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
627 if (edgedistance_min < waypointsprite_edgefadedistance) {
628 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
629 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
631 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
632 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
633 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
636 if (this.build_finished)
638 if (time < this.build_finished + 0.25)
640 if (time < this.build_started)
641 this.health = this.build_starthealth;
642 else if (time < this.build_finished)
643 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
651 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
654 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
657 txt = spritelookuptext(this, spriteimage);
658 if (this.helpme && time < this.helpme)
659 txt = sprintf(_("%s needing help!"), txt);
660 if (autocvar_g_waypointsprite_uppercase)
661 txt = strtoupper(txt);
663 draw_beginBoldFont();
664 if (this.health >= 0)
666 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
669 if (this.build_finished)
674 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
676 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
683 SPRITE_HEALTHBAR_WIDTH * t,
684 SPRITE_HEALTHBAR_HEIGHT * t,
686 SPRITE_HEALTHBAR_BORDER * t,
689 a * SPRITE_HEALTHBAR_BORDERALPHA,
691 a * SPRITE_HEALTHBAR_HEALTHALPHA,
697 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
702 void WaypointSprite_Load_Frames(string ext)
704 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
706 int ext_len = strlen(ext);
707 int n = search_getsize(dh);
708 for (int i = 0; i < n; ++i)
710 string s = search_getfilename(dh, i);
711 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
713 int o = strstrofs(s, "_frame", 0);
714 string sname = strcat("/spriteframes/", substring(s, 0, o));
715 string sframes = substring(s, o + 6, strlen(s) - o - 6);
716 int f = stof(sframes) + 1;
717 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
722 void WaypointSprite_Load();
723 STATIC_INIT(WaypointSprite_Load) {
724 WaypointSprite_Load();
725 WaypointSprite_Load_Frames(".tga");
726 WaypointSprite_Load_Frames(".jpg");
728 void WaypointSprite_Load()
730 waypointsprite_fadedistance = vlen(mi_scale);
731 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
732 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
733 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
734 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
735 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
736 waypointsprite_scale = autocvar_g_waypointsprite_scale;
737 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
738 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
739 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
740 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
741 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
742 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
743 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
744 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
745 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
746 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
747 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
748 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
749 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
750 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
751 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
753 waypointsprite_count = waypointsprite_newcount;
754 waypointsprite_newcount = 0;
759 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
761 string m1 = _m1.netname;
762 string m2 = _m2.netname;
763 string m3 = _m3.netname;
781 void WaypointSprite_UpdateHealth(entity e, float f)
783 f = bound(0, f, e.max_health);
784 if (f != e.health || e.pain_finished)
792 void WaypointSprite_UpdateMaxHealth(entity e, float f)
794 if (f != e.max_health || e.pain_finished)
802 void WaypointSprite_UpdateBuildFinished(entity e, float f)
804 if (f != e.pain_finished || e.max_health)
812 void WaypointSprite_UpdateOrigin(entity e, vector o)
821 void WaypointSprite_UpdateRule(entity e, float t, float r)
823 // no check, as this is never called without doing an actual change (usually only once)
829 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
831 // no check, as this is never called without doing an actual change (usually only once)
833 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
838 void WaypointSprite_Ping(entity e)
841 if (time < e.waypointsprite_pingtime) return;
842 e.waypointsprite_pingtime = time + 0.3;
843 // ALWAYS sends (this causes a radar circle), thus no check
848 void WaypointSprite_HelpMePing(entity e)
850 WaypointSprite_Ping(e);
851 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
855 void WaypointSprite_FadeOutIn(entity e, float t)
860 e.teleport_time = time + t;
862 else if (t < (e.teleport_time - time))
864 // accelerate the waypoint's dying
866 // (e.teleport_time - time) / wp.fade_time stays
867 // e.teleport_time = time + fadetime
868 float current_fadetime = e.teleport_time - time;
869 e.teleport_time = time + t;
871 e.fade_time = -e.fade_time;
872 e.fade_time = e.fade_time * t / current_fadetime;
878 void WaypointSprite_Init()
880 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
881 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
882 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
885 void WaypointSprite_Kill(entity wp)
888 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
892 void WaypointSprite_Disown(entity wp, float fadetime)
895 if (wp.classname != "sprite_waypoint")
897 backtrace("Trying to disown a non-waypointsprite");
902 if (wp.exteriormodeltoclient == wp.owner)
903 wp.exteriormodeltoclient = NULL;
904 wp.owner.(wp.owned_by_field) = NULL;
907 WaypointSprite_FadeOutIn(wp, fadetime);
911 void WaypointSprite_Think(entity this)
913 bool doremove = false;
915 if (this.fade_time && time >= this.teleport_time)
920 if (this.exteriormodeltoclient)
921 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
924 WaypointSprite_Kill(this);
926 this.nextthink = time; // WHY?!?
929 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
931 // personal waypoints
932 if (this.enemy && this.enemy != view)
936 if (this.rule == SPRITERULE_SPECTATOR)
938 if (!autocvar_sv_itemstime)
940 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
943 else if (this.team && this.rule == SPRITERULE_DEFAULT)
945 if (this.team != view.team)
947 if (!IS_PLAYER(view))
954 entity WaypointSprite_getviewentity(entity e)
956 if (IS_SPEC(e)) e = e.enemy;
957 /* TODO idea (check this breaks nothing)
958 else if (e.classname == "observer")
964 float WaypointSprite_isteammate(entity e, entity e2)
967 return e2.team == e.team;
971 bool WaypointSprite_Customize(entity this, entity client)
973 // this is not in SendEntity because it shall run every frame, not just every update
975 // make spectators see what the player would see
976 entity e = WaypointSprite_getviewentity(client);
978 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
981 return this.waypointsprite_visible_for_player(this, client, e);
984 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
986 void WaypointSprite_Reset(entity this)
988 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
991 WaypointSprite_Kill(this);
994 entity WaypointSprite_Spawn(
995 entity spr, // sprite
996 float _lifetime, float maxdistance, // lifetime, max distance
997 entity ref, vector ofs, // position
998 entity showto, float t, // show to whom? Use a flag to indicate a team
999 entity own, .entity ownfield, // remove when own gets killed
1000 float hideable, // true when it should be controlled by cl_hidewaypoints
1001 entity icon // initial icon
1004 entity wp = new(sprite_waypoint);
1005 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1007 _lifetime = -_lifetime;
1008 wp.teleport_time = time + _lifetime;
1009 wp.exteriormodeltoclient = ref;
1013 setorigin(wp, ref.origin + ofs);
1020 wp.currentammo = hideable;
1024 delete(own.(ownfield));
1025 own.(ownfield) = wp;
1026 wp.owned_by_field = ownfield;
1028 wp.fade_rate = maxdistance;
1029 setthink(wp, WaypointSprite_Think);
1030 wp.nextthink = time;
1031 wp.model1 = spr.netname;
1032 setcefc(wp, WaypointSprite_Customize);
1033 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1034 wp.reset2 = WaypointSprite_Reset;
1036 wp.colormod = spr.m_color;
1037 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1041 entity WaypointSprite_SpawnFixed(
1046 entity icon // initial icon
1049 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1052 entity WaypointSprite_DeployFixed(
1054 float limited_range,
1057 entity icon // initial icon
1067 maxdistance = waypointsprite_limitedrange;
1070 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1073 entity WaypointSprite_DeployPersonal(
1077 entity icon // initial icon
1080 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1083 entity WaypointSprite_Attach(
1086 float limited_range,
1087 entity icon // initial icon
1091 if (player.waypointsprite_attachedforcarrier)
1092 return NULL; // can't attach to FC
1099 maxdistance = waypointsprite_limitedrange;
1102 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1105 entity WaypointSprite_AttachCarrier(
1108 entity icon // initial icon and color
1111 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1112 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1115 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1116 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1121 void WaypointSprite_DetachCarrier(entity carrier)
1123 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1126 void WaypointSprite_ClearPersonal(entity this)
1128 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1131 void WaypointSprite_ClearOwned(entity this)
1133 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1134 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1135 WaypointSprite_Kill(this.waypointsprite_attached);
1138 void WaypointSprite_PlayerDead(entity this)
1140 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1141 WaypointSprite_DetachCarrier(this);
1144 void WaypointSprite_PlayerGone(entity this)
1146 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1147 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1148 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1149 WaypointSprite_DetachCarrier(this);