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 string spriteimage = "";
483 case SPRITERULE_SPECTATOR:
484 float t = entcs_GetTeam(player_localnum) + 1;
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:
495 if (this.team == myteam + 1)
496 spriteimage = this.netname;
501 spriteimage = this.netname;
503 case SPRITERULE_TEAMPLAY:
504 if (myteam == NUM_SPECTATOR)
505 spriteimage = this.netname3;
506 else if (this.team == myteam + 1)
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 crosshairdistance = sqrt( pow(o.x - vid_conwidth/2, 2) + pow(o.y - vid_conheight/2, 2) );
616 float t = waypointsprite_scale;
617 a *= waypointsprite_alpha;
620 a = a * (1 - (1 - waypointsprite_distancefadealpha) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
621 t = t * (1 - (1 - waypointsprite_distancefadescale) * (bound(0, dist/waypointsprite_distancefadedistance, 1)));
623 if (edgedistance_min < waypointsprite_edgefadedistance) {
624 a = a * (1 - (1 - waypointsprite_edgefadealpha) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
625 t = t * (1 - (1 - waypointsprite_edgefadescale) * (1 - bound(0, edgedistance_min/waypointsprite_edgefadedistance, 1)));
627 if (crosshairdistance < waypointsprite_crosshairfadedistance) {
628 a = a * (1 - (1 - waypointsprite_crosshairfadealpha) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
629 t = t * (1 - (1 - waypointsprite_crosshairfadescale) * (1 - bound(0, crosshairdistance/waypointsprite_crosshairfadedistance, 1)));
632 if (this.build_finished)
634 if (time < this.build_finished + 0.25)
636 if (time < this.build_started)
637 this.health = this.build_starthealth;
638 else if (time < this.build_finished)
639 this.health = (time - this.build_started) / (this.build_finished - this.build_started) * (1 - this.build_starthealth) + this.build_starthealth;
647 o = drawspritearrow(o, ang, rgb, a, SPRITE_ARROW_SCALE * t);
650 if (autocvar_g_waypointsprite_spam && waypointsprite_count >= autocvar_g_waypointsprite_spam)
653 txt = spritelookuptext(this, spriteimage);
654 if (this.helpme && time < this.helpme)
655 txt = sprintf(_("%s needing help!"), txt);
656 if (autocvar_g_waypointsprite_uppercase)
657 txt = strtoupper(txt);
659 draw_beginBoldFont();
660 if (this.health >= 0)
662 o = drawspritetext(o, ang, (SPRITE_HEALTHBAR_WIDTH + 2 * SPRITE_HEALTHBAR_BORDER) * t, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
665 if (this.build_finished)
670 marg = -(SPRITE_HEALTHBAR_MARGIN + SPRITE_HEALTHBAR_HEIGHT + 2 * SPRITE_HEALTHBAR_BORDER) * t - 0.5 * waypointsprite_fontsize;
672 marg = SPRITE_HEALTHBAR_MARGIN * t + 0.5 * waypointsprite_fontsize;
679 SPRITE_HEALTHBAR_WIDTH * t,
680 SPRITE_HEALTHBAR_HEIGHT * t,
682 SPRITE_HEALTHBAR_BORDER * t,
685 a * SPRITE_HEALTHBAR_BORDERALPHA,
687 a * SPRITE_HEALTHBAR_HEALTHALPHA,
693 o = drawspritetext(o, ang, 0, rgb, a, waypointsprite_fontsize * '1 1 0', txt);
698 void WaypointSprite_Load_Frames(string ext)
700 int dh = search_begin(strcat("models/sprites/*_frame*", ext), false, false);
702 int ext_len = strlen(ext);
703 int n = search_getsize(dh);
704 for (int i = 0; i < n; ++i)
706 string s = search_getfilename(dh, i);
707 s = substring(s, 15, strlen(s) - 15 - ext_len); // strip models/sprites/ and extension
709 int o = strstrofs(s, "_frame", 0);
710 string sname = strcat("/spriteframes/", substring(s, 0, o));
711 string sframes = substring(s, o + 6, strlen(s) - o - 6);
712 int f = stof(sframes) + 1;
713 db_put(tempdb, sname, ftos(max(f, stof(db_get(tempdb, sname)))));
718 void WaypointSprite_Load();
719 STATIC_INIT(WaypointSprite_Load) {
720 WaypointSprite_Load();
721 WaypointSprite_Load_Frames(".tga");
722 WaypointSprite_Load_Frames(".jpg");
724 void WaypointSprite_Load()
726 waypointsprite_fadedistance = vlen(mi_scale);
727 waypointsprite_normdistance = autocvar_g_waypointsprite_normdistance;
728 waypointsprite_minscale = autocvar_g_waypointsprite_minscale;
729 waypointsprite_minalpha = autocvar_g_waypointsprite_minalpha;
730 waypointsprite_distancealphaexponent = autocvar_g_waypointsprite_distancealphaexponent;
731 waypointsprite_timealphaexponent = autocvar_g_waypointsprite_timealphaexponent;
732 waypointsprite_scale = autocvar_g_waypointsprite_scale;
733 waypointsprite_fontsize = autocvar_g_waypointsprite_fontsize;
734 waypointsprite_edgefadealpha = autocvar_g_waypointsprite_edgefadealpha;
735 waypointsprite_edgefadescale = autocvar_g_waypointsprite_edgefadescale;
736 waypointsprite_edgefadedistance = autocvar_g_waypointsprite_edgefadedistance;
737 waypointsprite_edgeoffset_bottom = autocvar_g_waypointsprite_edgeoffset_bottom;
738 waypointsprite_edgeoffset_left = autocvar_g_waypointsprite_edgeoffset_left;
739 waypointsprite_edgeoffset_right = autocvar_g_waypointsprite_edgeoffset_right;
740 waypointsprite_edgeoffset_top = autocvar_g_waypointsprite_edgeoffset_top;
741 waypointsprite_crosshairfadealpha = autocvar_g_waypointsprite_crosshairfadealpha;
742 waypointsprite_crosshairfadescale = autocvar_g_waypointsprite_crosshairfadescale;
743 waypointsprite_crosshairfadedistance = autocvar_g_waypointsprite_crosshairfadedistance;
744 waypointsprite_distancefadealpha = autocvar_g_waypointsprite_distancefadealpha;
745 waypointsprite_distancefadescale = autocvar_g_waypointsprite_distancefadescale;
746 waypointsprite_distancefadedistance = waypointsprite_fadedistance * autocvar_g_waypointsprite_distancefadedistancemultiplier;
747 waypointsprite_alpha = autocvar_g_waypointsprite_alpha * (1 - autocvar__menu_alpha);
749 waypointsprite_count = waypointsprite_newcount;
750 waypointsprite_newcount = 0;
755 void WaypointSprite_UpdateSprites(entity e, entity _m1, entity _m2, entity _m3)
757 string m1 = _m1.netname;
758 string m2 = _m2.netname;
759 string m3 = _m3.netname;
777 void WaypointSprite_UpdateHealth(entity e, float f)
779 f = bound(0, f, e.max_health);
780 if (f != e.health || e.pain_finished)
788 void WaypointSprite_UpdateMaxHealth(entity e, float f)
790 if (f != e.max_health || e.pain_finished)
798 void WaypointSprite_UpdateBuildFinished(entity e, float f)
800 if (f != e.pain_finished || e.max_health)
808 void WaypointSprite_UpdateOrigin(entity e, vector o)
817 void WaypointSprite_UpdateRule(entity e, float t, float r)
819 // no check, as this is never called without doing an actual change (usually only once)
825 void WaypointSprite_UpdateTeamRadar(entity e, entity icon, vector col)
827 // no check, as this is never called without doing an actual change (usually only once)
829 e.cnt = (e.cnt & BIT(7)) | (i & BITS(7));
834 void WaypointSprite_Ping(entity e)
837 if (time < e.waypointsprite_pingtime) return;
838 e.waypointsprite_pingtime = time + 0.3;
839 // ALWAYS sends (this causes a radar circle), thus no check
844 void WaypointSprite_HelpMePing(entity e)
846 WaypointSprite_Ping(e);
847 e.waypointsprite_helpmetime = time + waypointsprite_deployed_lifetime;
851 void WaypointSprite_FadeOutIn(entity e, float t)
856 e.teleport_time = time + t;
858 else if (t < (e.teleport_time - time))
860 // accelerate the waypoint's dying
862 // (e.teleport_time - time) / wp.fade_time stays
863 // e.teleport_time = time + fadetime
864 float current_fadetime = e.teleport_time - time;
865 e.teleport_time = time + t;
867 e.fade_time = -e.fade_time;
868 e.fade_time = e.fade_time * t / current_fadetime;
874 void WaypointSprite_Init()
876 waypointsprite_limitedrange = autocvar_sv_waypointsprite_limitedrange;
877 waypointsprite_deployed_lifetime = autocvar_sv_waypointsprite_deployed_lifetime;
878 waypointsprite_deadlifetime = autocvar_sv_waypointsprite_deadlifetime;
881 void WaypointSprite_Kill(entity wp)
884 if (wp.owner) wp.owner.(wp.owned_by_field) = NULL;
888 void WaypointSprite_Disown(entity wp, float fadetime)
891 if (wp.classname != "sprite_waypoint")
893 backtrace("Trying to disown a non-waypointsprite");
898 if (wp.exteriormodeltoclient == wp.owner)
899 wp.exteriormodeltoclient = NULL;
900 wp.owner.(wp.owned_by_field) = NULL;
903 WaypointSprite_FadeOutIn(wp, fadetime);
907 void WaypointSprite_Think(entity this)
909 bool doremove = false;
911 if (this.fade_time && time >= this.teleport_time)
916 if (this.exteriormodeltoclient)
917 WaypointSprite_UpdateOrigin(this, this.exteriormodeltoclient.origin + this.view_ofs);
920 WaypointSprite_Kill(this);
922 this.nextthink = time; // WHY?!?
925 bool WaypointSprite_visible_for_player(entity this, entity player, entity view)
927 // personal waypoints
928 if (this.enemy && this.enemy != view)
932 if (this.rule == SPRITERULE_SPECTATOR)
934 if (!autocvar_sv_itemstime)
936 if (!warmup_stage && IS_PLAYER(view) && autocvar_sv_itemstime != 2)
939 else if (this.team && this.rule == SPRITERULE_DEFAULT)
941 if (this.team != view.team)
943 if (!IS_PLAYER(view))
950 entity WaypointSprite_getviewentity(entity e)
952 if (IS_SPEC(e)) e = e.enemy;
953 /* TODO idea (check this breaks nothing)
954 else if (e.classname == "observer")
960 float WaypointSprite_isteammate(entity e, entity e2)
963 return e2.team == e.team;
967 bool WaypointSprite_Customize(entity this, entity client)
969 // this is not in SendEntity because it shall run every frame, not just every update
971 // make spectators see what the player would see
972 entity e = WaypointSprite_getviewentity(client);
974 if (MUTATOR_CALLHOOK(CustomizeWaypoint, this, client))
977 return this.waypointsprite_visible_for_player(this, client, e);
980 bool WaypointSprite_SendEntity(entity this, entity to, float sendflags);
982 void WaypointSprite_Reset(entity this)
984 // if a WP wants to time out, let it time out immediately; other WPs ought to be reset/killed by their owners
987 WaypointSprite_Kill(this);
990 entity WaypointSprite_Spawn(
991 entity spr, // sprite
992 float _lifetime, float maxdistance, // lifetime, max distance
993 entity ref, vector ofs, // position
994 entity showto, float t, // show to whom? Use a flag to indicate a team
995 entity own, .entity ownfield, // remove when own gets killed
996 float hideable, // true when it should be controlled by cl_hidewaypoints
997 entity icon // initial icon
1000 entity wp = new(sprite_waypoint);
1001 wp.fade_time = _lifetime; // if negative tells client not to fade it out
1003 _lifetime = -_lifetime;
1004 wp.teleport_time = time + _lifetime;
1005 wp.exteriormodeltoclient = ref;
1009 setorigin(wp, ref.origin + ofs);
1016 wp.currentammo = hideable;
1020 delete(own.(ownfield));
1021 own.(ownfield) = wp;
1022 wp.owned_by_field = ownfield;
1024 wp.fade_rate = maxdistance;
1025 setthink(wp, WaypointSprite_Think);
1026 wp.nextthink = time;
1027 wp.model1 = spr.netname;
1028 setcefc(wp, WaypointSprite_Customize);
1029 wp.waypointsprite_visible_for_player = WaypointSprite_visible_for_player;
1030 wp.reset2 = WaypointSprite_Reset;
1032 wp.colormod = spr.m_color;
1033 Net_LinkEntity(wp, false, 0, WaypointSprite_SendEntity);
1037 entity WaypointSprite_SpawnFixed(
1042 entity icon // initial icon
1045 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, own, ownfield, true, icon);
1048 entity WaypointSprite_DeployFixed(
1050 float limited_range,
1053 entity icon // initial icon
1063 maxdistance = waypointsprite_limitedrange;
1066 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, NULL, ofs, NULL, t, player, waypointsprite_deployed_fixed, false, icon);
1069 entity WaypointSprite_DeployPersonal(
1073 entity icon // initial icon
1076 return WaypointSprite_Spawn(spr, 0, 0, NULL, ofs, NULL, 0, player, waypointsprite_deployed_personal, false, icon);
1079 entity WaypointSprite_Attach(
1082 float limited_range,
1083 entity icon // initial icon
1087 if (player.waypointsprite_attachedforcarrier)
1088 return NULL; // can't attach to FC
1095 maxdistance = waypointsprite_limitedrange;
1098 return WaypointSprite_Spawn(spr, waypointsprite_deployed_lifetime, maxdistance, player, '0 0 64', NULL, t, player, waypointsprite_attached, false, icon);
1101 entity WaypointSprite_AttachCarrier(
1104 entity icon // initial icon and color
1107 WaypointSprite_Kill(carrier.waypointsprite_attached); // FC overrides attached
1108 entity e = WaypointSprite_Spawn(spr, 0, 0, carrier, '0 0 64', NULL, carrier.team, carrier, waypointsprite_attachedforcarrier, false, icon);
1111 WaypointSprite_UpdateMaxHealth(e, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
1112 WaypointSprite_UpdateHealth(e, '1 0 0' * healtharmor_maxdamage(carrier.health, carrier.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
1117 void WaypointSprite_DetachCarrier(entity carrier)
1119 WaypointSprite_Disown(carrier.waypointsprite_attachedforcarrier, waypointsprite_deadlifetime);
1122 void WaypointSprite_ClearPersonal(entity this)
1124 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1127 void WaypointSprite_ClearOwned(entity this)
1129 WaypointSprite_Kill(this.waypointsprite_deployed_fixed);
1130 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1131 WaypointSprite_Kill(this.waypointsprite_attached);
1134 void WaypointSprite_PlayerDead(entity this)
1136 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1137 WaypointSprite_DetachCarrier(this);
1140 void WaypointSprite_PlayerGone(entity this)
1142 WaypointSprite_Disown(this.waypointsprite_deployed_fixed, waypointsprite_deadlifetime);
1143 WaypointSprite_Kill(this.waypointsprite_deployed_personal);
1144 WaypointSprite_Disown(this.waypointsprite_attached, waypointsprite_deadlifetime);
1145 WaypointSprite_DetachCarrier(this);